Report on a codebase cleanup effort for the GTK+ frontend of cdebconf

Author: lunar@debian.org

This report presents fe_gtk, an effort to cleanup and improve readability of the code of the GTK+ frontend of cdebconf.

Contents

1   Introduction

This document presents the result of more than two weeks of nearly full time work. I am quite happy with my achievements and I really think they are worthwhile for the debian-installer project.

I have put a lot of energy and heart into all this. I would be happy to explain in details all choices that I made during this work, but this report is already too long, so please don't hesitate to ask. :)

This work can be seen as a first attempt to improve the general code quality of cdebconf. Other parts of the code and other frontends might benefit from a similar process in the future.

1.1   Workflow

I am fully aware that this work was not really done in the best way for free software, but after Attilio's reaction to my first commits, it was just easier to work alone and present the final result.

We could use the following process to integrate it:

  • discuss, comment and test;
  • make the needed changes to fe_gtk's branch;
  • once we agree that it can be integrated in the main branch, write small patches (or incremental commits) to slowly transform the current code into the result of the process.

Even if I have been able to go through multiple installations and cdebconf's test suite several times. I am also pretty sure that Attilio or Frans will be able to spot regressions in my code. I have not done a detailed side by side comparison of both codebases to check for not-that-obvious changes.

1.2   Testing

The code is available in svn://svn.debian.org/svn/d-i/people/lunar/fe_gtk. Also have a look at the generated documentation.

You can test non d-i frontend by using the following commands:

$ svn co svn://svn.debian.org/svn/d-i/trunk/packages/cdebconf .
$ cd cdebconf
$ svn co svn://svn.debian.org/svn/d-i/people/lunar/fe_gtk \
         src/modules/frontend/fe_gtk
$ ./configure --with-frontend=fe_gtk --with-conffile=./cdebconf.conf
$ cd src/test
$ # Edit cdebconf.conf according with the example found on
$ # doc/testing.txt.  Configure "default_fe" to use "fe_gtk".
$ for t in *.templates; do ../debconf-loadtemplate debian $t; done
$ for t in *.config; do ../debconf $t; done

To create a d-i image using the new frontend, you will need to make changes in the following packages:

  • rootskel

    Replace "gtk" by "fe_gtk" in:

    • src/lib/debian-installer.d/S60frontend
    • src/lib/debian-installer.d/S70menu-linux
  • rootskel-gtk

    Replace "gtk" by "fe_gtk" in:

    • src/lib/debian-installer.d/S61mouse-support-x86
    • src/lib/debian-installer.d/S61mouse-support-powerpc
    • src/usr/bin/gtk-set-font
    • src/usr/bin/gtk-set-theme

    The logo also needs to be slightly modified to remove the border around the red area.

  • cdebconf

    • Adding the fe_gtk directory to src/modules/frontend.
    • Replace "gtk" by "fe_gtk" in DEB_FRONTENDS and UDEB_FRONTENDS at the beginning of debian/rules.
    • Change gtk* to fe_gtk* in debian/cdebconf-gtk-udeb.install.

You can also use the mini.iso I have built for i386 and amd64.

1.3   Some words about the coding style

It is not compliant with the style described in doc/coding.txt. But that is the style that I am used to, and it was just all easier to work with it in the first place.

The inspiration for my coding style is still available online, if you want to take a look.

I am open to normalize the code before integrating it. But I would be inclined to get a broader opinion about at least:

} else {

versus:

}
else
{

I find the former much more readable!

Functions were all renamed to start with a verb and variables are no longer abbreviations. Lines have no more than 79 characters.

2   Architectural changes

A few architectural changes in the GTK+ frontend were made during the refactoring. These changes were made to create a more clean, robust and readable code.

2.1   Frontend has been renamed to fe_gtk

This is a solution for namespace pollution: plugins are expected to implement a function with the following name: <frontend>_handler_<type>. Naming the plugin gtk means that a plugin will look like, for example, gtk_handler_entropy which pollutes the gtk namespace that should be left for the GTK+ library.

You might argue that I'm nitpicking here... It was also more convenient during the development to work in a separate directory.

2.2   fe_gtk namespace

Exported functions are all prefixed with fe_gtk. The only symbols that do not respect this convention is the needed debconf_frontend_module. :)

Having a clear namespace helps to quickly spot where to look at various functions while digging in the code or reading stack traces.

2.3   Code has been split into different files

In order to better separate areas of concern and to make it easier to add new features in the future, the code has been spread out to multiple files. Each C file, except fe_gtk.c, has a corresponding header that defines an internal interface to the module.

