Chapter 16. Making Slides
The Make Slides example creates a deck of five slides, illustrating different aspects of slide generation:
Slide 1. A slide combining a title and subtitle (see Fig. 142);
Slide 2. A slide with a title, bullet points, and an image (see Fig. 143);
Slide 3. A slide with a title, and an embedded video which plays automatically when that slide appears during a slide show (see Fig. 145);
Slide 4. A slide with an ellipse and a rounded rectangle acting as buttons. During a slide show, clicking on the ellipse starts a video playing in an external viewer. Clicking on the rounded rectangle causes the slide show to jump to the first slide in the deck (see Fig. 146);
Slide 5. This slide contains eight shapes generated using dispatches, including special symbols, block arrows, 3D shapes, flowchart elements, callouts, and stars (see Fig. 151).
make_slides.py creates a slide deck, adds the five slides to it, and finishes by asking if you want to close the document.:
# in make_slides.py
def main(self) -> None:
loader = Lo.load_office(Lo.ConnectPipe())
try:
doc = ImpressDoc(Draw.create_impress_doc(loader))
curr_slide = doc.get_slide(idx=0)
doc.set_visible()
Lo.delay(1_000) # delay to make sure zoom takes
doc.zoom(ZoomKind.ENTIRE_PAGE)
curr_slide.title_slide(
title="Python-Generated Slides",
sub_title="Using LibreOffice",
)
# second slide
curr_slide = doc.add_slide()
self._do_bullets(curr_slide=curr_slide)
# third slide: title and video
curr_slide = doc.add_slide()
curr_slide.title_only_slide("Clock Video")
curr_slide.draw_media(fnm=self._fnm_clock, x=20, y=70, width=50, height=50)
# fourth slide
curr_slide = doc.add_slide()
self._button_shapes(curr_slide=curr_slide)
# fifth slide
if DrawDispatcher:
# windows only
# a bit slow due to gui interaction but a good demo
self._dispatch_shapes(doc)
Lo.print(f"Total no. of slides: {doc.get_slides_count()}")
Lo.delay(2000)
msg_result = MsgBox.msgbox(
"Do you wish to close document?",
"All done",
boxtype=MessageBoxType.QUERYBOX,
buttons=MessageBoxButtonsEnum.BUTTONS_YES_NO,
)
if msg_result == MessageBoxResultsEnum.YES:
doc.close_doc()
Lo.close_office()
else:
print("Keeping document open")
except Exception:
Lo.close_office()
raise
The five slides are explained in the following sections.
16.1 The First Slide (Title and Subtitle)
Draw.create_impress_doc()
calls Lo.create_doc()
, supplying it with the Impress document string type:
# in Draw class
@staticmethod
def create_impress_doc(loader: XComponentLoader) -> XComponent:
return Lo.create_doc(doc_type=Lo.DocTypeStr.IMPRESS, loader=loader)
This creates a new slide deck with one slide whose layout depends on Impress’ default settings. Fig. 140 shows the usual layout when a user starts Impress.
The slide contains two empty presentation shapes – the text rectangle at the top is a TitleTextShape, and the larger rectangle below is a SubTitleShape.
This first slide, which is at index position 0
in the deck, can be referred to by calling Draw.get_slide()
:
doc = ImpressDoc(Draw.create_impress_doc(loader))
curr_slide = doc.get_slide(idx=0)
This is the same method used to get the first page in a Draw document, so we won’t go through it again.
The XDrawPage object can be examined by calling Draw.show_shapes_info()
which lists all the shapes (both draw and presentation ones) on the slide:
# in Draw class (simplified)
@classmethod
def show_shapes_info(cls, slide: XDrawPage) -> None:
print("Draw Page shapes:")
shapes = cls.get_shapes(slide)
for shape in shapes:
cls.show_shape_info(shape)
@classmethod
def show_shape_info(cls, shape: XShape) -> None:
print(f" Shape service: {shape.getShapeType()}; z-order: {cls.get_zorder(shape)}")
@staticmethod
def get_zorder(shape: XShape) -> int:
return int(Props.get(shape, "ZOrder"))
Draw.show_shapes_info()
output for the first slide is:
Draw Page shapes:
Shape service: com.sun.star.presentation.TitleTextShape; z-order: 0
Shape service: com.sun.star.presentation.SubtitleShape; z-order: 1
Obviously, the default layout sometimes isn’t the one we want. One solution would be to delete the unnecessary shapes on the slide, then add the shapes that we do want. A better approach is the programming equivalent of selecting a different slide layout.
This is implemented as several Draw
methods, called Draw.title_slide()
, Draw.bullets_slide()
, Draw.title_only_slide()
,
and Draw.blank_slide()
, which change the slide’s layout to those shown in Fig. 141.
A title/subtitle layout is used for the first slide by calling:
Having a Draw.title_slide()
method may seem a bit silly since we’ve seen that the first slide already uses this layout (e.g. in Fig. 140).
That’s true for the Impress setup, but may not be the case for other installations with different configurations.
The other layouts shown on the right of Fig. 140 could also be implemented as Draw methods, but the four in Fig. 141 seem most useful.
They set the Layout
property in the DrawPage service in the com.sun.star.presentation
module (not the one in the drawing module).
The documentation for DrawPage (use lodoc DrawPage presentation service
) only says that Layout
stores a short; it doesn’t list the possible values or how they correspond to layouts.
For this reason OooDev has PresentationLayoutKind
which is used as the basis of the layout constants in the Draw
class.
Draw.title_slide()
starts by setting the slide’s Layout
property to PresentationLayoutKind.TITLE_SUB
:
# in Draw class (simplified)
@classmethod
def title_slide(cls, slide: XDrawPage, title: str, sub_title: str = "") -> None:
Props.set(slide, Layout=PresentationLayoutKind.TITLE_SUB.value)
xs = cls.find_shape_by_type(slide=slide, shape_type=DrawingNameSpaceKind.TITLE_TEXT)
txt_field = Lo.qi(XText, xs, True)
txt_field.setString(title)
if sub_title:
xs = cls.find_shape_by_type(slide=slide, shape_type=DrawingNameSpaceKind.SUBTITLE_TEXT)
txt_field = Lo.qi(XText, xs, True)
txt_field.setString(sub_title)
See also
This changes the slide’s layout to an empty TitleTextShape and SubtitleShape. The functions adds title and subtitle strings to these shapes, and returns. The tricky part is obtaining a reference to a particular shape so it can be modified.
One (bad) solution is to use the index ordering of the shapes on the slide, which is displayed by Draw.show_shapes_info()
.
It turns out that TitleTextShape is first (i.e. at index 0
), and SubtitleShape second.
This can be used to write the following code:
x_shapes = curr_slide.qi(XShapes, True)
title_shape = Lo.qi(XShape, x_shapes.getByIndex(0))
sub_title_shape = Lo.qi(XShape, x_shapes.getByIndex(1))
This is a bit hacky, so Draw.find_shape_by_type()
is coded instead, which searches for a shape based on its type:
# in Draw class (simplified)
@classmethod
def find_shape_by_type(cls, slide: XDrawPage, shape_type: DrawingNameSpaceKind | str) -> XShape:
shapes = cls.get_shapes(slide)
if not shapes:
raise ShapeMissingError("No shapes were found in the draw page")
st = str(shape_type)
for shape in shapes:
if st == shape.getShapeType():
return shape
raise ShapeMissingError(f'No shape found for "{st}"')
See also
OooDev has DrawingNameSpaceKind
to lookup shape type names.
This allows for finding the title shape by calling:
xs = Draw.find_shape_by_type(curr_slide, DrawingNameSpaceKind.TITLE_TEXT)
16.2 The Second Slide (Title, Bullets, and Image)
The second slide uses a title and bullet points layout, with an image added at the bottom right corner. The relevant lines in make_slides.py are:
# in main() in make_slides.py
# second slide
curr_slide = doc.add_slide()
self._do_bullets(curr_slide=curr_slide)
The result shown in Fig. 143.
Fig. 143 slide is created by _do_bullets()
in make_slides.py:
# in main() in make_slides.py
def _do_bullets(self, curr_slide: ImpressPage[ImpressDoc]) -> None:
# second slide: bullets and image
body = curr_slide.bullets_slide(title="What is an Algorithm?")
# bullet levels are 0, 1, 2,...
body.add_bullet(
level=0,
text="An algorithm is a finite set of unambiguous instructions for solving a problem.",
)
body.add_bullet(
level=1,
text="An algorithm is correct if on all legitimate inputs, it outputs the right answer in a finite amount of time",
)
body.add_bullet(level=0, text="Can be expressed as")
body.add_bullet(level=1, text="pseudocode")
body.add_bullet(level=0, text="flow charts")
body.add_bullet(
level=1,
text="text in a natural language (e.g. English)",
)
body.add_bullet(level=1, text="computer code")
# add the image in bottom right corner, and scaled if necessary
im = curr_slide.draw_image_offset(
fnm=self._fnm_img,
xoffset=ImageOffset(0.6),
yoffset=ImageOffset(0.5),
)
# move below the slide text
im.move_to_bottom()
Draw.bullets_slide()
works in a similar way to Draw.title_slide()
– first the slide’s layout is set, then the presentation shapes are found and modified:
# in Draw class (simplified)
@classmethod
def bullets_slide(cls, slide: XDrawPage, title: str) -> XText:
Props.set(slide, Layout=PresentationLayoutKind.TITLE_BULLETS.value)
xs = cls.find_shape_by_type(slide=slide, shape_type=DrawingNameSpaceKind.TITLE_TEXT)
txt_field = Lo.qi(XText, xs, True)
txt_field.setString(title)
xs = cls.find_shape_by_type(slide=slide, shape_type=DrawingNameSpaceKind.BULLETS_TEXT)
return Lo.qi(XText, xs, True)
See also
The PresentationLayoutKind.TITLE_BULLETS
enum changes the slide’s layout to contain two presentation shapes – a TitleTextShape at the top,
and an OutlinerShape beneath it (as in the second picture in Fig. 141).
Draw.bullets_slide()
calls Draw.find_shape_by_type()
twice to find these shapes, but it does nothing to the OutlinerShape itself,
returning it as an XText reference. This allows text to be inserted into the shape by other code (i.e. by Draw.add_bullet()
).
16.2.1 Adding Bullets to a Text Area
Draw.add_bullet()
converts the shape’s XText reference into an XTextRange, which offers a setString()
method:
# in Draw class (simplified)
@staticmethod
def add_bullet(bulls_txt: XText, level: int, text: str) -> None:
bulls_txt_end = Lo.qi(XTextRange, bulls_txt, True).getEnd()
Props.set(bulls_txt_end, NumberingLevel=level)
bulls_txt_end.setString(f"{text}\n")
See also
As explained Chapter 5. Text API Overview, XTextRange is part of the TextRange service which inherits both paragraph and character property classes, as indicated by Fig. 144.
A look through the ParagraphProperties documentation reveals a NumberingLevel
property which affects the displayed bullet level.
Another way of finding out about the properties associated with XTextRange is to use Props.show_obj_props()
to list all of them:
Props.show_obj_props("TextRange in OutlinerShape", tr)
The bullet text is added with XTextRange.setString()
.
A newline is added to the text before the set, to ensure that the string is treated as a complete paragraph.
The drawback is that the newline causes an extra bullet symbol to be drawn after the real bullet points.
This can be seen in Fig. 143, at the bottom of the slide. (Principal Skinner is pointing at it.)
16.2.2 Offsetting an Image
The Animate Bike example in Chapter 14. Animation employed a version of Draw.draw_image()
based around specifying an (x, y) position on the page and a width and height for the image frame.
Draw.draw_image_offset()
used here is a variant which specifies its position in terms of fractional offsets from the top-left corner of the slide.
from ooodev.office.draw import Draw, ImageOffset
im = curr_slide.draw_image_offset(
fnm=self._fnm_img,
xoffset=ImageOffset(0.6),
yoffset=ImageOffset(0.5),
)
The last two arguments mean that the image’s top-left corner will be placed at a point that is 0.6
of the slide’s width across and 0.5
of its height down.
draw_image_offset()
also scales the image so that it doesn’t extend beyond the right and bottom edges of the slide.
The scaling is the same along both dimensions so the picture isn’t distorted.
ImageOffset
ensure that offsets are not out of range.
The code for Draw.draw_image_offset()
:
# in Draw class (simplified)
@classmethod
def draw_image_offset(
cls, slide: XDrawPage, fnm: PathOrStr, xoffset: ImageOffset, yoffset: ImageOffset
) -> XShape:
slide_size = cls.get_slide_size(slide)
x = round(slide_size.Width * xoffset.Value) # in mm units
y = round(slide_size.Height * yoffset.Value)
max_width = slide_size.Width - x
max_height = slide_size.Height - y
im_size = ImagesLo.calc_scale(fnm=fnm, max_width=max_width, max_height=max_height)
if im_size is None:
Lo.print(f'Unable to calc image size for "{fnm}"')
return None
return cls.draw_image(
slide=slide, fnm=fnm, x=x, y=y, width=im_size.Width, height=im_size.Height
)
See also
draw_image_offset()
uses the slide’s size to determine an (x, y) position for the image, and its width and height.
ImagesLo.calc_scale()
calculates the best width and height for the image frame such that the image will be drawn entirely on the slide:
# in ImagesLo class
@classmethod
def calc_scale(cls, fnm: PathOrStr, max_width: int, max_height: int) -> Size | None:
im_size = cls.get_size_100mm(fnm) # in 1/100 mm units
if im_size is None:
return None
width_scale = (max_width * 100) / im_size.Width
height_scale = (max_height * 100) / im_size.Height
scale_factor = min(width_scale, height_scale)
w = round(im_size.Width * scale_factor / 100)
h = round(im_size.Height * scale_factor / 100)
return Size(w, h)
calc_scale()
uses ImagesLo.get_size100mm()
to retrieve the size of the image in 1/100 mm
units, and then a scale factor is calculated for both the width and height.
This is used to set the image frame’s dimensions when the graphic is loaded by draw_image()
.
16.3 The Third Slide (Title and Video)
The third slide consists of a title shape and a video frame, which looks like Fig. 145.
When this slide appears in a slide show, the video will automatically start playing.
The code for generating this slide is:
# in MakeSlide.main() of make_slides.py
# third slide: title and video
curr_slide = doc.add_slide()
curr_slide.title_only_slide("Clock Video")
curr_slide.draw_media(
fnm=self._fnm_clock, x=20, y=70, width=50, height=50
)
Draw.title_only_slide()
works in a similar way to Draw.title_slide()
and Draw.bullets_slide()
:
# in Draw class (simplified)
@classmethod
def title_only_slide(cls, slide: XDrawPage, header: str) -> None:
Props.set(slide, Layout=PresentationLayoutKind.TITLE_ONLY.value)
xs = cls.find_shape_by_type(slide=slide, shape_type=DrawingNameSpaceKind.TITLE_TEXT)
txt_field = Lo.qi(XText, xs, True)
txt_field.setString(header)
See also
The MediaShape
service doesn’t appear in the Office documentation.
Perhaps one reason for its absence is that the shape behaves a little ‘erratically’.
Although make_slides.py successfully builds a slide deck containing the video.
When the deck is run as a slide show, the video frame is sometimes incorrectly placed, although the video plays correctly.
Draw.draw_media()
is defined as:
# in Draw class (simplified)
@classmethod
def draw_media(
cls, slide: XDrawPage, fnm: PathOrStr, x: int, y: int, width: int, height: int
) -> XShape:
shape = cls.add_shape(
slide=slide, shape_type=DrawingShapeKind.MEDIA_SHAPE, x=x, y=y, width=width, height=height
)
Lo.print(f'Loading media: "{fnm}"')
cls.set_shape_props(shape, Loop=True, MediaURL=mFileIO.FileIO.fnm_to_url(fnm))
See also
In the absence of documentation, Props.show_obj_props()
can be used to list the properties for the MediaShape
:
Props.show_obj_props("Shape", shape)
The MediaURL
property requires a file in URL format, and Loop
is a boolean for making the animation play repeatedly.
16.5 Shape Animation
Shape animations are performed during a slide show, and are regulated through three presentation Shape properties:
Effect
, Speed
and TextEffect
.
Effect
can be assigned a large range of animation effects, which are defined as constants in the AnimationEffect enumeration.
Details can be found in the com.sun.star.presentation module. Another nice summary, in the form of a large table, is in the Developer’s Guide. Fig. 147 shows part of that table.
There are two broad groups of effects: those that move a shape onto the slide when the page appears, and fade effects that make a shape gradually appear in a given spot.
The following code fragment makes the ellipse on the fourth slide slide into view, starting from the left of the slide:
# in _button_shapes() in make_slides.py
ellipse.set_property(
Effect=AnimationEffect.MOVE_FROM_LEFT, Speed=AnimationSpeed.FAST
)
The animation speed takes a AnimationSpeed value and can be set to AnimationSpeed.SLOW
, AnimationSpeed.MEDIUM
, or AnimationSpeed.FAST
.
Unfortunately, there seems to be an issue with some of the Animation Effects as shown in Fig. 148, Fig. 149, and Fig. 150. When some of the effects are set they actually work in reverse. At least this is the case on Windows 10 and LibreOffice 7.3 There seemed to be issues with most of the fade effects. Not all effects were tested due to the volume of effects. There may be more effects of different types not working correctly.
The developer tools of LibreOffice can be used to confirm that Effect
property is actually being set correctly as shown in Fig. 150.
Developer tools are available in LibreOffice 7.3 +
.
More Complex Shape Animations
If you browse chapter 9 of the Impress user’s guide on slide shows, its animation capabilities extend well beyond the constants in AnimationEffect
.
These features are available through the XAnimationNode interface, which is obtained like so:
from com.sun.star.animations import XAnimationNode
from ooodev.loader.lo import Lo
node_supp = Lo.qi(XAnimationNodeSupplier, slide)
slide_node = node_supp.getAnimationNode() # XAnimationNode
XAnimationNode allows a programmer much finer control over animation timings and animation paths for shapes.
XAnimationNode is part of the large com.sun.star.animations
package.
16.6 The Fifth Slide (Various Dispatch Shapes)
The fifth slide is a hacky, slow solution for generating the numerous shapes in Impress’ GUI which have no corresponding classes in the API. The approach uses dispatch commands, OooDev GUI Automation for windows, and Class RobotKeys (first described back in 4.6 Robot Keys).
The resulting slide is shown in Fig. 151.
The shapes in Fig. 151 are just a few of the many available via Impress’ “Drawing Toolbar”, shown in Fig. 152. The relevant menus are labeled and their sub-menus are shown beneath the toolbar.
Each sub-menu shape has a name which appears in a tooltip when the cursor is placed over the shape’s icon. This text turns out to be very useful when writing the dispatch commands.
There’s also a “3D-Objects” toolbar which offers the shapes in Fig. 153.
Some of these 3D shapes are available in the API as undocumented Shape subclasses, but it was unable to programmatically resize the shapes to make them visible. The only way possible to get them to appear at a reasonable size was by creating them with dispatch commands.
Although there’s no mention of these custom and 3D shapes in the Developer’s Guide, their dispatch commands do appear in the
UICommands.ods
spreadsheet (available from https://arielch.fedorapeople.org/devel/ooo/UICommands.ods).
They’re also mentioned, in less detail, in the online documentation for Impress dispatches at
https://wiki.documentfoundation.org/Development/DispatchCommands#Impress_slots_.28sdslots.29
It’s quite easy to match up the tooltip names in the GUI with the dispatch names.
For example, the smiley face in the Symbol shapes menu is called “Smiley Face” in the GUI and .uno:SymbolShapes.smiley
in the UICommands
spreadsheet.
make_slides.py generates the eight shapes shown in Fig. 151 by calling _dispatch_shapes()
:
# in make_slides.py
def _dispatch_shapes(self, doc: ImpressDoc) -> None:
curr_slide = doc.add_slide()
curr_slide.title_only_slide("Dispatched Shapes")
doc.set_visible()
Lo.delay(1_000)
doc.goto_page(page=curr_slide.component)
Lo.print(
f"Viewing Slide number: {Draw.get_slide_number(Draw.get_viewed_page(doc.component))}"
)
# first row
y = 38
_ = curr_slide.add_dispatch_shape(
shape_dispatch=ShapeDispatchKind.BASIC_SHAPES_DIAMOND,
x=20,
y=y,
width=50,
height=30,
fn=DrawDispatcher.create_dispatch_shape,
)
_ = curr_slide.add_dispatch_shape(
shape_dispatch=ShapeDispatchKind.THREE_D_HALF_SPHERE,
x=80,
y=y,
width=50,
height=30,
fn=DrawDispatcher.create_dispatch_shape,
)
dispatch_shape = curr_slide.add_dispatch_shape(
shape_dispatch=ShapeDispatchKind.CALLOUT_SHAPES_CLOUD_CALLOUT,
x=140,
y=y,
width=50,
height=30,
fn=DrawDispatcher.create_dispatch_shape,
)
dispatch_shape.set_bitmap_color(name=DrawingBitmapKind.LITTLE_CLOUDS)
dispatch_shape = curr_slide.add_dispatch_shape(
shape_dispatch=ShapeDispatchKind.FLOW_CHART_SHAPES_FLOWCHART_CARD,
x=200,
y=y,
width=50,
height=30,
fn=DrawDispatcher.create_dispatch_shape,
)
dispatch_shape.set_hatch_color(name=DrawingHatchingKind.BLUE_NEG_45_DEGREES)
# convert blue to black manually
dispatch_hatch = cast(Hatch, dispatch_shape.get_property("FillHatch"))
dispatch_hatch.Color = CommonColor.BLACK
dispatch_shape.set_property(
LineColor=CommonColor.BLACK, FillHatch=dispatch_hatch
)
# Props.show_obj_props("Hatch Shape", dispatch_shape)
# second row
y = 100
dispatch_shape = curr_slide.add_dispatch_shape(
shape_dispatch=ShapeDispatchKind.STAR_SHAPES_STAR_12,
x=20,
y=y,
width=40,
height=40,
fn=DrawDispatcher.create_dispatch_shape,
)
dispatch_shape.set_gradient_color(name=DrawingGradientKind.SUNSHINE)
dispatch_shape.set_property(LineStyle=LineStyle.NONE)
dispatch_shape = curr_slide.add_dispatch_shape(
shape_dispatch=ShapeDispatchKind.SYMBOL_SHAPES_HEART,
x=80,
y=y,
width=40,
height=40,
fn=DrawDispatcher.create_dispatch_shape,
)
dispatch_shape.set_property(FillColor=CommonColor.RED)
_ = curr_slide.add_dispatch_shape(
shape_dispatch=ShapeDispatchKind.ARROW_SHAPES_LEFT_RIGHT_ARROW,
x=140,
y=y,
width=50,
height=30,
fn=DrawDispatcher.create_dispatch_shape,
)
dispatch_shape = curr_slide.add_dispatch_shape(
shape_dispatch=ShapeDispatchKind.THREE_D_CYRAMID,
x=200,
y=y - 20,
width=50,
height=50,
fn=DrawDispatcher.create_dispatch_shape,
)
dispatch_shape.set_bitmap_color(name=DrawingBitmapKind.STONE)
Draw.show_shapes_info(curr_slide.component)
A title-only slide is created, followed by eight calls to Draw.add_dispatch_shape()
to create two rows of four shapes in Fig. 151.
Note that Draw.add_dispatch_shape()
take a fn
parameter. This is basically a call back function.
fn
is expected to be a function that takes a XDrawPage and str
as input parameters and returns XShape or None
.
The reason for this is OooDev is not responsible for automating Windows GUI however, OooDev GUI Automation for windows is.
OooDev GUI Automation for windows provides odevgui_win.draw_dispatcher.DrawDispatcher.create_dispatch_shape()
that handles automating mouse movements and returns the shape.
So, add_dispatch_shape()
is passed as call back function.
See also
16.6.1 Viewing the Fifth Slide
Draw.add_dispatch_shape()
requires the fifth slide to be the active, visible window on- screen.
This necessitates a call to GUI.set_visible()
to make the document visible, but that isn’t quite enough.
Making the document visible causes the first slide to be displayed, not the fifth one.
Impress offers many ways of viewing slides, which are implemented in the API as view classes that inherit the Controller service. The inheritance structure is shown in Fig. 154.
When a Draw or Impress document is being edited, the view is DrawingDocumentDrawView, which supports a number of useful properties,
such as ZoomType
and VisibleArea
. Its XDrawView interface is employed for getting and setting the current page displayed in this view.
Draw.goto_page()
gets the XController interface for the document, and converts it to XDrawView so the visible page can be set:
# in Draw class (simplified)
@classmethod
def goto_page(cls, doc: XComponent, page: XDrawPage) -> None:
try:
ctl = GUI.get_current_controller(doc)
cls.goto_page(ctl, page)
except DrawError:
raise
except Exception as e:
raise DrawError("Error while trying to go to page") from e
@staticmethod
def goto_page(ctl: XController, page: XDrawPage) -> None:
try:
xdraw_view = Lo.qi(XDrawView, ctl)
xdraw_view.setCurrentPage(page)
except Exception as e:
raise DrawError("Error while trying to go to page") from e
See also
After the call to Draw.goto_page()
, the specified draw page will be visible on-screen, and so receive any dispatch commands.
Draw.get_viewed_page()
returns a reference to the currently viewed page by calling XDrawView.getCurrentPage()
:
# in Draw class
@staticmethod
def get_viewed_page(doc: XComponent) -> XDrawPage:
try:
ctl = GUI.get_current_controller(doc)
xdraw_view = Lo.qi(XDrawView, ctl, True)
return xdraw_view.getCurrentPage()
except Exception as e:
raise DrawPageError("Error geting Viewed page") from e
16.6.2 Adding a Dispatch Shape to the Visible Page
If you try adding a smiley face to a slide inside Impress, it’s a two-step process. It isn’t enough only to click on the icon, it’s also necessary to drag the cursor over the page in order for the shape to appear and be resized.
These steps are necessary for all the Drawing toolbar and 3D-Objects shapes, and are emulated by my code.
The programming equivalent of clicking on the icon is done by calling Lo.dispatch_cmd()
,
while implementing a mouse drag utilizes OooDev GUI Automation for windows and Class RobotKeys.
Draw.add_dispatch_shape()
uses Draw.create_dispatch_shape()
to create the shape, and then positions and resizes it:
# in Draw class
@classmethod
def add_dispatch_shape(
cls, slide: XDrawPage, shape_dispatch: ShapeDispatchKind | str,
x: int, y: int, width: int, height: int, fn: DispatchShape
) -> XShape:
cls.warns_position(slide, x, y)
try:
shape = fn(slide, str(shape_dispatch))
if shape is None:
raise NoneError(f'Failed to add shape for dispatch command "{shape_dispatch}"')
cls.set_position(shape=shape, x=x, y=y)
cls.set_size(shape=shape, width=width, height=height)
return shape
except NoneError:
raise
except Exception as e:
raise ShapeError(
f'Error occured adding dispatch shape for dispatch command "{shape_dispatch}"'
) from e