© W. David Ashley and Andrew Krause 2019
W. David Ashley and Andrew KrauseFoundations of PyGTK Developmenthttps://doi.org/10.1007/978-1-4842-4179-0_13

13. More GTK Widgets

W. David Ashley1  and Andrew Krause2
(1)
AUSTIN, TX, USA
(2)
Leesburg, VA, USA
 

You have learned, by now, almost everything this book has to teach you. However, there are a number of widgets that did not quite fit into previous chapters. Therefore, this chapter covers those widgets.

The first two widgets are used for drawing and are named Gtk.DrawingArea and Gtk.Layout. These two widgets are very similar except the Gtk.Layout widget allows you to embed arbitrary widgets into it in addition to using functions for drawing.

In addition, you learn about Gtk.Entry widgets that support automatic completion and calendars. Lastly, you are introduced to widgets that were added in GTK+ 2.10 including status icons, printing support, and recent file managers.

In this chapter, you learn the following.
  • How to use the drawing widgets Gtk.DrawingArea and Gtk.Layout

  • How to use the Gtk.Calendar widget to track information about months of the year

  • How to use widgets introduced in GTK+ 2.10 that provide recent file tracking, printing support, and status icons

  • How to implement automatic completion in a Gtk.Entry widget by applying a Gtk.EntryCompletion object

Drawing Widgets

Gtk.DrawingArea only provides one method, Gtk.DrawingArea.new(), which accepts no parameters and returns a new drawing area widget.
Gtk.DrawingArea.new()

To begin using the widget, you only need to use the supplied by the parent widget Gdk.Window to draw on the area. Remember that a Gdk.Window object is also a Gdk.Drawable object.

One advantage of Gtk.DrawingArea is that it derives from Gtk.Widget, which means that it can be connected to GDK events. There are a number of events to which you want to connect your drawing area. You first want to connect to realize so that you can handle any tasks that need to be performed when the widget is instantiated, such as creating GDK resources. The "configure-event" signal notifies you when you have to handle a change in the size of the widget. Also, "expose-event" allows you to redraw the widget when a portion is exposed that was previously hidden. The "expose-event" signal is especially important, because if you want the content of the drawing area to persist over "expose-event" callbacks, you have to redraw its content. Lastly, you can connect to button and mouse click events so that the user can interact with the widget.

Note

To receive certain types of events, you need to add them to the list of widget events that are supported with widget.add_events(). Also, to receive keyboard input from the user, you need to set the widget.set_can_focus(True) flag, since only focused widgets can detect key presses.

A Drawing Area Example

Listing 13-1 implements a simple drawing program using the Gtk.DrawingArea widget. Since the introduction of GTK+ 3 the Cairo drawing library has replaced the old drawing primitives used in earlier versions of GTK+. This library differs from the old primitives in that it use vector graphics to draw shapes instead of using freehand techniques. Vector graphics are interesting because they don’t lose clarity when resized or transformed.

Figure 13-1 is a screenshot of this application.
../images/142357_2_En_13_Chapter/142357_2_En_13_Fig1_HTML.jpg
Figure 13-1

A drawing area widget with text drawn with the mouse

While this is a very simple program, it nonetheless shows how to interact with the Gtk.DrawingArea widget.
#!/usr/bin/python3
import sys
import cairo
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
SIZE = 30
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_size_request(450, 550)
        drawingarea = Gtk.DrawingArea()
        self.add(drawingarea)
        drawingarea.connect('draw', self.draw)
    def triangle(self, ctx):
        ctx.move_to(SIZE, 0)
        ctx.rel_line_to(SIZE, 2 * SIZE)
        ctx.rel_line_to(-2 * SIZE, 0)
        ctx.close_path()
    def square(self, ctx):
        ctx.move_to(0, 0)
        ctx.rel_line_to(2 * SIZE, 0)
        ctx.rel_line_to(0, 2 * SIZE)
        ctx.rel_line_to(-2 * SIZE, 0)
        ctx.close_path()
    def bowtie(self, ctx):
        ctx.move_to(0, 0)
        ctx.rel_line_to(2 * SIZE, 2 * SIZE)
        ctx.rel_line_to(-2 * SIZE, 0)
        ctx.rel_line_to(2 * SIZE, -2 * SIZE)
        ctx.close_path()
    def inf(self, ctx):
        ctx.move_to(0, SIZE)
        ctx.rel_curve_to(0, SIZE, SIZE, SIZE, 2 * SIZE, 0)
        ctx.rel_curve_to(SIZE, -SIZE, 2 * SIZE, -SIZE, 2 * SIZE, 0)
        ctx.rel_curve_to(0, SIZE, -SIZE, SIZE, -2 * SIZE, 0)
        ctx.rel_curve_to(-SIZE, -SIZE, -2 * SIZE, -SIZE, -2 * SIZE, 0)
        ctx.close_path()
    def draw_shapes(self, ctx, x, y, fill):
        ctx.save()
        ctx.new_path()
        ctx.translate(x + SIZE, y + SIZE)
        self.bowtie(ctx)
        if fill:
            ctx.fill()
        else:
            ctx.stroke()
        ctx.new_path()
        ctx.translate(3 * SIZE, 0)
        self.square(ctx)
        if fill:
            ctx.fill()
        else:
            ctx.stroke()
        ctx.new_path()
        ctx.translate(3 * SIZE, 0)
        self.triangle(ctx)
        if fill:
            ctx.fill()
        else:
            ctx.stroke()
        ctx.new_path()
        ctx.translate(3 * SIZE, 0)
        self.inf(ctx)
        if fill:
            ctx.fill()
        else:
            ctx.stroke()
        ctx.restore()
    def fill_shapes(self, ctx, x, y):
        self.draw_shapes(ctx, x, y, True)
    def stroke_shapes(self, ctx, x, y):
        self.draw_shapes(ctx, x, y, False)
    def draw(self, da, ctx):
        ctx.set_source_rgb(0, 0, 0)
        ctx.set_line_width(SIZE / 4)
        ctx.set_tolerance(0.1)
        ctx.set_line_join(cairo.LINE_JOIN_ROUND)
        ctx.set_dash([SIZE / 4.0, SIZE / 4.0], 0)
        self.stroke_shapes(ctx, 0, 0)
        ctx.set_dash([], 0)
        self.stroke_shapes(ctx, 0, 3 * SIZE)
        ctx.set_line_join(cairo.LINE_JOIN_BEVEL)
        self.stroke_shapes(ctx, 0, 6 * SIZE)
        ctx.set_line_join(cairo.LINE_JOIN_MITER)
        self.stroke_shapes(ctx, 0, 9 * SIZE)
        self.fill_shapes(ctx, 0, 12 * SIZE)
        ctx.set_line_join(cairo.LINE_JOIN_BEVEL)
        self.fill_shapes(ctx, 0, 15 * SIZE)
        ctx.set_source_rgb(1, 0, 0)
        self.stroke_shapes(ctx, 0, 15 * SIZE)
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",**kwargs)
        self.window = None
    def do_activate(self):
        if not self.window:
            self.window = AppWindow(application=self, title="Drawing Areas")
        self.window.show_all()
        self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 13-1