Please refer to the generated file list and the identifier map below to have a better view on how the split was done.

The longest file has a total of 638 lines (including documentation and header) vs. 1790 lines for the current code.

2.4   Public API for plugins is in fe_gtk.h

A clear API was created for frontend plugins. This API is defined and documented in fe_gtk.h. This API was tested to be fine by writing a new plugin (see below).

Please not that there is no mention of struct frontend_data in the public API: a plugin should not be able to fiddle with the frontend data structures.

A linker-script is also available to reduce the exported symbols to those defined in this public API. The Makefile contains a commented line enabling it, which unfortunately breaks when building the whole package. I did not investigate why exactly.

2.5   Separation of code specific to the debian-installer

Most of the development done here was actually done by building the frontend against the standard GTK+ X11 library. In order to do so, all code specific to the debian-installer is now isolated in one specific module.

This module is only linked in when --enable-d-i has been given to the ./configure script. This option also adds a field to the private handling in struct frontend_data and calls to fe_gtk_di_setup() during frontend initialization, fe_gtk_di_shutdown() during frontend shutdown and fe_gtk_di_run_dialog() on start of GO and PROGRESS.

(Note that I don't like the name of this last function, if someone could make up a better name, that would be great.)

The screenshot module is compiled conditionally as I see no reason to enable this feature on standard desktops.

2.6   Plugin handling

The current code tries to load a plugin to handle the current questions eight times for each question (see gtk_go()).

This was replaced by a simple function, find_external_handler() which cache plugins in an hash table after they have been loaded. Plugins are unloaded when the corresponding entry is deleted from the cache.

2.7   select and multiselect handling

All select and multiselect handlers are now build as views of a common GtkTreeModel. This really fits the GTK+ API better, as GtkTreeView and GtkComboBox get their data from a GtkTreeModel.

The only exception is the handler of multiselect when there is multiple questions asked in a GO and which uses checkboxes, but even in this case, a GtkTreeModel is a convenient way to store the relevant data.

The views are implemented in select_handlers.c.

The model is created and manipulated through functions defined in choice_model.h, fe_gtk_choice_model_create_full() being the full interface to create a new model from a given question. The model stores the index, the value in canonical and translated form, and a flag for the selection status. This enables to relevant setters to fully abstract the view, which results in prettier code.

The code is quite generic and clean in this regard: the previous code duplication related to tree-like questions (countrychooser/country-name and partman/choose_partition) is now gone. Tree-like questions are now implemented through a predicate function (see parent_predicate, is_disk(), is_country()) and registered in special_questions.

This has a drawback though: GtkTreeView adds a small margin by default if the model used is a GtkTreeStore. This can quite easily be worked around by adding a little bit more code, but it was not worth it in my opinion: I would be in favor of simplifying that code, returning to a more simple GtkListStore and moving away specific d-i bits to new plugin (a world map for countrychooser, anyone? ;) ). See the present code as an experiment on how far it was possible to avoid code duplication.

2.8   Dynamic content for target box and action box

The target box is the container where question widgets (for GO) and progress widgets (for PROGRESS) are laid out. In current code, a container for questions widgets and a container for progress where both created during the frontend initialization. They are hidden or shown when needed.

The action box is container for buttons found in the bottom of the frontend. In the same spirit, buttons where created during plugin initialization and hidden or disabled at appropriate time.

I have tried a different direction, and this is a pretty disputable change: the target box and action box is filled and emptied when appropriate.

The initial idea was to totally separate GO and PROGRESS handling. Ultimately, this failed on my first run of pkgsel as GO is called (package installation) while the PROGRESS about remaining packages is running. But the resulting bug might prompt us to a new direction which is detailed below.

Another point in making such change was to get rid of tricky functions actually refreshing various labels on language change. A lot of references and tricky code seems to be avoidable by using this quite simple design.

This change allow plugins to have more control about buttons displayed in the action box though. This might help us to improve the usuability, but this is also worth more discussions.

2.9   Handling of keyboard shortcuts

The previous key_press_event() has been replaced by a more scalable mechanism. A function, fe_gtk_add_global_key_handler() registers a new global (associated to the main window) key handler. This handler is tied to a specific widgets, and will be desactivated if the widget is destroyed.

This change is also quite tied to the previous one. A plugin can define new keyboard shortcuts associated with its buttons or its entry widget. These shortcuts will then be removed automatically at the end of the GO run.

3   Smaller changes

3.1   call_setters() no longer free setters

