This is a mini howto, describing the steps to add a new dialog box to psppire. This is document is only tested for the master branch. Gtk3 is uncharted territory .... How to add a new dialog to Psppire ================================== Before you start. 1. You will need to install Glade (but see the important note about which version at the end of this document! 2. Build and install the lastest pspp master HEAD, with the "--with-gui-tools" option passed to configure. Thus: ./configure --with-gui-tools make sudo make install This installs the pspp specific libraries and widgets that glade needs. For this step, you must build inside the pspp source directory, and you must install to the default directory. DO NOT build from outside the source, and do not pass a --prefix to configure. Having done the above, you should be able to edit an existing dialog definition, thus: glade src/ui/gui/descriptives.ui You will probably get a lot of Gtk-Warnings/Criticals most of them are harmless. If this works, now you are in a position to start .... There are two basic steps. Firstly, you need to define the layout of the dialog. This is done using glade, which you have installed as described above. The second step is to define the behaviour of the dialog. For this, you need to define a new object deriving from PsppireDialogAction Example: Adding a new dialog: foobar ------------------------------------- Defining the layout ................... Start glade and create a new GtkBuilder file, thus: glade-3 src/ui/gui/foobar.ui All dialogs need a top level window. For Psppire, this must be an instance of PsppireDialog. On the left hand side of the Glade window, you will see a category headed "Psppire". Click on the first item in that category and a new (empty) dialog will be created. Most dialogs have a vertical button box on the right hand side, so click on the "Vertical Button Box" icon. Then on the right hand panel of the new dialog. You will be prompted to enter the number of items in the button box. For this example, chooose 5. Note, that by default, the dialog box was created with an "internal child" GtkVbox whose "Number of items" property is 2. Change this to 4. You should see the extra empty panels appear. Typically a dialog box needs a DictionaryTreeview in the left hand panel. But we normally put that inside a scrolled window, which in turn is inside a Frame. Under the "Containers" category there is a "Frame" item. Click on this. Then on the leftmost panel of the dialog box. The new GtkFrame widget should appear. Again in the "Containers" category click on "Scrolled Window" and then in the frame you have just created. This will put a new GtkScrolledWindow inside the GtkFrame. Now click on the "Dictionary Treeview" widget (unde the "Psppire" category, and put that inside the Scrolled window. You should now have a half complete dialog looking something like this: +---------------+-----------------+----------------+-----------------+ |Frame1 | | | +--------------+| |+-------------+| | | | OK || || || | | +--------------+| || || | | | || || | | +--------------+| || || | | | Paste || || || | | +--------------+| || || | | | || || | | +--------------+| || || | | | Cancel || || || | | +--------------+| || || | | | || || | | +--------------+| || || | | | Reset || || || | | +--------------+| || || | | | || || | | +--------------+| || || | | | Help || |+-------------+| | | +--------------+| +---------------+-----------------+----------------+-----------------+ In the second panel from the left, we might want a selector button. However, we don't want to fill the entire panel. So we put it in inside a GtkAlignment. From the "Containers" category click "Alignment". and then the panel. Such a widget is of course not visible, but you will note its presence from the widget hierarchy in the top right hand corner of the glade window. Now, from the "Psppire" category, click on "Selector Button" and put it inside the new GtkAlignment. It will appear to occupy the entire panel. To fix this, select the GtkAlignment and change its Horizontal Scale and Vertical Scale properties to zero. If all is well, the dialog box now looks like this: +---------------+-----------------+----------------+-----------------+ |Frame1 | | | +--------------+| |+-------------+| | | | OK || || || | | +--------------+| || || | | | || || | | +--------------+| || || | | | Paste || || || | | +--------------+| || || | | | || || |\ | | +--------------+| || || | \ | | | Cancel || || || | / | | +--------------+| || || |/ | | | || || | | +--------------+| || || | | | Reset || || || | | +--------------+| || || | | | || || | | +--------------+| || || | | | Help || |+-------------+| | | +--------------+| +---------------+-----------------+----------------+-----------------+ However you will notice that upon resizing the dialog, the selector button's panel expands with it, which is probably not what we want. To fix this, select the GtkAlignment which we created above, and set its "Expand" packing-property to false. We final panel might require a PsppireVarView widget, again inside a GtkScrolled window (and possibly a GtkFrame). Create this, in a similar way to which you did the PsppireDictionaryTreeview above. Now you have a fully populated dialog box. It might need tweaking which we can do later. But we are now in a position to display our defined dialog box. Displaying the Dialog box in Psppire .................................... 1. Define a new PsppireDialogAction Class Create a new object class derived from PsppireDialogAction (note that PsppireDialogAction itself implements GAction). It's probably best if you use an existing example as a template. The minimum you require is: #include #include "psppire-dialog-action-foobar.h" #include "psppire-var-view.h" #include "psppire-dialog.h" #include "builder-wrapper.h" static void psppire_dialog_action_foobar_init (PsppireDialogActionFoobar *act); static void psppire_dialog_action_foobar_class_init (PsppireDialogActionFoobarClass *class); G_DEFINE_TYPE (PsppireDialogActionFoobar, psppire_dialog_action_foobar, PSPPIRE_TYPE_DIALOG_ACTION); static gboolean dialog_state_valid (gpointer data) { PsppireDialogActionFoobar *ud = PSPPIRE_DIALOG_ACTION_FOOBAR (data); // This function is a predicate to determine if the dialog box has // been set to a state where it is appropriate to click OK / // Paste. // If it returns FALSE, the OK and PASTE buttons are insensitive return ....; } static void refresh (PsppireDialogAction *rd_) { PsppireDialogActionFoobar *uv = PSPPIRE_DIALOG_ACTION_FOOBAR (rd_); // This function is called when the Reset Button is clicked. // It sets the dialog to its default state } // This function is called when the menuitem is activated. // It is what pops up the dialog static void psppire_dialog_action_foobar_activate (GAction *a) { PsppireDialogAction *pda = PSPPIRE_DIALOG_ACTION (a); PsppireDialogActionFoobar *act = PSPPIRE_DIALOG_ACTION_FOOBAR (a); // These three lines are (almost) always required GtkBuilder *xml = builder_new ("foobar.ui"); pda->dialog = get_widget_assert (xml, "foobar-dialog"); pda->source = get_widget_assert (xml, "dict-view"); // ... here you can load any widgets that your dialog uses act->this_widget = get_widget_assert (xml, "this_widget"); act->that_widget = get_widget_assert (xml, "that_widget"); // ... you will most probably need to have these here psppire_dialog_action_set_valid_predicate (pda, dialog_state_valid); psppire_dialog_action_set_refresh (pda, refresh); // Everything below this line is necessary. g_object_unref (xml); if (PSPPIRE_DIALOG_ACTION_CLASS (psppire_dialog_action_foobar_parent_class)->activate) PSPPIRE_DIALOG_ACTION_CLASS (psppire_dialog_action_foobar_parent_class)->activate (pda); } // Most often this function will not need any changes static void psppire_dialog_action_foobar_class_init (PsppireDialogActionFoobarClass *class) { GActionClass *action_class = GTK_ACTION_CLASS (class); action_class->activate = psppire_dialog_action_foobar_activate; PSPPIRE_DIALOG_ACTION_CLASS (class)->generate_syntax = generate_syntax; } static void psppire_dialog_action_foobar_init (PsppireDialogActionFoobar *act) { // often, this function can be empty } static char * generate_syntax (PsppireDialogAction *act) { PsppireDialogActionFoobar *uvd = PSPPIRE_DIALOG_ACTION_FOOBAR (act); gchar *text = NULL; GString *str = g_string_new ("FOOBAR "); // ... this function generates the syntax to be interpreted by // PSPP's backend g_string_append (str, ".\n"); text = str->str; g_string_free (str, FALSE); return text; } 2. Declare the new PsppireDialogAction Class You will also need to define the corresponding .h header file. Best to copy and search replace on an existing one. 3. Adding the entry point for the dialog. Edit src/ui/gui/data-editor.ui It is possible to do this with Glade, but frankly is easier with a text editor. Something like the following is typical. uimanager1 some-menu_foobar _Foobar somemenu-foobar 4. Announce the new object to GtkBuilder. Add the new PsppireDialogAction's get_type function to to src/ui/gui/widgets.c Just have a look there and follow the pattern. This is necessary to that GtkBuilder knows about the new object class. 5. Putting it all together. Don't forget to put any new files you've created in src/ui/gui/automake.mk and to rerun make ; make install Note! Currently (as of commit fe7682b3c3d36cf9ba3e867588e5b808af833262 ) psppire is in a transitional phase. Our .ui files come in two mutually incompatible varieties. The older variety can be identified by a string similar to: To edit these files you will need to install Glade version 3.8.4 --- ANY OTHER VERSION IS UNLIKELY TO WORK --- I know that 3.18.x does not! If your distro doesn't have this version, you will need to download it and build it from source. The newer ones contain the string: Like the string suggests Glade version 3.18.x or later will probably be ok for these files. Hopefully the older style .ui files will gradually be converted to new style ones. That's about it, I think. Did I forget anything?