The Drawing Area Widget

The best way to understand how to use Cairo is to imagine that you are an artist using a paintbrush to draw out a shape on canvas.

To begin, you can choose a few characteristics of your brush. You can choose the thickness of your brush and the color you want to paint with. You can also choose the shape of your brush tip. You can choose either a circle or a square.

Once you have chosen your brush, you are ready to start painting. You have to be quite precise when describing what you want to appear.

First, decide where you want to place your brush on the canvas. You do this by supplying an x and a y coordinate. Next, you define how you want your brush stroke to look—an arc, a straight line, and so forth. Finally, you define the point where you want your stroke to end, again by supplying an x and a y coordinate. Triangles and squares are very easy to do!

More complex graphics are generated using variations of the above theme with a few additions, such as Fills (coloring in), transformations (zooming in, moving), and so forth, using the Python interface to Cairo.

Nearly all the work revolves around using the cairo.Context (or cairo_t in the Cairo C API). This is the object that you send your drawing commands to. There are a few options available to initialize this object in different ways.

It is very important to know that there is a difference between the coordinates that you are describing your graphics on and the coordinates that you are displaying your graphics on. When giving a presentation, you draw on your transparent acetate beforehand, and then display it on your overhead projector. Cairo calls the transparent acetate that the user space coordinates and the projected image that the device space coordinates.

When initializing the Cairo context object, we tell it how our description should be displayed. To do this, we supply a transformation matrix. Modifying the transformation matrix can lead to some very interesting results.

One of Cairo’s most powerful features is that it can output graphics in many different formats (it can use multiple back ends). For printing, we can have Cairo translate our graphics into postscript to send to the printer. For onscreen display, Cairo can translate our graphics into something gtk can understand for hardware-accelerated rendering! It has many more important and useful target back ends. On initializing the cairo.Context, we set its target back end, supplying a few details (such as color depth and size), as seen in the next example.

The Layout Widget

In addition to Gtk.DrawingArea, GTK+ provides another drawing widget called Gtk.Layout. This widget is actually a container and differs from Gtk.DrawingArea in that it supports not only drawing primitives but also child widgets. In addition, Gtk.Layout provides scrolling support natively, so it does not need a viewport when added to a scrolled window.

Note

One important distinction to note with layouts is that you should draw to Gtk.Layout’s bin_window member instead of Gtk.Widget’s window . For example, you need to draw to the parent binary window, not the layout window. You can obtain the binary window by calling the layout.get_bin_window() method. This allows child widgets to be correctly embedded into the widget.

New Gtk.Layout widgets are created with Gtk.Layout.new(), which accepts horizontal and vertical adjustments. Adjustments are created for you if you pass None to both function parameters. Since Gtk.Layout has native scrolling support, it can be much more useful than Gtk.DrawingArea when you need to use it with a scrolled window.

However, Gtk.Layout does add some overhead, since it is capable of containing widgets as well. Because of this, Gtk.DrawingArea is a better choice if you only need to draw on the widget’s Gdk.Window.

Child widgets are added to a Gtk.Layout container with layout.put(), which places the child with respect to the top-left corner of the container. Since Gtk.Layout is derived directly from Gtk.Container, it is able to support multiple children.
layout.put(child_widget, x, y)

A call to layout.move()can be used later to relocate the child widget to another location in the Gtk.Layout container.

Caution

Because you place child widgets at specific horizontal and vertical locations, Gtk.Layout presents the same problems as Gtk.Fixed. You need to be careful of these when using the layout widget! You can read more about Gtk.Fixed widget issues in the “Fixed Containers” section in Chapter 4.

Lastly, if you want to force the layout to be a specific size, you can send new width and height parameters to layout.set_size(). You should use this method instead of layout.set_size_request(), because it adjusts the adjustment parameters as well.
layout.set_size(width, height)

Also, unlike size requests, the layout sizing function requires unsigned numbers. This means that you must specify an absolute size for the layout widget. This size should be the total size of the layout, including portions of the widget that are not visible on the screen because they are beyond the bounds of the scrolling area! The size of a Gtk.Layout widget defaults to 100×100 pixels.

Calendars

GTK+ provides the Gtk.Calendar widget, which is a widget that displays one month of a calendar. It allows the user to move among months and years with scroll arrows, as shown in Figure 13-2. You can also display three-letter abbreviations of the day names and week numbers for the chosen year.
../images/142357_2_En_13_Chapter/142357_2_En_13_Fig2_HTML.jpg
Figure 13-2

Gtk.Calendar widget

New Gtk.Calendar widgets are created with Gtk.Calendar.new(). By default, the current date is selected. Therefore, the current month and year stored by the computer are also displayed. You can retrieve the selected date with calendar.get_date() or select a new day with calendar.select_day(). To deselect the currently selected day, you should use calendar.select_day() with a date value of zero.

To customize how the Gtk.Calendar widget is displayed and how it interacts with the user, you should use calendar.set_display_options() to set a bitwise list of Gtk.CalendarDisplayOptions values. The following are nondeprecated values of this enumeration.
  • Gtk.CalendarDisplayOptions.SHOW_HEADING: If set, the name of the month and the year are displayed.

  • Gtk.CalendarDisplayOptions.SHOW_DAY_NAMES: If set, a three-letter abbreviation of each day is shown above the corresponding column of dates. They are rendered between the heading and the main calendar content.

  • Gtk.CalendarDisplayOptions.SHOW_DETAILS: Shows only a when details are provided. See calendar.set_detail_func().

  • Gtk.CalendarDisplayOptions.NO_MONTH_CHANGE: Stops the user from changing the current month of the calendar. If this flag is not set, you are presented with arrows that allow you to go to the next or previous month. By default, the arrows are enabled.

  • Gtk.CalendarDisplayOptions.SHOW_WEEK_NUMBERS: Displays the week number along the left side of the calendar for the current year. The week numbers are hidden by default.