A separate function, free_setters() has been introduced to do the job. This allow us to recover memory if the setters are not called (e.g. "Go Back" has been pressed).

3.2   Compilation flags

The frontend is built with the following flags:

-funsigned-char -fstrict-aliasing -Wall -W -Werror -Wundef \
-Wcast-align -Wwrite-strings -Wsign-compare -Wno-unused-parameter \
-Winit-self -Wpointer-arith -Wredundant-decls \
-Wno-format-zero-length -Wmissing-prototypes \
-Wmissing-format-attribute

The meaning of each of these flags is explained in the Makefile.

The compiler can spot a wider range of issues with this flags, and it has been pretty helpful already.

I would strongly advocate to progressively enable these flags for all cdebconf code.

3.3   Changes in struct frontend_data

The data structure frontend_data is the holder for internal data of the GTK+ frontend. It is defined in the current code in cdebconf_gtk.h whereas it now lies in its own specific internal header, fe_data.h.

The following changes were made:

  • button_next, button_prev, button_screenshot, button_cancel were all removed as there is no need to keep a reference to them.
  • action_box was added to keep a reference to the button container.
  • progress_bar, progress_bar_label, progress_bar_box were moved to the opaque progress_data.
  • button_val was renamed to answer.
  • answer_mutex and asnwer_cond were added to replace the previously global button_mutex and button_cond.
  • event_listener was added to be able to join the thread processing GTK+ events on shutdown.
  • plugins was added to keep a cache of already loaded plugins.
  • The opaque di_data was added to hold data specific to the debian-installer.

3.4   fe_gtk_get_text() and memory handling

The current code contains a get_text() function (renamed fe_gtk_get_text()) which is basically a copy and paste of cdebconf's question_get_text(). This function has been kept as an opportunity to add an important fix regarding memory management: question_get_text() does not call strdup() for the fallback string. The returned string can then either come from both dynamic or static allocation at the same time, which disallow correct memory management. question_get_text() should be fixed and this function removed from the GTK+ frontend.

3.5   fe_gtk_set_title() reactivated

The implementation of the set_title method of cdebconf was changed to the default one in r43372 to avoid spurious title changes during pkgsel.

I implemented a somewhat different workaround: fe_gtk_set_title() implements the method and updates the frontend field like the default one. It also triggers an update of the label when the frontend is not built for d-i. When built with --enable-d-i, the label is updated update is done during fe_gtk_di_run_dialog() at the beginning of a PROGRESS or a GO.

3.6   Changes in the common naming of variables

Arguments of type struct frontend * were renamed to a more meaningful fe instead of obj.

This is a disputable change as well, but obj sounded really internal to me.

3.7   STRING_MAX_LENGTH was removed

I have seen no reason to limit the size of entered answers for string questions. So the STRING_MAX_LENGTH constraints were just removed.

4   New features

Doing only refactoring became quite boring at some point and I couldn't resist to add some small new features described here.

Most of them are experiments, both on user interface usability and on the soundness of the code that I was (re)writing.

The banner now adapts to any screen width. The logo is aligned on the left of the screen, and its right side is filled with the color defined in LOGO_BACKGROUND_COLOR.

See: create_banner and handle_exposed_banner.

4.2   Main window adapts to various screen size

WINDOW_HEIGHT and WINDOW_WIDTH constants were removed, as the screen height and width are are now dynamically retrieved through gdk_screen_get_height() and gdk_screen_get_width(). The frontend now works a bit better on higher-resolution.

See: make_fullscreen.

4.3   Screenshot button has been removed

To be able to make more experiments with the interface, the screenshot button has been removed. F10 can be pressed to optain the same effect. The code to create a button is still in screenshot.c though, but commented out.

Frans suggested on IRC that it could perfectly be an option in a menu. I would find it great, except that I don't really have an idea on where we could put it. :)

4.4   Go Back as secondary button

The Go Back button was set secondary. In GTK+ terminology, this means that it will be displayed on the opposite side of other buttons added to the action box.

This make the frontend looks a bit more like the newt frontend. I am not sure if it's better from a usability perspective, though.

4.5   Yes/No buttons for boolean questions

When a boolean question is the only question in a GO, Yes and No buttons are used instead of radio buttons to get the answer.

A screenshot is available if you want to take a look at the result.

4.6   glib logs to syslog

Instead of being lost, logs written by GTK+ are now going to the global syslog. I hope this will allow us to track problems more easily.

5   Bonuses

To close this report, two bonuses that where found along the way... :)

