Make your own sugar activities

Porting your Sugar Activities to Gtk3

by Aneesh Dogra

This is a guide to port existing sugar applications from gtk2 to gtk3, and from sugar to sugar3.

Important Changes in sugar3 :-

  • The keep button has been removed completely
  • The old-style toolbar has been removed
  • Do not use set_toolbox anymore use set_toolbar_box instead.
  • Remove import of deprecated ActivityToolbox. We have an ActivityToolbar in sugar3.activity.widgets now.
  • Support for 'service_name' and 'class' has been removed from the activity.info make sure you are using: 'bundle_id' instead of 'service_name' and 'exec' instead of 'class'

This guide is organized into the following sections :-

  1. Imports
  2. Gtk API changes
  3. New Toolbar
  4. Boxes
  5. Pango

Imports

The first step in the gtk2->gtk3 porting is to replace/remove all the imports of gtk2 or any library using gtk2.

import gtk

to :

from gi.repository import Gtk

 Any references to gtk.gdk should be replaced by Gdk and Gdk should be imported.

from gi.repository import Gdk

 Any imports to sugar libraries should be replaced with sugar3 libraries (you need sugar-toolkit-gtk3)

from sugar.activity import activity

 to:

from sugar3.activity import activity

 do the same for other sugar.* imports.

Changes to the setup.py

You have to change the bundlebuilder import in your activity's setup.py file.

from sugar.activity import bundlebuilder

 to:

from sugar3.activity import bundlebuilder

 These are the header changes you need to do for ReadEText 1 Activity :-

import os
import zipfile
import gtk
import pango
from sugar.activity import activity
from sugar.graphics import style

to:

import os
import zipfile
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import Pango
from sugar3.activity import widgets
from sugar3.activity.widgets import StopButton
from sugar3.activity import activity
from sugar3.graphics import style

Notice: That we add 2 more imports for StopButton and widgets. This is because the ActivityToolbox in sugar.activity.activity is replaced by ActivityToolbar in sugar.activity.widgets.

The ActivityToolbar doesn't have the stop button as in ActivityToolbox so we need to add it.

Please check the Gtk3 version of ReadETexts 1 activity to see how it is done.

Gtk API changes 

There are a lot of API changes in Gtk3. I usually start the porting by replacing all gtk.* with Gtk.* and the fixing the errors one by one.

A simple script pygi-enumerate.py can be as a reference in the porting.

How to use?

1) Download pygi-enumerate.py

2) In the main function add the library you need to recurse.

do_recurse(LIB, "NAME")

like:

do_recurse(Gtk, "Gtk")

Add an import for the LIB in the header and let it rip.

When you run it, you'll see a long list of class methods, constants running through your terminal.

How do I use it to find my desired gtk2->gtk3 replacement?

A: grep is your friend!

 Example: How to find the replacement of gtk.WRAP_WORD_CHAR

[aneesh-sugardev@localhost ~]$ python pygi-enumerate.py | grep constant | grep -i word_char
Gtk.WrapMode.WORD_CHAR (integer constant)

Dissecting the command:

We use pipes to transfer stdout of one command to stdin of other.

1)  python pygi-enumerate.py

This would print all the Class methods and functions to stdout.

2) grep constant

This would filter out all the constants.

3) grep -i word_char

The '-i' tells grep to ignore case

ReadEText I Activity Replacements:

First of all replace all the gtk.* with Gtk.*. Then look for errors and fix them one by one.

Hint: You might need to change some constants. Check the Gtk3 version in the code directory to see how its done.

Gtk.Adjustment: It turns out that Gtk.Adjustment class no longer contains class attributes like lower, upper, value or page size. To get these you need to use get_lower(), get_upper(), get_value() and get_page_size() functions respectively. To set the value you need to use set_value().

New Toolbar

sugar3 no longer has the ActivityToolbox, therefore you'll need to use the new ToolbarBox.

To use it we need to import it:

from sugar3.graphics.toolbarbox import ToolbarBox

To add ActivityToolbox widgets in your ToolbarBox you can use ActivityToolbarButton.

Import ActivityToolbarButton:

from sugar3.graphics.widgets import ActivityToolbarButton