In addition to selecting a single day, you can mark as many days in the month as you want one at a time with calendar.mark_day(). This function returns True if the day was successfully marked.
calendar.mark_day(day)

There are two signals available for detecting when the user selects a day. The first signal, "day-selected", is emitted when the user selects a new day with the mouse or the keyboard. The "day-selected-double-click" signal is emitted when the user selects a day by double-clicking it. This means that you should not need the "button-press-event" signal with the Gtk.Calendar widget in most cases.

Printing Support

GTK+ 2.10 introduced a number of new widgets and objects that add printing support to the library. While there are many objects in this API, in most instances, you only need to directly interact with Gtk.PrintOperation, which is a high-level printing API that can be used across multiple platforms. It acts as a front-end interface for handling most print operations.

In this section, we implement an application that prints the content of a text file that the user selects in a Gtk.FileChooserButton widget. Figure 13-3 is a screenshot of the default print dialog on a Linux system. The user selects a file from the disk using a Gtk.FileChooserButton widget, and clicks the Print button in the main window to open this dialog.
../images/142357_2_En_13_Chapter/142357_2_En_13_Fig3_HTML.jpg
Figure 13-3

Printing dialog

Listing 13-2 begins by defining the necessary data structures for the application and setting up the user interface. The PrintData class holds information about the current print job, which helps with rendering the final product. Widgets is a simple structure that provides access to multiple widgets and the print job information in callback methods.
#!/usr/bin/python3
import sys
import math
from os.path import expanduser
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('PangoCairo', '1.0')
from gi.repository import Gtk, cairo, Pango, PangoCairo
class Widgets:
    def __init__(self):
        self.window = None
        self.chooser = None
        self.data = None
        self.settings = Gtk.PrintSettings.new()
class PrintData:
    def __init__(self):
        self.filename = None
        self.fontsize = None
        self.lines_per_page = None
        self.lines = None
        self.total_lines = None
        self.total_pages = None
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.HEADER_HEIGHT = 20.0
        self.HEADER_GAP = 8.5
        w = Widgets()
        w.window = self
        self.set_border_width(10)
        w.chooser = Gtk.FileChooserButton.new ("Select a File",
                                               Gtk.FileChooserAction.OPEN)
        w.chooser.set_current_folder(expanduser("~"))
        print = Gtk.Button.new_with_label("Print")
        print.connect("clicked", self.print_file, w)
        hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 5)
        hbox.pack_start(w.chooser, False, False, 0)
        hbox.pack_start(print, False, False, 0)
        self.add(hbox)
    def print_file(self, button, w):
        filename = w.chooser.get_filename()
        if filename == None:
            return
        operation = Gtk.PrintOperation.new()
        if w.settings != None:
            operation.set_print_settings(w.settings)
        w.data = PrintData()
        w.data.filename = filename
        w.data.font_size = 10.0
        operation.connect("begin_print", self.begin_print, w)
        operation.connect("draw_page", self.draw_page, w)
        operation.connect("end_print", self.end_print, w)
        res = operation.run(Gtk.PrintOperationAction.PRINT_DIALOG,
                                     w.window)
        if res == Gtk.PrintOperationResult.APPLY:
            if w.settings != None:
                w.settings = None
            settings = operation.get_print_settings()
        elif res == Gtk.PrintOperationResult.ERROR:
            dialog = Gtk.MessageDialog.new(w.window,
                                           Gtk.DialogFlags.DESTROY_WITH_PARENT,
                                           Gtk.MessageType.ERROR,
                                           Gtk.ButtonsType.S_CLOSE,
                                           "Print operation error.")
            dialog.run()
            dialog.destroy()
    def begin_print(self, operation, context, w):
        w.data.lines = []
        f = open(w.data.filename)
        for line in f:
            w.data.lines.append(line)
        f.close()
        w.data.total_lines = len(w.data.lines)
        height = context.get_height() - self.HEADER_HEIGHT –
        self.HEADER_GAP w.data.lines_per_page = math.floor(height /
        (w.data.font_size + 3)) w.data.total_pages =
        (w.data.total_lines - 1) / w.data.lines_per_page+1
        operation.set_n_pages(w.data.total_pages)
    def draw_page(self, operation, context, page_nr, w):
        cr = context.get_cairo_context()
        width = context.get_width()
        layout = context.create_pango_layout()
        desc = Pango.font_description_from_string("Monospace")
        desc.set_size(w.data.font_size * Pango.SCALE)
        layout.set_font_description(desc)
        layout.set_text(w.data.filename, -1)
        layout.set_width(-1)
        layout.set_alignment(Pango.Alignment.LEFT)
        (width, height) = layout.get_size()
        text_height = height / Pango.SCALE
        cr.move_to(0, (self.HEADER_HEIGHT - text_height) / 2)
        PangoCairo.show_layout(cr, layout)
        page_str = "%d of %d" % (page_nr + 1, w.data.total_pages)
        layout.set_text(page_str, -1)
        (width, height) = layout.get_size()
        layout.set_alignment(Pango.Alignment.RIGHT)
        cr.move_to(width - (width / Pango.SCALE),
                     (self.HEADER_HEIGHT - text_height) / 2)
        PangoCairo.show_layout(cr, layout)
        cr.move_to(0, self.HEADER_HEIGHT + self.HEADER_GAP)
        line = page_nr * w.data.lines_per_page
         i = 0
        while i < w.data.lines_per_page and line <
         w.data.total_lines:
            layout.set_text(w.data.lines[line], -1)
            PangoCairo.show_layout(cr, layout)
            cr.rel_move_to(0, w.data.font_size + 3)
            line += 1
            i += 1
    def end_print(self, operation, context, w):
        w.data.lines = None
        w.data = None
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                         **kwargs)
        self.window = None
    def do_activate(self):
        if not self.window:
            self.window = AppWindow(application=self,
                                    title="Calendar")
            self.window.show_all()
            self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 13-2

GTK+ Printing Example

Two values are defined at the top of AppWindow class in Listing 13-2 called HEADER_HEIGHT and HEADER_GAP. HEADER_HEIGHT is the amount of space that is available for the header text to be rendered. This displays information, such as the file name and page number. HEADER_GAP is padding placed between the header and the actual page content.

The PrintData class stores information about the current print job. This includes the location of the file on the disk, the size of the font, the number of lines that can be rendered on a single page, the file’s content, the total number of lines, and the total number of pages.

Print Operations