5.1   An entropy plugin for the GTK+ frontend

In order to test that the public API for the plugin was sound, I implemented a GTK+ version of the entropy plugin found in cdebconf-entropy.

It worked well during my tests but is not ready to be packaged for two reasons: I was unsure about reusing the templates of cdebconf-entropy-text and I also stumbled on a linking issue.

The current plugin for the newt frontend uses dlopen() to gain access to three functions defined in newt.so. While this sounds reasonable for only 3 functions, it sounds less reasonable for 12 or more.

We can fully avoid dlopen() and dlsym() calls if we explicitely link the plugin to the frontend shared object. This is basically just adding fe_gtk.so at the end of GCC's command line.

Where this leads to complication is in terms of packaging... Currently cdebconf-entropy Build-Depends on libdebconfclient0-dev to gain access to the necessary headers. But if we want to explicitely link to the plugin, it would need to Build-Depends on cdebconf-gtk-udeb which does not sound nice to me...

It will depend on how we are going to solve this issue, but in any cases, the same system should be adopted by all frontends.

5.2   Progress bar and questions?

Beforing figuring out that during pkgsel, GO commands were issued during a PROGRESS, I got something which we could develop further: the popularity-contest question was asked below the progress bar.

This could actually be great to keep the progress bar running during pkgsel while the various questions are being asked. This would not be so hard to do, IMHO, with the current design.

6   Map of current identifiers

Current identifier name Corresponding identifier name and location
last_keymap di.c:di_data#previous_keymap
current_keymap removed, see di_data#previous_keymap
progressbar_title removed, see progress_data#label
gtk_handler fe_gtk.h:fe_gtk_handler
button_cond fe_data.h:frontend_data#answer_cond
button_mutex fe_data.h:frontend_data#answer_mutex
setter_struct go.c:setter
custom_func_t fe_gtk.h:fe_gtk_handler
register_setter() go.c:fe_gtk_register_setter()
free_description_data() removed, use g_signal_connect_swapped() instead
is_first_question() fe_gtk.c:fe_gtk_is_first_question()
bool_setter() handlers.c:set_value_from_toggle_button()
entry_setter() handlers.c:set_value_from_entry()
combo_setter() handlers.c:set_value_from_combo()
select_setter() handlers.c:set_value_from_select()
multiselect_single_setter() handlers.c:set_value_from_multiselect()
multiselect_multiple_setter() handlers.c:set_value_from_multiselect()
call_setters() go.c:call_setters()
expose_event_callback() ui.c:handle_exposed_banner()
treeview_exposed_callback() removed, selection can be done directly in handler
screenshot_button_callback() screenshot.c:do_screenshot()
multiselect_single_callback() select_handlers.c:update_model_from_toggle_button()
key_press_event removed, replaced by fe_gtk_add_global_key_handler()
exit_button_callback() fe_gtk.c:fe_gtk_set_answer_goback()
cancel_button_callback() fe_gtk.c:fe_gtk_set_answer_goback()
select_onRowActivated() fe_gtk.c:fe_gtk_set_answer_ok()
get_text() fe_gtk.c:fe_gtk_get_text()
get_text_direction() di.c:get_text_direction()
display_descriptions() descriptions.c:fe_gtk_create_description()
gtkhandler_boolean() handlers.c:fe_gtk_handler_boolean()
gtkhandler_multiselect_single() select_handlers.c:create_multiselect_list()
gtkhandler_multiselect_multiple() select_handlers.c:create_multiselect_checkboxes()
gtkhandler_multiselect() select_handlers.c:fe_gtk_handle_multiselect()
gtkhandler_note() handlers.c:fe_gtk_handle_note()
gtkhandler_text() handlers.c:fe_gtk_handle_text()
gtkhandler_password() handlers.c:fe_gtk_handle_password()
gtkhandler_select_single_list() select_handlers.c:create_select_list()
gtkhandler_select_single_tree() select_handlers.c:create_select_list()
gtkhandler_select_multiple select_handlers.c:create_select_combo()
gtkhandler_select() select_handlers.c:fe_gtk_handle_multiselect()
gtkhandler_string() handlers.c:fe_gtk_handle_string()
question_handlers go.c:question_handlers
set_design_elements() ui.c:create_main_widgets()
eventhandler_thread() fe_gtk.c:handle_gtk_events()
gtk_initialize() fe_gtk.c:fe_gtk_initialize()
gtk_plugin_destroy_notify() removed, now use GHashTable destroy callbacks
gtk_go() go.c:fe_gtk_go()
gtk_set_title() fe_gtk.c:fe_gtk_set_title()
gtk_can_go_back() fe_gtk.c:fe_gtk_can_go_back()
gtk_can_cancel_progress() progress.c:fe_gtk_can_cancel_progress()
set_design_elements_while_progressbar_runs() removed, not needed anymore
gtk_progress_start() progress.c:fe_gtk_progress_start()
gtk_progress_set() progress.c:fe_gtk_progress_set()
gtk_progress_info() progress.c:fe_gtk_progress_info()
gtk_progress_stop() progress.c:fe_gtk_progress_stop()
gtk_query_capability fe_gtk.c:fe_gtk_query_capability()
update_frontend_title() ui.c:fe_gtk_update_frontend_title()