This button can be added to the ToolbarBox:

        toolbar_box = ToolbarBox()
        activity_button = ActivityToolbarButton(self)
        toolbar_box.toolbar.insert(activity_button, 0)
        activity_button.show()

  Similarly you can add other toolbars in your ToolbarBox:

        self.view_toolbar = ViewToolbar()
        self.view_toolbar.connect('go-fullscreen', \
            self.view_toolbar_go_fullscreen_cb)
        self.view_toolbar.zoom_in.connect('clicked', self.zoom_in_cb)
        self.view_toolbar.zoom_out.connect('clicked', self.zoom_out_cb)
        self.view_toolbar.show()
        view_toolbar_button = ToolbarButton(
            page=self.view_toolbar,
            icon_name='toolbar-view')
        toolbar_box.toolbar.insert(view_toolbar_button, -1)
        view_toolbar_button.show()

 What we are doing here is, first we create our desired toolbar and then we create a button which if pressed collapses our toolbar.

 Don't have icons? Well, you can also add buttons with labels.

        view_toolbar_button = ToolbarButton(
            page=self.view_toolbar,
            label=_('View'))

 Check GTK3 versions of ReadETexts Activity 3 and 4 in the code example directory for working examples.

Screenshots


Old Toolbar:

 

New Toolbar:


Boxes

Gtk Boxes have 2 common functions pack_start and pack_end. In Gtk2 these 2 functions could be called using 1-4 arguments (excluding self) that's because these functions were defined with default values. We no longer have default values is Gtk3, thus you are required to call pack_start and pack_end with 4 arguments (excluding self) only.

 pack_start(widget, expand, fill, padding)

widget: The Gtk3 widget you wish to pack inside the box.

expand: Whether the child should get extra space when the container grows.

fill: True if space given to child by the expand option is actually allocated to child, rather than just padding it. This parameter has no effect if expand is set to False. A child is always allocated the full height of a Gtk.HBox and the full width of a Gtk.VBox. This option affects the other dimension.

padding: extra space in pixels to put between child and its neighbor.

Here's how it was defined in gtk2 :-

pack_start(widget, expand=True, fill=True, padding=0)

thus, you could use, in gtk2, pack_start(widget). But in Gtk3 you have to provide all the arguments.

pack_start(widget, True, True, 0)

even keyword arguments work :-

pack_start(widget, expand=True, fill=True, padding=0)

same is with pack_end.

In Gtk3, GtkHBox and GtkVBox have been depreciated, which suggests they might get removed in Gtk4. You can use the following for replacing your GtkVBox and GtkHBox respectively.

vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, homogeneous=True, spacing=8)

Note: When GtkBox is used instead of GtkHBox or GtkVBox. The default value of expand is set to False.

Pango

Pango adds a couple of useful tools in Gtk3. One of them is Pango Text Markup, you can use it to style your texts your way. Yes, no need for those attribute lists, its as simplified as HTML.

Why wouldn't I use attribute lists instead?

The problem with attribute list is that you'd need to apply attributes to some numeric range of characters, for example "characters 12-17." This is broken from an internationalization standpoint; once the text is translated, the word you wanted to italicize could be in a different position.

How to fix this without markup?

Okay, so you don't want to use markup and still want to stick to attribute list. One way to fix the above issue would be to set your page as a GtkToolItem and by inserting GtkLabels with custom attributes. So, if you wanna add "A bold" you'll have to use:-

page = Gtk.ToolItem()
l1 = Gtk.Label()
l1.set_text('A ')
page.insert(l1)

l2 = Gtk.Label()
l2.set_text('bold')
attr = pango.AttrList()
attr.change(pango.AttrWeight(pango.WEIGHT_BOLD, 0, -1))
l2.set_attributes(attr)

page.insert(l2)

 Painstaking? Pango Text Markup to the rescue.

How to fix this with markup?

Markup is the most elegant way to solve the above problem. So, if you wanna add "A bold" and style it using markup, it'll take just 2 lines.

l1 = Gtk.Label()

l1.set_markup("A <b>bold</b>")

Note: While porting from Pango gtk2 to Pango gtk3 you also need to change some constants. The same tool discussed in Gtk Api Changes sections can be used to do the needful.