The next step is to implement the print_file callback method that runs when the Print button is clicked. This method is implemented in Listing 13-2. It takes care of creating the PrintData, connecting all the necessary signals, and creating the print operation.

The first step in printing is to create a new print operation, which is done by calling Gtk.PrintOperation.new(). What makes Gtk.PrintOperation unique is that it uses the platform’s native print dialog if there is one available. On platforms like UNIX, which do not provide such a dialog, Gtk.PrintUnixDialog or the GNOME dialog is used.

Note

For most applications, you should use the Gtk.PrintOperation methods when possible, instead of directly interacting with the print objects. Gtk.PrintOperation was created as a platform-independent printing solution, which cannot be easily reimplemented without a lot of code.

The next step is to call operation.set_print_settings() to apply print settings to the operation. In this application, the Gtk.PrintSettings object is stored as an attribute in the Widgets class instance. If the print operation is successful, you should store the current print settings so that these same settings can be applied to future print jobs.

You then set up the PrintData class by allocating a new instance. The file name is set to the currently selected file in the Gtk.FileChooserButton , which was already confirmed to exist. The print font size is also set to 10.0 points. In text editing applications, you would usually retrieve this font from Gtk.TextView’s current font. In more complex printing applications, the font size may vary throughout a document, but this is a simple example meant only to get you started.

Next, we connect to three Gtk.PrintOperation signals, which are discussed in detail later in this section. In short, begin_print is called before the pages are rendered and can be used for setting the number of pages and doing necessary preparation. The draw_page signal is called for every page in the print job so that it can be rendered. Lastly, the end_print signal is called after the print operation has completed, regardless of whether it succeeded or failed. This callback method cleans up after the print job. A number of other signals can be used throughout the print operation. A full list is in Appendix B.

Once the print operation has been set up, the next step is to begin the printing by calling operation.run(). This method is where you define which task the print operation performs.
operation.run(action, parent)
The Gtk.PrintOperationAction enumeration, shown in the following list, defines which printing task the print operation performs. To print the document, you should use Gtk.PrintOperationAction.PRINT_DIALOG.
  • Gtk.PrintOperationAction.ERROR: Some type of error has occurred in the print operation.

  • Gtk.PrintOperationAction.PREVIEW: Preview the print job that is performed with the current settings. This uses the same callbacks for rendering as the print operation, so it should take little work to get it up and running.

  • Gtk.PrintOperationAction.PRINT: Start printing using the current printing settings without presenting the print dialog. You should only do this if you are 100 percent sure that the user approves of this action. For example, you should have already presented a confirmation dialog to the user.

  • Gtk.PrintOperationAction.EXPORTPRINT: Export the print job to a file. To use this setting, you have to set the export-filename property prior to running the operation.

The last two parameters of operation.run() allow you to define a parent window to use for the print dialog to use None to ignore this parameter. This function does not return until all of the pages have been rendered and are sent to the printer.

When the function does give back control, it returns a Gtk.PrintOperationResult enumeration value. These values give you instructions on what task you should perform next, and whether the print operation succeeded or failed. The four enumeration values are shown in the following list.
  • Gtk.PrintOperationResult.ERROR: Some type of error has occurred in the print operation.

  • Gtk.PrintOperationResult.APPLY: Print settings were changed. Therefore, they should be stored immediately so that changes are not lost.

  • Gtk.PrintOperationResult.CANCEL: The user cancelled the print operation, and you should not save the changes to the print settings.

  • Gtk.PrintOperationResult.PROGRESS: The print operation has yet to be completed. You only get this value if you are running the task asynchronously.

It is possible to run the print operation asynchronously, which means that operation.run() may return before the pages have been rendered. This is set with operation.set_allow_async(). You should note that not all platforms allow this operation, so you should be prepared for this not to work!

If you run the print operation asynchronously, you can use the done signal to retrieve notification when the printing has completed. At this point, you are given the print operation results, and you need to handle it accordingly.

After handling the print operation result, you should also handle the resulting error if it was set and if it exists.

A full list of possible errors under the Gtk.PrintError domain can be found in Appendix E.

One unique feature provided by Gtk.PrintOperation is the ability to show a progress dialog while the print operation is running. This is turned off by default, but it can be turned on with operation.set_show_progress(). This is especially useful if you allow the user to run multiple print operations at the same time.
operation.set_show_progress(boolean)
It may be necessary at times to cancel a current print job, which can be done by calling operation.cancel(). This function is usually used within a begin_print, paginate, or draw_page callback method. It also allows you to provide a Cancel button so that the user can stop in the middle of an active print operation.
operation_cancel()

It is also possible to give a unique name to the print job, which identifies it within an external print monitoring application. Print jobs are given names with operation.set_job_name(). If this is not set, GTK+ automatically designates a name for the print job and numbers consecutive print jobs accordingly.

If you are running the print job asynchronously, you may want to retrieve the current status of the print job. By calling operation.get_status(), a Gtk.PrintStatus enumeration value is returned, which gives more information about the status of the print job. The following is a list of possible print job status values.
  • Gtk.PrintStatus.INITIAL: The print operation has yet to begin. This status is returned while the print dialog is still visible because it is the default initial value.

  • Gtk.PrintStatus.PREPARING: The print operation is being split into pages, and the begin-print signal was emitted.

  • Gtk.PrintStatus.GENERATING_DATA: The pages are being rendered. This is set while the draw-page signal is being emitted. No data has been sent to the printer at this point.

  • Gtk.PrintStatus.SENDING_DATA: Data about the print job is being sent to the printer.

  • Gtk.PrintStatus.PENDING: All of the data has been sent to the printer, but the job has yet to be processed. It is possible that the printer may be stopped.

  • Gtk.PrintStatus.PENDING_ISSUE: There was a problem during the printing. For example, the printer could be out of paper, or there could be a paper jam.

  • Gtk.PrintStatus.PRINTING: The printer is currently processing the print job.

  • Gtk.PrintStatus.FINISHED: The print job has been successfully completed.

  • Gtk.PrintStatus.FINISHED_ABORTED: The print job was aborted. No further action is taken unless you run the job again.

The value returned by operation.get_status() can be used within applications, since it is a numerical value. However, GTK+ also provides the ability to retrieve a string with operation.get_status_string(), which is a human-readable description of the print job status. It is used for debugging output or displaying more information to the user about the print job. For example, it could be displayed on a status bar or in a message dialog.

Beginning the Print Operation

Now that the print operation is set up, it is time to implement the necessary signal callback methods. The “begin-print” signal is emitted when the user initiates printing, which means that all settings have been finalized from the user’s point of view.