7   Statistics on code length

7.1   Current code

Number of functions:
 48
Average LOC per function:
 26.06
Median LOC per function:
 10
Maximum LOC in a function:
 137
sloccount:1,441
Function LOC
register_setter 8
free_description_data 1
is_first_question 9
bool_setter 1
entry_setter 1
combo_setter 22
select_setter 30
multiselect_single_setter 42
multiselect_multiple_setter 48
call_setters 10
expose_event_callback 19
treeview_exposed_callback 6
screenshot_button_callback 65
multiselect_single_callback 11
key_press_event 8
exit_button_callback 10
cancel_button_callback 1
select_onRowActivated 1
get_text 2
get_text_direction 4
display_descriptions 66
gtkhandler_boolean 34
gtkhandler_multiselect_single 80
gtkhandler_multiselect_multiple 64
gtkhandler_multiselect 4
gtkhandler_note 9
gtkhandler_text 1
gtkhandler_password 22
gtkhandler_select_single_list 74
gtkhandler_select_single_tree 115
gtkhandler_select_multiple 51
gtkhandler_select 9
gtkhandler_string 26
set_design_elements 98
eventhandler_thread 4
gtk_initialize 60
gtk_plugin_destroy_notify 1
gtk_go 137
gtk_set_title 4
gtk_can_go_back 1
gtk_can_cancel_progress 1
set_design_elements_while_progressbar_runs 18
gtk_progress_start 23
gtk_progress_set 21
gtk_progress_info 17
gtk_progress_stop 5
gtk_query_capability 2
update_frontend_title 5

7.2   Proposed code

Number of functions:
 141
Average LOC per function:
 11.78
Median LOC per function:
 9
Maximum LOC in a function:
 90
