Make your own sugar activities

Converting Hippo Canvas To GTK3

by Aneesh Dogra 

When the Sugar environment was first created it made a lot of use of Hippo Canvas and so did some Activities. Lately Sugar Labs has reconsidered that and Sugar 3 will use no Hippo Canvas at all

Sugar used Hippo because it had some significant improvements over Gtk Widgets, In the past time it was the only way to do some things, like layout modernization (see [1]), but now most of its features can be done in gtk using Cairo, Pango and Gtk3.

Sugar 3 has been completely ported to Gtk3 and doesn't use Hippo canvases. To make your application Gtk3 based you will have to remove Hippo dependencies and use Gtk3 widgets to do the job. Gtk3 makes that a little easy for us, as we are provided with CSS, Pango Markup and Boxes which can replace Hippo to create an identical UI.

Porting

CanvasBox Object

A hippo.CanvaxBox() could include the following arguments. 

orientation: Whether it should be a Vertical box or a Horizontal one.

background_color: The box's background color.

box_width: The box's width

box_height: The box's height

padding: The padding between the Box and its surrounding widgets. Analogous to padding in Gtk.Box.pack_start()  and Gtk.Box.pack_end()

spacing: The spacing between the Box and its surrounding widgets.

A CanvasBox can be easily ported to Gtk3 based code using a Gtk.Box

Example:

        conversation = hippo.CanvasBox(
                       spacing=0,
                       background_color=COLOR_WHITE.get_int())
        self.conversation = conversation

The above CanvasBox call can be replaced by :-

        self.conversation = Gtk.VBox()
        self.conversation.override_background_color(Gtk.StateType.NORMAL, \
                           Gdk.RGBA(*COLOR_WHITE.get_rgba()))

CanvasText Object

A hippo.CanvasText could include the following options

text: The text you wish to display
size_mode: Wrap mode.
color: Text color.

font_desc: A Pango.FontDescription object, used to style the text.

xalign: Start or End. Start for Left to Right languages and End for Right to Left languages.

 

A CanvasText object can be converted to a Gtk3 widget using textview.

Example:

            message = hippo.CanvasText(
                text=text,
                size_mode=hippo.CANVAS_SIZE_WRAP_WORD,
                color=text_color,
                font_desc=FONT_NORMAL.get_pango_desc(),
                xalign=hippo.ALIGNMENT_START)

  The above CanvasText call can be replaced by :-

            msg = Gtk.TextView()
            text_buffer = msg.get_buffer()
            text_buffer.set_text(text)
            msg.set_editable(False)
            msg.set_justification(Gtk.Justification.LEFT)
            msg.set_font(FONT_NORMAL.get_pango_desc())
            msg.override_color(Gtk.StateType.Normal,  text_color)
            # You need to convert text_color to a Gdk.RGBA object.
            msg.set_wrap_mode(Gtk.WrapMode.WORD_CHAR)  

 CanvasScrollbars Object 

Its almost similar to the way Gtk.ScrolledWindow works.

Example:

    sw = hippo.CanvasScrollbars()
    sw.set_policy(hippo.ORIENTATION_HORIZONTAL, hippo.SCROLLBAR_NEVER)
    sw.set_root(conversation)

The above hippo implementation can be replaced by:

    sw = Gtk.ScrolledWindow()
    sw.set_policy(Gtk.PolicyType.NEVER,
                         Gtk.PolicyType.AUTOMATIC)
    sw.add(conversation)

CSS Styling

At some points you might want to use CSS to style your widgets, because Its more manageable secondly CSS is more documented than Gtk.

Here's how you can do it:

Add the following in your application's init (you need to import Gtk and Gdk) :-

        screen = Gdk.Screen.get_default()
        css_provider = Gtk.CssProvider()
        css_provider.load_from_path(FILE)
        context = Gtk.StyleContext()
        context.add_provider_for_screen(screen,
                                        css_provider,
                                        Gtk.STYLE_PROVIDER_PRIORITY_USER)

 

You need to change FILE to your desired CSS file's path.

A basic CSS file

GtkLabel {
    background-color: red;
}
Note: In the style sheet we define styles using their gtype names.

It means you can also set gtype name to a specific widget and style it specifically.

Porting MiniChat from Hippo to Gtk3

MiniChat can be found in the book code examples repository, by the name /MiniChat (Hippo Version) and /MiniChat_gtk (GTK3 version)

Screen Shots

Hippo Version

GTK3 Version:

As you can see there isn't much difference in the 2 layouts, except that the GTK3 version's chatbox has sharp corners instead of rounded ones.

Porting

Understanding the layout

The Hippo version uses the following layout.

         .------------- rb ---------------.
        | +name_vbox+ +----msg_vbox----+ |
        | |         | |                | |
        | | nick:   | | +--msg_hbox--+ | |
        | |         | | | text       | | |
        | +---------+ | +------------+ | |
        |             |                | |
        |             | +--msg_hbox--+ | |
        |             | | text       | | |
        |             | +------------+ | |
        |             +----------------+ |
        `--------------------------------'

We can replicate it easily in Gtk3. Using a Horizontal box for rb, A TextView for name_vbox and another TextView for the messages.

Thus, our Gtk3 layout would be :-

        .------------- rb ---------------.
        | +name_vbox+ +----msg_vbox----+ |
        | |         | |                | |
        | | nick:   | | +------------+ | |
        | |         | | |    Text    | | |
        | +---------+ | +------------+ | |
        |             +----------------+ |
        `--------------------------------'

 Its similar to the Hippo version just a lot simpler. Note that we don't need msg_hbox in the Gtk3 version because TextView are enough for the functionality we are looking for.