In Listing 13-2, the begin_print callback method first retrieves the contents of the file and splits it into the number of lines. The total number of lines is then calculated, which can retrieve the number of pages.

To calculate the number of pages required by the print operation, you need to figure out how many lines can be rendered on every page. The total height of every page is retrieved with context.get_height(), which is stored in a Gtk.PrintContext object. Gtk.PrintContext stores information about how to draw the page. For example, it stores the page setup, width and height dimensions, and dots per inch in both directions. We go into more detail in the draw_page callback method later in this chapter.

Once you have the total height of the page that is available for rendering text, the next step is to divide that height by the font size of the text plus 3 pixels of spacing to be added between each line. The floor() function rounds down the number of lines per page so that clipping does not occur along the bottom of every full page.

Once you have the number of lines per page, you can calculate the number of pages. Then, you must send this value to operation.set_n_pages() by the end of this callback method. The number of pages are used so that GTK+ knows how many times to call the draw_page callback method. This must be set to a positive value so that rendering does not begin until it is changed from its default –1 value.

Rendering Pages

The next step is to implement the draw_page callback method, which is called once for every page that needs to be rendered. This callback method requires the introduction of a library called Cairo. It is a vector graphics library that renders print operations, among other things.

Listing 13-2 begins by retrieving the Cairo drawing context for the current Gtk.PrintContext with context.get_cairo_context(). The returned context object renders print content and then applies it to the PangoLayout.

At the beginning of this callback method, we also need to retrieve two other values from the Gtk.PrintContext. The first is context.get_width(), which returns the width of the document. Notice that we do not need to retrieve the height of the page, since we have already calculated the number of lines that fit on each page. If the text is wider than the page, it is clipped. You have to alter this example to avoid clipping the document.

Caution

The width returned by the Gtk.PrintContext is in pixels. You need to be careful because different functions may use alternative scales, such as Pango units or points!

The next step is to create a PangoLayout with context.create_pango_layout(), which is used for the print context. You should create Pango layouts in this manner for print operations, because the print context already has the correct font metrics applied.

The next operation performed by this function is to add the file name to the top-left corner of the page. To start, layout.set_text() sets the current text stored by the layout to the file name. The width of the layout is set to –1 so that the file name does not wrap at forward slash characters. The text is also aligned to the left of the layout with layout.set_alignment().

Now that the text is added to the layout, cr.move_to() moves the current point in the Cairo context to the left of the page and the center of the header. Note that the height of the PangoLayout must first be reduced by a factor of Pango.SCALE!
cairo.move_to(x, y)
Next, we call cr.show_layout()to draw the PangoLayout on the Cairo context. The top-left corner of the layout is rendered at the current point in the Cairo context. This is why it was first necessary to move to the desired position with cr.move_to().
cairo.show_layout(layout)

After rendering the file name, the same method adds the page count to the top-right corner of each page. You should again note that the width returned by the PangoLayout had to be scaled down by Pango.SCALE so that it would be in the same units as other Cairo values.

The next step is to render all of the lines for the current page. We begin by moving to the left of the page, HEADER_GAP units below the header. Then, each line is incrementally rendered to the Cairo context with cr.show_layout(). One interesting thing to note is that the cursor position in the loop is moved with cr.rel_move_to().
cairo.rel_move_to(dx, dy)

This function moves the current position relative to the previous position. Therefore, after a line is rendered, the current position is moved down one line, which is equal to the font size of the text since the font is monospace.

Tip

By moving the cursor relative to the previous position, it is easy to add an arbitrary amount of spacing between each line of text and the adjacent one as long as this additional height was previously taken into consideration when calculating the number of pages in the begin_print callback method.

When developing with GTK+, you have the whole Cairo library available to you. More basics are covered in the “Cairo Drawing Context” section of this chapter; however, if you are implementing printing in your own applications, you should take the time to learn more about this library from the Cairo API documentation.

Finalizing the Print Operation

After all of the pages have been rendered, the "end-print" signal is emitted. Listing 13-2 shows the end_print callback method, which is used for the signal. It resets modified attributes of the PrintData instance.

Cairo Drawing Context

Cairo is a graphics-rendering library that is used throughout the GTK+ library. In the context of this book, Cairo renders pages during a print operation. This section introduces you to the Pycairo library and some of the classes and drawing methods associated with them.

Pages of a print operation in GTK+ are rendered as Cairo context objects. This object allows you to render text, draw various shapes and lines, and fill clipped areas with color. Let us look at a few methods provided by Cairo for manipulating Cairo drawing contexts.

Drawing Paths

Shapes in Cairo contexts are rendered with paths. A new path is created with cairo.new_path(). You can then retrieve a copy of the new path with cairo.copy_path() and add new lines and shapes to the path.
cairo.copy_path()
There are a number of functions provided for drawing paths, which are listed in Table 13-1. More information about each function can be found in the Cairo API documentation.
Table 13-1

Cairo Path-Drawing Methods

Method

Description

cairo.arc()

Draw an arc in the current path. You must provide the radius of the arc, horizontal and vertical positions of its center, and the start and end angle of the curve in radians.

cairo.curve_to()

Create a Bezier curve in the current path. You must provide the end position of the curve and two control points that calculate the curve.

cairo.line_to()

Draw a line from the current position to the specified point. The current position is simply moved if an initial point does not exist.

cairo.move_to()

Move to a new position in the context, which causes a new subpath to be created.

cairo.rectangle()

Draw a rectangle in the current path. You must provide the coordinates of the top-left corner of the rectangle, its width, and its height.

cairo.rel_curve_to()

This function is the same as cairo.curve_to(), except it is drawn with respect to the current position.

cairo.rel_line_to()

This function is the same as cairo.line_to(), except it is drawn with respect to the current position.

cairo.rel_move_to()

This function is the same as cairo.move_to(), except it is drawn with respect to the current position.

When you are finished with a subpath, you can close it with cairo.path_close(). This encloses the current path so that it can be filled with a color if necessary.

Rendering Options

The current color used for drawing operations on a source is cairo.set_source_rgb(). The color is used until a new color is set. In addition to choosing a color, you can use cairo.set_source_rgba(), which accepts a fifth alpha parameter. Each of the color parameters is a floating-point number between 0.0 and 1.0.