sloccount:2,441
Function LOC
choice_model.c:is_searched_value 9
choice_model.c:fe_gtk_choice_model_find_value 6
choice_model.c:fe_gtk_choice_model_create_full 90
choice_model.c:fe_gtk_choice_model_create 2
choice_model.c:increment_model_length 2
choice_model.c:fe_gtk_choice_model_get_length 5
choice_model.c:is_selected 9
choice_model.c:fe_gtk_choice_model_get_first_selected 4
choice_model.c:fe_gtk_choice_model_set 4
descriptions.c:get_icon_path 7
descriptions.c:add_icon 16
descriptions.c:get_background_color 4
descriptions.c:add_description 26
descriptions.c:add_extended_description 20
descriptions.c:fe_gtk_create_description 20
di.c:fix_gtk_settings 3
di.c:get_question_value 9
di.c:print_to_syslog 1
di.c:get_prefix 9
di.c:log_glib_to_syslog 13
di.c:fe_gtk_di_setup 16
di.c:refresh_keymap 1
di.c:get_text_direction 10
di.c:refresh_language 4
di.c:fe_gtk_di_run_dialog 25
di.c:fe_gtk_di_shutdown 12
di.c:make_fullscreen 4
fe_gtk.c:fe_gtk_get_text 3
fe_gtk.c:handle_gtk_events 4
fe_gtk.c:fe_gtk_is_first_question 9
fe_gtk.c:fe_gtk_force_quit 3
fe_gtk.c:destroy_frontend_data 28
fe_gtk.c:create_frontend_data 27
fe_gtk.c:fe_gtk_shutdown 14
fe_gtk.c:show_main_window 3
fe_gtk.c:fe_gtk_get_answer 6
fe_gtk.c:fe_gtk_set_answer 5
fe_gtk.c:fe_gtk_set_answer_ok 1
fe_gtk.c:fe_gtk_set_answer_notok 1
fe_gtk.c:fe_gtk_set_answer_goback 1
fe_gtk.c:create_event_listener_thread 12
fe_gtk.c:fe_gtk_initialize 34
fe_gtk.c:fe_gtk_set_title 7
fe_gtk.c:fe_gtk_can_go_back 1
fe_gtk.c:fe_gtk_query_capability 1
go.c:fe_gtk_register_setter 9
go.c:call_setters 7
go.c:free_setters 9
go.c:handle_goback_key 5
go.c:create_goback_button 11
go.c:fe_gtk_create_continue_button 12
go.c:create_default_buttons_if_needed 5
go.c:destroy_buttons 4
go.c:find_internal_handler 7
go.c:find_external_handler 14
go.c:update_question_database 6
go.c:call_question_handlers 18
go.c:create_question_box 25
go.c:wait_answer 6
go.c:fe_gtk_go 44
handlers.c:set_value_from_toggle_button 3
handlers.c:handle_boolean_radio 41
handlers.c:handle_false_button 3
handlers.c:handle_true_button 3
handlers.c:handle_boolean_buttons 28
handlers.c:fe_gtk_handle_boolean 5
handlers.c:fe_gtk_handle_note 4
handlers.c:fe_gtk_handle_text 1
handlers.c:set_value_from_entry 1
handlers.c:create_entry_alignment 7
handlers.c:fe_gtk_handle_password 14
handlers.c:fe_gtk_handle_string 15
progress.c:create_progress_bar 10
progress.c:destroy_progress_bar 6
progress.c:create_progress_label 10
progress.c:destroy_progress_label 6
progress.c:create_progress_box 10
progress.c:destroy_progress_box 8
progress.c:fe_gtk_show_progress 16
progress.c:fe_gtk_hide_progress 14
progress.c:handle_cancel_key 5
progress.c:create_cancel_button 13
progress.c:destroy_cancel_button 6
progress.c:fe_gtk_can_cancel_progress 2
progress.c:init_progress 14
progress.c:destroy_progress 9
progress.c:update_progress_bar 7
progress.c:fe_gtk_progress_start 27
progress.c:fe_gtk_progress_set 20
progress.c:fe_gtk_progress_info 16
progress.c:fe_gtk_progress_stop 9
screenshot.c:save_screenshot 24
screenshot.c:create_screenshot_path 16
screenshot.c:popup_success 12
screenshot.c:do_screenshot 11
screenshot.c:handle_shortcut_key 5
screenshot.c:fe_gtk_add_screenshot_shortcut 3
screenshot.c:fe_gtk_create_screenshot_button 16
screenshot.c:fe_gtk_destroy_screenshot_button 6
screenshot.c:fe_gtk_refresh_screenshot_button_label 3
select_handlers.c:combo_setter 13
select_handlers.c:select_setter 14
select_handlers.c:is_country 1
select_handlers.c:is_disk 1
select_handlers.c:get_special_predicate 7
select_handlers.c:create_select_list 62
select_handlers.c:update_selection_for_toggle 11
select_handlers.c:multiselect_setter 38
select_handlers.c:update_model_from_toggle_button 12
select_handlers.c:connect_signal_to_row 8
select_handlers.c:create_multiselect_list 42
select_handlers.c:focus_first_child 4
select_handlers.c:create_multiselect_checkboxes 32
select_handlers.c:set_selected_active 11
select_handlers.c:create_select_combo 19
select_handlers.c:handle_all 16
select_handlers.c:fe_gtk_handle_select 1
select_handlers.c:fe_gtk_handle_multiselect 1
ui.c:fe_gtk_add_common_layout 19
ui.c:handle_exposed_banner 29
ui.c:create_banner 16
ui.c:create_label_title 11
ui.c:create_target_box 9
ui.c:create_action_box 12
ui.c:create_main_widgets 21
ui.c:handle_closed_main_window 4
ui.c:fe_gtk_create_main_window 22
ui.c:remove_shortcut 3
ui.c:fe_gtk_add_global_key_handler 9
ui.c:fe_gtk_destroy_main_window 7
ui.c:fe_gtk_center_widget 13
ui.c:create_dialog_action_box 15
ui.c:create_dialog_title_label 10
ui.c:fe_gtk_run_message_dialog 35
ui.c:fe_gtk_set_buttons_sensitive 7
ui.c:fe_gtk_show_buttons 2
ui.c:fe_gtk_add_button 9
ui.c:fe_gtk_set_button_secondary 3
ui.c:fe_gtk_update_frontend_title 5
ui.c:fe_gtk_show_target_box 2
ui.c:fe_gtk_empty_target_box 3