Using Fremantle widgets
This document should help developers to port the user interface from Diablo to Fremantle. The document mirrors what was discussed on the mailing lists, forum and bug reports. It is based completely on the Fremantle Beta SDK which is the most recent SDK at the moment. In addition, please check the growing Human Interface Guidelines and the official hildon documentation:
Please feel totally free to add/update/change/remix this document. Many aspects are currently not discussed here but will hopefully be added by you!
Note: The casts of all code examples have been removed for better readability.
Contents
|
[edit] Toggle Buttons Vs. Radio Buttons
The use of radio buttons in Fremantle is discouraged because they don't fit the visual style quite well. Instead you should use toggle buttons.
So if you need the behavior of radio buttons (which is that only one can be selected at a time) simply create them as usually and then call gtk_toggle_button_set_mode()
. This will create a button that acts like a radio button, but is displayed as a toggle button.
Example:
GtkWidget *b1 = gtk_radio_button_new_with_label("One"); GtkWidget *b2 = gtk_radio_button_new_with_label_from_widget(b1, "Two"); gtk_toggle_button_set_mode(b1, FALSE); gtk_toggle_button_set_mode(b2, FALSE);
[edit] Widget Sizes
For some widgets there is now an enum which provides three different values: HILDON_SIZE_FINGER_HEIGHT
, HILDON_SIZE_THUMB_HEIGHT
and HILDON_SIZE_AUTO_HEIGHT
. You can use them, for example, like that:
GtkWidget but* = hildon_gtk_button_new(HILDON_SIZE_FINGER_HEIGHT);
You should always use the finger or the thumb size, don't use HILDON_SIZE_AUTO_HEIGHT
as the outcome is not totally clear (see below for screenshots).
If you have a button where you cannot provide a size with the constructor, for example, a stock button, then you can use hildon_gtk_widget_set_theme_size()
to set the code afterwards. Like in this small example:
GtkWidget *but = gtk_button_new_from_stock(GTK_STOCK_COPY); hildon_gtk_widget_set_theme_size(but, HILDON_SIZE_THUMB_HEIGHT);
Now to get a feeling for the different sizes here are some screenshots that show a HildonEntry
together with a GtkButton
using different sizes.
[edit] Window Menu
In Fremantle it is not encouraged to use GtkMenu
as window menu anymore. Instead you should use the HildonAppMenu
. This new menu is really different from the old one so it´s worth taking a closer look. Here are the main features of the HildonAppMenu
.
- It has big / finger friendly menu items.
- It holds a maximum of ten menu items.
- It does not support sub menues.
- The first row can be used for Filters. (Filters are explained in more details below.)
- In landscape mode the menu items are displayed in two rows.
- In portrait mode the menu items are automatically displayed in one row.
- There are no separators, so you cannot group your menu items.
- The menu items are
GtkButton
s and notGtkMenuItem
s.
The HildonAppMenu
is meant to be displayed when the user clicks the title bar of the window, but it is possible to have several other HildonAppMenu
s in your application and activate them for example on button press.
As you can see the HildonAppMenu
differs quite a lot from the old GtkMenu
. The main difference is surely that you cannot have more then 10 menu items inside the new menu. The Hildon HIG gives an overview on how to deal with that.
[edit] Using HildonAppMenu with GtkActions
As said earlier, the menu items are not GtkMenuItem
s anymore but instead they are normal GtkButton
s. So you´re using GtkAction
s in your code, you´ll probably miss a replacement for gtk_action_create_menu_item()
. There is no direct replacement, instead you just have to use standard gtk functions to connect the button as a proxy to the action. Here is an example:
GtkButton *menu_item = gtk_button_new(); gtk_action_connect_proxy(action, menu_item);
This is also possible with GtkToggleAction
and GtkRadioAction
. But remember that your GtkRadioButton
s should look like GtkToggleButton
s as mentioned above. So a complete example for a HildonAppMenu
that should display three GtkRadioAction
s would look like this:
GSList *group = NULL; GtkAction *a1, *a2, *a3; GtkWidget *b1, *b2, *b3; GtkWidget *menu; /* Create the actions */ a1 = gtk_radio_action_new("small", "Small", NULL, NULL, 0); a2 = gtk_radio_action_new("normal", "Normal", NULL, NULL, 1); a3 = gtk_radio_action_new("large", "Large", NULL, NULL, 2); /* Build the radio action group */ gtk_radio_action_set_group(a1, group); group = gtk_radio_action_get_group(a1); gtk_radio_action_set_group(a2, group); group = gtk_radio_action_get_group(a2); gtk_radio_action_set_group(a3, group); group = gtk_radio_action_get_group(a3); /* Create the toggle buttons */ b1 = gtk_toggle_button_new(); b2 = gtk_toggle_button_new(); b3 = gtk_toggle_button_new(); /* Connect the buttons and the actions */ gtk_action_connect_proxy(a1, b1); gtk_action_connect_proxy(a2, b2); gtk_action_connect_proxy(a3, b3); /* Create the menu */ menu = hildon_app_menu_new(); /* Add the buttons to the menu */ hildon_app_menu_append(menu, b1); hildon_app_menu_append(menu, b2); hildon_app_menu_append(menu, b3);
[edit] Finger Friendly GtkTreeView
To get a finger friendly GtkTreeView
it´s not enough to call hildon_gtk_tree_view_new()
instead of gtk_tree_view_new()
but you also have to put the tree view inside a HildonPannableArea
to get finger friendly sizes. If you have your tree view inside a GtkScrolledWindow
it will be displayed with stylus-style (like in Diablo).
Also if you´re displaying icons in your tree view you probably should use the 40×40 pixel version for Fremantle if you used the 26×26 pixel version in Diablo.
[edit] Normal Vs. Edit Mode
There are now two different modes for the tree view. Those modes affect how the rows in a tree view react to finger taps.
You should use HILDON_UI_MODE_NORMAL
if the user should only be able to activate one row of the text view. If the user taps a row in the tree view the row-activated
is emitted. In standard Gtk+ this happens only if the user double clicks the row - a single click selects the row which is not possible with HILDON_UI_MODE_NORMAL
.
If the user should be able to select or edit rows then you should use HILDON_UI_MODE_EDIT
.
[edit] Using Filters For Sorting
Most of you probably are using the tree view with a header row that displays column labels. Often this header row can also be used to sort the columns ascending or descending. If you're designing a finger friendly UI you will usually have a very limited number of visible columns - probably only one or two. In this case the column content is often self explanatory and the header row is only there for sorting. If that's the case for your UI, you might want to remove the header column completely and replace it with so called filters on the HildonAppMenu.
According to the HIG filters are meant to change how data is presented, but it should not affect the amount of data that is displayed. Sorting a tree view would be a possible use case. To do this, just have a look at the example:
/* Remove the header row */ gtk_tree_view_set_headers_visible(view, FALSE); /* Create two radio buttons for sorting */ GtkWidget *b1, *b2; b1 = gtk_radio_button_new_with_label(NULL, "Sort By Title"); b2 = gtk_radio_button_new_with_label_from_widget(b1, "Sort By Date"); /* Draw them as toggle buttons not as radio buttons */ gtk_toggle_button_set_mode(b1, FALSE); gtk_toggle_button_set_mode(b2, FALSE); /* Add the buttons as filters to the menu */ hildon_app_menu_add_filter(menu, b1); hildon_app_menu_add_filter(menu, b2);
That is all for showing them on the window menu. Now only a callback is needed which gets activated whenever the user selects a filter. An example implementation might look like that:
void on_sort_filter_clicked(GtkToggleButton *button, GtkTreeSortable *sortable) { if (gtk_toggle_button_get_active(button)) { gtk_tree_sortable_set_sort_column_id(sortable, TITLE_COLUMN, GTK_SORT_ASCENDING); } }
You can add as many filters as you like, but they will be displayed all in one row and there is no possibility to add another filter row. This means you have to select carefully what to put there and also have a look at the length of the strings. If you, for example have two rows, one with a title and one with a date you could offer the user:
- Sort ascending by date
- Sort descending by date
- Sort ascending by title
- Sort descending by title
Those four strings will not fit into one row. Therefore you have to make a selection and include only the cases which will (hopefully) be most relevant to the user. In this case you should offer:
- Sort by title (which sorts by title ascending - A before B)
- Sort by date (which sorts by date descending - newest on top)
In the most recent version (not yet in Beta 2) the filter group has a fixed size which does not depend on the number of filters or the orientation of the device. So if your menu looks good in landscape mode it will look good in portrait mode.
[edit] Context Menus
To display finger friendly context menues there is a hildon helper function called hildon_gtk_menu_new()
. Just call this instead of gtk_menu_new()
and you´re done.
Example:
GtkWidget *menu = hildon_gtk_menu_new(); GtkWidget *item1 = gtk_menu_item_new_with_label("Bla"); GtkWidget *item2 = gtk_menu_item_new_with_label("Blablabla"); gtk_menu_shell_append(GTK_MENU_SHELL(menu), item1); gtk_menu_shell_append(GTK_MENU_SHELL(menu), item2); gtk_widget_show_all(menu); gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
Note that in the Beta SDK there is a bug. If you´re using GtkImageMenuItem
s instead of GtkMenuItem
s the menu will have stylus-style. This bug is already fixed and a work around is available here: https://bugs.maemo.org/show_bug.cgi?id=4654 .
[edit] Panning
Almost all of you will know the problems with scrollbars in Diablo. You can have small scrollbars which are not finger friendly or you can have large scrollbars which are using a lot of screen space and look ugly.
In Fremantle both kinds of scrollbars are still available, but there is also a third option. HildonPannableArea
features kinetic panning using fingers. There is no scrollbar anymore, only a very small scroll indicator that indicates which part of the document you´re seeing right now.
HildonPannableArea
can be used as a replacement for GtkScrolledWindow
but there are things you have to keep in mind when doing so.
[edit] GtkTextView Inside HildonPannableArea
Panning does not work with a GtkTextView
. Instead you can use a HildonTextView
which is a drop in replacement. The advantage is, that then panning is possible. The disadvantage is that selecting text is not possible anymore. The discussion about this is here: https://bugs.maemo.org/show_bug.cgi?id=4619
[edit] GtkHtml Inside HildonPannableArea
When using GtkHtml
inside a HildonPannableArea
the panning is not smooth. The problem seems to be that GtkHtml
provides some build in support for panning and that it conflicts with the panning offered by Hildon. See this bug report here: https://bugs.maemo.org/show_bug.cgi?id=4631
[edit] Portrait Mode
If your application should work in portrait mode you probably have to change the UI quite a bit. There are a couple of problems you'll might have:
- The toolbar is not wide enough to hold all items anymore.
- The columns of your tree view get too small.
- You cannot enter text because there´s no onscreen keyboard. (Actually the FKB works perfectly, but it will switch to landscape and then back to portrait when closed.)
- Dialogs are not shown correctly (see https://bugs.maemo.org/show_bug.cgi?id=4618 ).
- Probably more...
This means you have to listen to changes in the orientation and hide/show/change widgets to make the UI functional in portrait mode.
[edit] Listening To Screen Orientation Changes
Basically here you´re not listening to changes in the devices orientation, but you check the height and width of the screen. So if width is greater than height you are in landscape mode and if height is greater than width you´re in portrait mode. This is supposed to work no matter why height and width changed. So it does not depend on hardware support like acceleratormeters it could also just be a software setting that rotates the screen.
gboolean is_portrait() { GdkScreen *screen = gdk_screen_get_default(); int width = gdk_screen_get_width(screen); int height = gdk_screen_get_height(screen); if (width > height) { return FALSE; } else { return TRUE; } } void on_orientation_changed(GdkScreen *screen, GtkWidget *widget) { if (is_portrait()) { gtk_widget_hide(widget); /* Hide/Show other things */ } else { gtk_widget_show(widget); /* Hide/Show other things */ } }
Then inside your UI code you should listen to the size-changed
signal of GdkScreen
. For example:
g_signal_connect(screen, "size-changed", on_orientation_changed, widget);
[edit] Testing Screen Orientation Changes In Scratchbox
For testing your code you can execute the following commands outside scratchbox:
xrandr -display :2 -o left xrandr -display :2 -o normal
[edit] Automatic Screen Rotation
Your application is not automatically rotated when the user rotates its device. Even if the hardware supports it. There are more things to do to make that happen. Basically you have to do two things:
- During startup of your application, or better shortly before showing the first window, you should check the orientation of the device and adjust your application.
- You have to listen to all changes in the devices orientation while your application is running and adapt accordingly.
Whether or not your application is rotated depends on a couple of things:
- There are two X window properties which you can set on your windows.
- Those tell the system what to do. The type of the value of the properties is
XA_CARDINAL
where 0 means "off", and 1 stands for "on". Other values may get other meaning in the future.-
HILDON_PORTRAIT_MODE_SUPPORT
tells the system that your application can be rotated, so it won't prevent any other application from rotating. -
HILDON_PORTRAIT_MODE_REQUEST
tells the system that your application wants to be rotated.
-
- Rotation only happens when all visible windows are ok with being rotated.
That means that to get a rotated desktop all visible windows must have the HILDON_PORTRAIT_MODE_SUPPORT
flag set to 1 and at least one of them must have the HILDON_PORTRAIT_MODE_REQUEST
flag set to true. The flags are inherited through the transiency chain, so you needn't set the SUPPORT flag on your transient dialogs for example, nor on all stackable windows.
It's important that you set the flags correctly on all windows that you open. If you or another application opens a window with the HILDON_PORTRAIT_MODE_SUPPORT
flag not set, the whole UI is rotated to landscape mode.
So here are the steps you have to do to make correct use of the portrait mode:
- On all windows that support portrait mode, set the
HILDON_PORTRAIT_MODE_SUPPORT
flag. - During startup check the hardware orientation. If the hardware orientation is portrait mode, then set the
HILDON_PORTRAIT_MODE_REQUEST
flag on at least one visible window. - While running your application, listen to changes in the orientation of the device. If it is rotated to landscape mode set the
HILDON_PORTRAIT_MODE_SUPPORT
flag. If it's rotated to portrait mode set theHILDON_PORTRAIT_MODE_REQUEST
flag.
It is not necessary to set 'request' together with 'support' as 'request' implies 'support', provided that 'support' is unset (as opposed to set to 0).
[edit] Finding Out The Current Hardware Orientation
In order to find out whether or not the device currently is in portrait you need to ask the MCE (Mode Control Entity) via D-Bus. The parameters of the call are the following and can be found as constants in 'mce/dbus-names.h
'.
- Service
-
com.nokia.mce
- Path
-
/com/nokia/mce/request
- Interface
-
com.nokia.mce.request
- Member
-
get_device_orientation
This method returns several values:
-
gchar *
portrait/landscape orientation (seemce/mode-names.h
for valid portrait/landscape states) -
gchar *
on/off stand (see mce/mode-names.h for valid stand states) -
gchar *
face up/face down (see mce/mode-names.h for valid facing states) -
dbus_int32_t
x axis (unit mG) -
dbus_int32_t
y axis (unit mG) -
dbus_int32_t
z axis (unit mG)
In our case we are only interested in the first return value which will be 'portrait' or 'landscape'. Because we only need to know the first value here we can use the osso_rpc_run_system()
function for making this call to D-Bus. It's a convenience function provided by libosso for making D-Bus calls. The advantage is that our code gets shorter, the disadvantage is that it only return the first return value - which in our case is ok. So here is the code using libosso.
#include <mce/dbus-names.h> #include <mce-dev/mode-names.h gboolean device_is_portrait_mode(osso_context_t* ctx) { osso_rpc_t ret; gboolean result = FALSE; if (osso_rpc_run_system(ctx, MCE_SERVICE, MCE_REQUEST_PATH, MCE_REQUEST_IF, MCE_DEVICE_ORIENTATION_GET, &ret, DBUS_TYPE_INVALID) == OSSO_OK) { g_printerr("INFO: DBus said orientation is: %s\n", ret.value.s); if (strcmp(ret.value.s, MCE_ORIENTATION_PORTRAIT) == 0) { result = TRUE; } osso_rpc_free_val(&ret); } else { g_printerr("ERROR: Call do DBus failed\n"); } return result; }
[edit] Listening To Hardware Orientation Changes
So now you know how to ask the device for its orientation. Now let's see how to connect a signal handler to listen to changes in the orientation.
First here is again the signature:
- Service
-
com.nokia.mce
- Path
- /com/nokia/mce/signal</code>
- Interface
-
com.nokia.mce.signal
- Member
-
sig_device_orientation_ind
The signal transports three values:
-
gchar *
portrait/landscape orientation (see mce/mode-names.h for valid portrait/landscape states) -
gchar *
on/off stand (see mce/mode-names.h for valid stand states) -
gchar *
face up/face down (see mce/mode-names.h for valid facing states)
Again we are only interested in the first value which is 'portrait' or 'landscape'. Unfortunately there is no libosso convenience function for listening to D-Bus signals, so we have to use the raw D-Bus API.
static DBusHandlerResult dbus_handle_mce_message(DBusConnection *con, DBusMessage *msg, gpointer data) { DBusMessageIter iter; const gchar *mode = NULL; if (dbus_message_is_signal(msg, MCE_SIGNAL_IF, MCE_DEVICE_ORIENTATION_SIG)) { if (dbus_message_iter_init(msg, &iter)) { dbus_message_iter_get_basic(&iter, &mode); g_printerr("INFO: New orientation is now: %s\n", mode); } } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; }
Now we need to register this signal handler.
#define MCE_MATCH_RULE "type='signal',interface='" MCE_SIGNAL_IF "',member='" MCE_DEVICE_ORIENTATION_SIG "'" /* Get the system DBus connection */ DBusConnection *con = osso_get_sys_dbus_connection(osso_context); /* Add the callback, which should be called, once the device is rotated */ dbus_bus_add_match(con, MCE_MATCH_RULE, NULL); dbus_connection_add_filter(con, dbus_handle_mce_message, NULL, NULL);
[edit] Listening To Hardware Orientation Changes (Python example)
This short snippet should get you started (you only need the gobject.MainLoop
if you are writing a console application). "handler" will be called every time the orientation changes, and the first parameter is the important one (compare it to "landscape" and "portrait").
import dbus import dbus.glib # Again, this is only needed for console apps import gobject mainloop = gobject.MainLoop() # Define some constants MCE_SIGNAL_NAME = 'sig_device_orientation_ind' MCE_SIGNAL_IFACE = 'com.nokia.mce.signal' MCE_SIGNAL_PATH = '/com/nokia/mce/signal' # Get the system bus system_bus = dbus.Bus.get_system() # Define a callback that gets called for orientation changes def handler(orientation, stand, face, x, y, z): print orientation # Connect the "handler" to the orientation changed signal of mce system_bus.add_signal_receiver(handler, signal_name=MCE_SIGNAL_NAME, \ dbus_interface=MCE_SIGNAL_IFACE, path=MCE_SIGNAL_PATH) # Start the main loop - only needed for console apps mainloop.run()
Do you want it even more comfortable? See this forum post for a link to an easy-to-use drop-in module that will take care of auto-rotation all your application windows.
[edit] Forcing screen rotation
If you need a way to force some rotation of your N900 screen, this is for you.
/* gcc rotate.c -lX11 -lXrandr */ #include <X11/Xlib.h> #include <X11/extensions/Xrandr.h> static void set_rotation (Rotation r_to) { Rotation r; int screen = -1; XRRScreenConfiguration* config; int current_size=0; Display* display; Window rootWindow; display = XOpenDisplay(":0"); if (display == NULL) { return; } screen = DefaultScreen(display); rootWindow = RootWindow(display, screen); XRRRotations(display, screen, &r); config = XRRGetScreenInfo(display, rootWindow); current_size = XRRConfigCurrentConfiguration (config, &r); XRRSetScreenConfig(display, config, rootWindow, current_size, r_to, CurrentTime); } int main (int argc, char *argv[]) { set_rotation(RR_Rotate_90); //set_rotation(RR_Rotate_270); //set_rotation(RR_Rotate_180); //set_rotation(RR_Rotate_0); }
[edit] Testing Hardware Orientation In Scratchbox
Testing whether or not your D-Bus signal handler for device orientation change works is easy. Just run the following commands inside scratchbox while your application is running.
Device was rotated into portrait mode:
run-standalone.sh dbus-send --system --type=signal /com/nokia/mce/signal com.nokia.mce.signal.sig_device_orientation_ind string:'portrait'
Device was rotated into landscape mode:
run-standalone.sh dbus-send --system --type=signal /com/nokia/mce/signal com.nokia.mce.signal.sig_device_orientation_ind string:'landscape'
For testing the call to the MCE you first have to install the MCE because it's not part of the default SDK installation. Simply do:
fakeroot apt-get install mce
Now start it using the debug mode. It won't start without the '--debug-mode' flag. You'll get lots of debug output on the terminal, just ignore it:
mce --debug-mode
Now the D-Bus call to the MCE should return 'landscape'. It probably will always return 'landscape' when run inside the SDK as it is not possible to really rotated the "device" there.
[edit] References
In no particular order
- http://maemo.org/api_refs/5.0/beta/tutorial/html
- http://maemo.org/api_refs/5.0/beta/hig/html
- http://lists.maemo.org/pipermail//maemo-developers/2009-May/019368.html
- http://lists.maemo.org/pipermail//maemo-developers/2009-May/019144.html
- http://lists.maemo.org/pipermail//maemo-developers/2009-June/019426.html
- http://lists.maemo.org/pipermail//maemo-developers/2009-June/019579.html
- http://lists.maemo.org/pipermail//maemo-developers/2009-August/020202.html
- https://bugs.maemo.org/show_bug.cgi?id=4617
- https://bugs.maemo.org/show_bug.cgi?id=4618
- https://bugs.maemo.org/show_bug.cgi?id=4619
- https://bugs.maemo.org/show_bug.cgi?id=4654
- https://bugs.maemo.org/show_bug.cgi?id=4637
- https://bugs.maemo.org/show_bug.cgi?id=4648
- This page was last modified on 4 May 2011, at 08:51.
- This page has been accessed 61,842 times.