After you have moved to a specific point and set the source color, you can fill the current path with cairo.fill(), which accepts only the context. Alternatively, you can fill a rectangular area with cairo.fill_extents(). This function calculates an area with corners of (x1,y1) and (x2,y2), filling all of the area that is in between those points that is also contained by the current path.
cairo.fill_extents(x1, y1, x2, y2)
Drawing operations, such as curves, can cause edges to become jagged. To fix this, Cairo provides antialiasing to drawings with cairo.set_antialias().
cairo.set_antialias(antialias)
Antialiasing settings are provided by the cairo.Antialias enumeration. The following is a list of values provided by this enumeration.
  • cairo.Antialias.DEFAULT: The default antialiasing algorithm is used.

  • cairo.Antialias.NONE: No antialiasing occurs; instead, an alpha mask is used.

  • cairo.Antialias.GRAY: Uses only a single color for antialiasing. This color is not necessarily gray but is chosen based on the foreground and background colors.

  • cairo.Antialias.SUBPIXEL: Uses subpixel shading provided by LCD screens.

This is simply a short introduction to Cairo drawing contexts. For further information about Cairo, you should reference its API documentation at www.cairographics.org .

Recent Files

In GTK+ 2.10, a new API was introduced that allows you to keep track of recently opened files across applications. In this section, we are going to implement this functionality in the simple text editing application. This application with a recent file chooser is shown in Figure 13-4. Later, in this chapter’s exercise, you are going to add recent file support to your text editor.
../images/142357_2_En_13_Chapter/142357_2_En_13_Fig4_HTML.jpg
Figure 13-4

Recent file chooser dialog

The code in Listing 13-3 sets up the text editing application. Two buttons allow you to open an existing file using a Gtk.FileChooserDialog and save your changes.

Then, there is a Gtk.MenuToolButton that provides two functions. When the button is clicked, a Gtk.RecentChooserDialog is displayed that allows you to select a recent file from the list. The menu in the Gtk.MenuToolButton widget is of the type Gtk.RecentChooserMenu, which shows the ten most recent files.
#!/usr/bin/python3
import sys
import urllib
from urllib.request import pathname2url
import os
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Pango
class Widgets():
    def __init__(self):
        self.window = None
        self.textview = None
        self.recent = None
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        w = Widgets()
        w.window = self
        self.set_border_width(5)
        self.set_size_request(600, 400)
        w.textview = Gtk.TextView.new()
        fd = Pango.font_description_from_string("Monospace 10")
        self.modify_font(fd)
        swin = Gtk.ScrolledWindow.new(None, None)
        openbutton = Gtk.Button.new_with_label("open")
        save = Gtk.Button.new_with_label("Save")
        icon_theme = Gtk.IconTheme.get_default()
        icon = icon_theme.load_icon("document-open", -1,
                                    Gtk.IconLookupFlags.FORCE_SIZE)
        image = Gtk.Image.new_from_pixbuf(icon)
        w.recent = Gtk.MenuToolButton.new(image, "Recent Files")
        manager = Gtk.RecentManager.get_default()
        menu = Gtk.RecentChooserMenu.new_for_manager(manager)
        w.recent.set_menu(menu)
        menu.set_show_not_found(False)
        menu.set_local_only(True)
        menu.set_limit(10)
        menu.set_sort_type(Gtk.RecentSortType.MRU)
        menu.connect("selection-done", self.menu_activated, w)
        openbutton.connect("clicked", self.open_file, w)
        save.connect("clicked", self.save_file, w)
        w.recent.connect("clicked", self.open_recent_file, w)
        hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 5)
        hbox.pack_start(openbutton, False, False, 0)
        hbox.pack_start(save, False, False, 0)
        hbox.pack_start(w.recent, False, False, 0)
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 5)
        swin.add(w.textview)
        vbox.pack_start(hbox, False, False, 0)
        vbox.pack_start(swin, True, True, 0)
        w.window.add(vbox)
    def save_file(self, save, w):
        filename = w.window.get_title()
        buffer = w.textview.get_buffer()
        (start, end) = buffer.get_bounds()
        content = buffer.get_text(start, end, False)
        f = open(filename, 'w')
        f.write(content)
        f.close()
    def menu_activated(self, menu, w):
        filename = menu.get_current_uri()
        if filename != None:
            fn = os.path.basename(filename)
            f = open(fn, 'r')
            contents = f.read()
            f.close()
            w.window.set_title(fn)
            buffer = w.textview.get_buffer()
            buffer.set_text(content, -1)
        else:
            print("The file '%s' could not be read!" % filename)
    def open_file(self, openbutton, w):
        dialog = Gtk.FileChooserDialog(title="Open File", parent=w.window,
                                       action=Gtk.FileChooserAction.OPEN,
                                       buttons=("Cancel", Gtk.ResponseType.CANCEL,"Open", Gtk.ResponseType.OK))
        if dialog.run() == Gtk.ResponseType.OK:
            filename = dialog.get_filename()
            content = ""
            f = open(filename, 'r')
            content = f.read()
            f.close()
            if len(content) > 0:
                # Create a new recently used
                resource. data = Gtk.RecentData()
                data.display_name = None
                data.description = None
                data.mime_type = "text/plain"
                data.app_name =
                os.path.basename(__file__)
                data.app_exec = " " + data.app_name +
                "%u" #data.groups = ["testapp", None]
                data.is_private = False
                url = pathname2url(filename)
                # Add the recently used resource to the default
                recent manager. manager =
                Gtk.RecentManager.get_default()
                result = manager.add_full(url, data)
                # Load the file and set the filename as the title of
                the window. w.window.set_title(filename)
                buffer =
                w.textview.get_buffer()
                buffer.set_text(content,-1)
        dialog.destroy()
    def open_recent_file(self, recent, w):
        manager = Gtk.RecentManager.get_default()
        dialog = Gtk.RecentChooserDialog(title="Open Recent File",
                                         parent=w.window,
                                         recent_manager=manager,
                                         buttons=("Cancel",
                                         Gtk.ResponseType.CANCEL,
                                         "Open",
                                         Gtk.ResponseType.OK))
        # Add a filter that will display all of the files in
        the dialog. filter = Gtk.RecentFilter.new()
        filter.set_name("All Files")
        filter.add_pattern("*") dialog.add_filter(filter)
        # Add another filter that will only display plain
        text files. filter = Gtk.RecentFilter.new()
        filter.set_name("Plain Text")
        filter.add_mime_type("text/plain")
        dialog.add_filter(filter)
        dialog.set_show_not_found(False)
        dialog.set_local_only(True)
        dialog.set_limit(10)
        dialog.set_sort_type(Gtk.RecentSortType.MRU)
        if dialog.run() == Gtk.ResponseType.OK:
            filename = dialog.get_current_uri()
            if filename != None:
                   # Remove the "file://" prefix from the beginning of the
                   # URI if it exists.
                   content = ""
                   fn = os.path.basename(filename)
                   f = open(fn, 'r')
                   contents = f.read()
                   f.close()
                   if len(content) > 0:
                       w.window.set_title(fn)
                       buffer = w.textview.get_buffer()
                       buffer.set_text(content, -1)
                   else:
                       print("The file '%s' could not be read!" % filename)
        dialog.destroy()
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                         **kwargs)
        self.window = None
    def do_activate(self):
        if not self.window:
            self.window = AppWindow(application=self, title="Recent Files")
        self.window.show_all()
        self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 13-3