What do we need to port?

 Let's checkout the function calls we need to port/replace. Grepping for "hippo." in the MiniChat/minichat.py would give you a list.

  1. hippo.CanvasBox (used with name_vbox, msg_vbox, msg_hbox and for the conversation box)
  2. hippo.CanvasScrollbars (used for adding Scrollbar to the conversation box)
  3. hippo.Canvas (the container of conversation box) [We'll not require it in out Gtk3 version.]
  4. hippo.CanvasText (used for displaying nick and messages)
  5. CanvasRoundBox (used with rb)

What can they be replaced with?

Now, we need to think about possible Gtk3 widgets with which we can replace these hippo elements.

  1. hippo.CanvasBox can be replaced with Gtk.Box
  2. hippo.CanvasScrollbars can be replaced with Gtk.ScrolledWindow
  3. hippo.CanvasText can be replaced with Gtk.TextView
  4. hippo.Canvas is not required.
  5. CanvasRoundBox [part of sugar, not available in sugar3] can be replaced with a Gtk.Box.

Note: Gtk.Box doesn't support rounded corners, there is a way to add rounded corners to Gtk.Box but that requires Cairo which is out of the scope of this chapter. You can check how it is implemented in the Chat activity.

Replacing

The Conversation Box

The conversation box is a container of rb's [rb is a box in our layout, as discussed above]

The conversation box is defined in the make_root function as :

        conversation = hippo.CanvasBox(
            spacing=0,
            background_color=COLOR_WHITE.get_int())
        self.conversation = conversation

 Now, it needs to be a VBox because you expect your chat messages to be displayed linearly from top->bottom not from left->right. This can be replicated in GTK3 as:

self.conversation = Gtk.VBox() self.conversation.override_background_color(Gtk.StateType.NORMAL, \
                               Gdk.RGBA(*COLOR_WHITE.get_rgba()))

The Scrollbar

The scrollbar is defined in make_root function as :

        sw = hippo.CanvasScrollbars()
        sw.set_policy(hippo.ORIENTATION_HORIZONTAL, hippo.SCROLLBAR_NEVER)
        sw.set_root(conversation)
        self.scrolled_window = sw

This can be replicated in Gtk3 using Gtk.ScrolledWindow :

        self.scroller = Gtk.ScrolledWindow()
        self.scroller.set_vexpand(True)
        self.scroller.add_with_viewport(self.conversation)
        self.scroller.override_background_color(Gtk.StateType.NORMAL,
                                    Gdk.RGBA(*COLOR_WHITE.get_rgba()))

The Round Box (RB)

The rb contains our message boxes and our name boxes.

Here's how it is defined in the Hippo Version:

            rb = CanvasRoundBox(background_color=color_fill,
                                border_color=color_stroke,
                                padding=4)

GTK boxes doesn't have any functionality to add border_colour but we can add borders to boxes by adding them in an EventBox. Here's how :

            eb = Gtk.EventBox()
            eb.override_background_color(Gtk.StateType.NORMAL, color_stroke)

            rb = Gtk.HBox()
            rb.override_background_color(Gtk.StateType.NORMAL, color_fill)
            rb.set_border_width(1)
            eb.add(rb)

The Boxes

msg_vbox:
 msg_vbox = hippo.CanvasBox(
                orientation=hippo.ORIENTATION_VERTICAL)

  In Gtk3:

            msg_vbox = Gtk.VBox()

 name_vbox:

                name_vbox = hippo.CanvasBox(
                    orientation=hippo.ORIENTATION_VERTICAL)

in Gtk3:

                 name_vbox = Gtk.VBox()

msg_hbox

msg_hbox is required in the Hippo version because hippo.CanvasText doesn't have a box so we need a container where we can append the text, but Gtk.TextView provides us with a box as well as a TextBuffer. Thus, we don't require msg_hbox in Gtk3.

Adding Messages

In the hippo version, every time a message is posted, the message is appended in the msg_hbox which is eventually appended to the msg_vbox.

             message = hippo.CanvasText(
                text=text,
                size_mode=hippo.CANVAS_SIZE_WRAP_WORD,
                color=text_color,
                font_desc=FONT_NORMAL.get_pango_desc(),
                xalign=hippo.ALIGNMENT_START)
             msg_hbox.append(message)

 In Gtk3 we can make this simpler, Firstly we don't need the msg_hbox (as discussed in the preceding sections), secondly instead of creating new message boxes everytime a new message is posted we can append the text to the textview.
Here's how:

            if not new_msg:
                msg = msg_vbox.get_children()[0]
                text_buffer = msg.get_buffer()
                text_buffer.set_text(text_buffer.get_text(
                    text_buffer.get_start_iter(),
                    text_buffer.get_end_iter(), True) + "\n" + text)
            else:
                msg = Gtk.TextView()
                text_buffer = msg.get_buffer()
                text_buffer.set_text(text)
                msg.show()
                msg.set_editable(False)
                msg.set_border_width(5)
                msg.set_justification(Gtk.Justification.LEFT)
                msg.override_font(FONT_NORMAL.get_pango_desc())
                msg.override_color(Gtk.StateType.NORMAL, text_color)
                msg.override_background_color(Gtk.StateType.NORMAL, \
                                              color_fill)
                msg.set_wrap_mode(Gtk.WrapMode.WORD_CHAR)
                msg_vbox.pack_start(msg, True, True, 0)

 Colors

 Hippo elements generally input integer form of colors but in Gtk you need the Gtk.RGBA type object. 

example:

                 text_color = COLOR_WHITE.get_int()

 should be replaced by:

                text_color = Gdk.RGBA(*COLOR_WHITE.get_rgba())

These were the major changes required to port the MiniChat activity from Hippo to Gtk3, you can checkout the fully ported activity in /MiniChat_gtk3

[1]: https://bugzilla.gnome.org/show_bug.cgi?id=310809