Remembering Recently Opened Files

A central class called Gtk.RecentManager handles recent file information. It is possible to create your own from scratch, but if you want to share recent files across applications, you can retrieve the default with Gtk.RecentManager.get_default(). This allows you to share recent files with applications, such as gedit, GNOME’s recent documents menu, and others that take advantage of the Gtk.RecentManager API.

We next create a new Gtk.RecentChooserMenu widget from the default Gtk.RecentManager. This menu displays recent files and (optionally) number the menu items created with Gtk.RecentChooserMenu.new_for_manager(). The files are not numbered by default, but this property can be changed by setting "show-numbers" to True or by calling menu.set_show_numbers().

Gtk.RecentChooserMenu implements the Gtk.RecentChooser interface, which provides the functionality you need for interacting with the widget. In Listing 13-3, a number of Gtk.RecentChooser properties customize the menu. These also apply to two other widgets that implement the Gtk.RecentChooser interface: Gtk.RecentChooserDialog and Gtk.RecentChooserWidget.

It is possible that recent files in the list have been removed since they were added. In this case, you may not want to display them in the list. You can hide recent files that no longer exist with rchooser.set_show_not_found(). This property only works with files that are located on the local machine.

Tip

You may actually want to show files that are not found to the user. If the user selects a file that does not exist, you can then easily remove it from the list after informing the user about the problem.

By default, only local files are shown, which means that they have a file:// Uniform Resource Identifier (URI) prefix. A URI refers to things, such as file locations or Internet addresses based on their prefixes. Using only the file:// prefix guarantees that they are located on the local machine. You can set this property to False to show recent files that are located at a remote location. You should note that remote files are not filtered out if they no longer exist!

If the list includes a large number of recent files, you probably will not want to list all of them in the menu. A menu with a hundred items is quite large! Therefore, you can use recentchooser.set_limit()to set a maximum number of recent items that are displayed in the menu.
recentchooser.set_limit(limit)
When you set a limit on the number of elements, which files are shown depends on the sort type you defined with recentchooser.set_sort_type(). By default, this is set to Gtk.RecentSortType.NONE. The following are the available values in the Gtk.RecentSortType enumeration.
  • Gtk.RecentSortType.NONE: The list of recent files is not sorted at all and is returned in the order that they appear. This should not be used when you are limiting the number of elements that are displayed, because you cannot predict which files will be displayed!

  • Gtk.RecentSortType.MRU: Sorts the most recently added files first in the list. This is most likely the sorting method you want to use, because it places the most recent file at the beginning of the list.

  • Gtk.RecentSortType.LRU: Sorts the least-recently added files first in the list.

  • Gtk.RecentSortType.CUSTOM: Uses a custom sorting function to sort the recent files. To use this, you need recentmanager.set_sort_func() to define the sorting method.

The last part of this example saves the file under the specified name. When a file is opened in this text editor, the window title is set to the file name. This file name is used to save the file. Therefore, be careful because this simple text editor cannot be used to create new files!

Recent Chooser Menu

You have just learned about the Gtk.RecentChooserMenu widget. Listing 13-3 implements the "selection-done" callback method that was connected to it. This function retrieves the selected URI and opens the file if it exists.

You can use recentchooser.get_current_uri() to retrieve the currently selected recent file, since only one item can be selected. Since we restricted the menu to only displaying local files, we need to remove the file:// prefix from the URI. If you are allowing remote files to be displayed, you may need to remove different prefixes from the URI, such as http://. You can use the Python method os.path.basename() to remove URI prefixes.
os.path.basename(filename)
os.path.basename(filename)

After the prefix is removed, we attempt to open the file. If the file was successfully opened, the window title is set to the file name and the file is opened; otherwise, a warning is presented to the user that the file could not be opened.

Adding Recent Files

When the Open button is pressed, we want to allow the user to select a file to open from a Gtk.FileChooserDialog . If the file is opened, it is added to the default Gtk.RecentManager.

If the file is successfully opened, recentmanager.add_full() adds it as a new recent item to the default Gtk.RecentManager. To use this method, you need two items. First, you need the URI, which is created by appending the file name to file:// to show that it is a local file. This file name can be built with pathname2url() from the url import.
pathname2url(filepath)

Secondly, you need an instance of the Gtk.RecentData class. The content of this class are a set of attributes that describe the data needed to store the file information to the Gtk.RecentManager. display_name displays a shortened name instead of the file name, and description is a short description of the file. Both of these values can safely be set to None.

You then have to specify a MIME type for the file, the name of your application, and the command line used to open the file. The name of your application can be retrieved by calling the Python library method os.path.basename(__file__). There a number of ways to get the program name but you can also safely set this to None.

Next, groups is a list of strings that designate what groups the resource belongs to. You are able to use this to filter out files that do not belong to a specific group.

The last member, is_private, specifies whether this resource is available to applications that did not register it. By setting this to True, you can prevent other applications that use the Gtk.RecentManager API from displaying this recent file.

Once you construct the Gtk.RecentData instance, it can be added along with the recent file URI as a new resource with recentmanager.add_full(). You can also add a new recent item with recentmanager.add_item(), which creates a Gtk.RecentData object for you.

To remove a recent item, call recentmanager.remove_item(). This function returns True if a file with the specified URI is successfully removed. If not, an error under theGtk.RecentManagerError domain is set. You can also remove all recent items from the list with recentmanager.purge_items().
recentmanagerremove_item(uri)

Caution

You should avoid purging all of the items in the default Gtk.RecentManager! This removes recent items that are registered by every application, which the user probably does not want since your application should not alter recent resources from other applications.

Recent Chooser Dialog

GTK+ also provides a widget called Gtk.RecentChooserDialog, which displays recent files in a convenient dialog. This widget implements the Gtk.RecentChooser interface, so it is very similar in functionality to Gtk.RecentChooserMenu. In Listing 13-3, open_recent_file shows how to allow the user to open a recent file with this widget.

New Gtk.RecentChooserDialog widgets are created in a similar way to dialogs with Gtk.RecentChooserDialog(). This function accepts a title for the dialog, a parent window, a Gtk.RecentManager widget to display, and pairs of buttons and response identifiers.

Listing 13-3 introduces recent file filters. New Gtk.RecentFilter objects are created with Gtk.RecentFilter.new(). Filters display only recent files that follow installed patterns.
filter.set_name("All Files")
filter.add_pattern("*")
dialog.add_filter(filter)

The next step is to set the name of the filter. This name is displayed in the combo box where the user chooses which filter to use. There are many ways to create filters, including with filter. add_pattern() , which finds filters with matching patterns. The asterisk character can be used as the wildcard. There are also functions for matching MIME types, image file types, application names, group names, and ages in days. Next, use recentchooser.add_filter() to add the Gtk.RecentFilter to the recent chooser.

With the Gtk.RecentChooserDialog widgets, it is possible to choose multiple files with recentchooser.set_select_multiple(). If the user can select multiple files, you want to use recentchooser.get_uris() to retrieve all of the selected files.
recentchooser.get_uris(length)

This function also returns the number of elements in the list of strings.

Automatic Completion

You learned about the Gtk.Entry widget in Chapter 5, but GTK+ also provides the Gtk.EntryCompletion object. Gtk.EntryCompletion is derived from GObject and provides the user with automatic completion in Gtk.Entry. Figure 13-5 shows an example Gtk.Entry that is providing the user with multiple selections. Note that the user also has the option of ignoring the choices and entering an arbitrary string.
../images/142357_2_En_13_Chapter/142357_2_En_13_Fig5_HTML.jpg
Figure 13-5

Gtk.EntryCompletion automatic completion

Listing 13-4 implements a Gtk.Entry widget that asks you to enter the name of a GTK+ widget. All of the strings in the Gtk.EntryCompletion widget that have the same prefix as the entered text are displayed as choices. This example shows just how easy it is to get automatic completion up and running.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        widgets = ["GtkDialog", "GtkWindow", "GtkContainer",
        "GtkWidget"] self.set_border_width(10)
        label = Gtk.Label.new("Enter a widget in the following GtkEntry:")
        entry = Gtk.Entry.new()
        # Create a GtkListStore that will hold autocompletion
        possibilities. types = (GObject.TYPE_STRING,)
        store = Gtk.ListStore.new(types) for widget in widgets:
        iter = store.append() store.set(iter, 0, widget)
        completion = Gtk.EntryCompletion.new()
        entry.set_completion(completion)
        completion.set_model(store)
        completion.set_text_column(0)
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
        vbox.pack_start(label, False, False, 0)
        vbox.pack_start(entry, False, False, 0)
        self.add(vbox)
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                         **kwargs)
        self.window = None
    def do_activate(self):
        if not self.window:
            self.window = AppWindow(application=self, title="Automatic Completion")
        self.window.show_all()
        self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 13-4

Automatic Completion

To implement a Gtk.EntryCompletion, you need to first create a new Gtk.ListStore that displays the choices. The model in this example only has one textual column, but it is acceptable to provide a more complex Gtk.ListStore as long as one column is of the type GObject.TYPE_STRING.

New Gtk.EntryCompletion objects are created with Gtk.EntryCompletion.new(). You can then apply it to an existing Gtk.Entry widget with entry.set_completion(). GTK+ takes care of displaying matches and applying the choices by default.

Next, completion.set_model() applies the tree model to the Gtk.EntryCompletion object. If there was already a model applied to the object, it is replaced. You also have to use completion.set_text_column() to designate which column contains the string, since models do not have to be only a single column. If you do not set the text column, automatic completion will not work because the text column is set to –1 by default.

It is possible to display as much of the prefix as is common to all of the matches with completion.set_inline_completion(). You should note that inline completion is case sensitive, but automatic completion is not! If you are using this, you may want to set completion.set_popup_single_match(), which prevents the pop-up menu from being displayed when there is only a single match.

You can use completion.set_popup_set_width() to force the pop-up menu to be the same width as the Gtk.Entry widget. This corresponds to Gtk.EntryCompletion’s popupset_width property.

If there are a lot of matches, you may want to set the minimum match length with completion.set_minimum_key_length(). This is useful when there is such a large number of elements in the list that it would take a long time for the list to be rendered on the screen.

Test Your Understanding

In this chapter’s exercise, you finish the text editing application that has been the focus of multiple exercises in past chapters. It requires you to integrate the automatic completion, printing, and recent file capabilities into your application.

Exercise 1: Creating a Full Text Editor

In this exercise, you complete the text editor that you have been creating in the last few chapters. You add three new features to the application.

First, add the automatic completion feature, which should be implemented to remember past searches in the search toolbar. The application has to remember the past searches for only the current instance of the application runtimes. Next, add printing support, which includes printing and print preview abilities. Printing support can be easily implemented with the high-level Gtk.PrintOperation class. Lastly, instruct the text editor to remember the last five files loaded using the Gtk.RecentManager class.

So that you do not have to rewrite previous aspects of the application, you should use the solution to a Chapter 11 exercise or download that solution from this book’s official web site.

Summary

In this chapter, you learned about a number of widgets that did not quite fit into previous chapters. These widgets and objects are summarized in the following list.
  • Gtk.DrawingArea: An empty widget that is meant to allow you to draw on its Gdk.Window object, which is also a Gdk.Drawable object.

  • Gtk.Layout: This widget is like Gtk.DrawingArea, except it allows you to embed widgets within its interface as well. It introduces overhead, so you should| not use this widget if you want only drawing capabilities.

  • Gtk.Calendar: Display a single month for the chosen year. This widget allows the user to select a date, and you can mark multiple dates programmatically.

  • Gtk.PrintOperation: A high-level printing API that is platform independent. There are many other objects provided for implementing printing support, but most actions should be handled with the Gtk.PrintOperation class so that it functions across multiple platforms.

  • Gtk.RecentManager: A simple class for managing lists of recent files. These lists can be shared across applications. Menu and dialog widgets are provided for displaying recent files.

  • Gtk.EntryCompletion: Provide automatic completion support to Gtk.Entry widgets. The choices are composed of a Gtk.ListStore object filled with possible matches.

You have now learned all of the topics that this book intended to introduce. In the next chapter, you are presented with five complete applications that take advantage of topics that were covered in the past 12 chapters.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.187.223