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 <config.h>
#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.


        <child>
          <object class="PsppireDialogActionFoobar" id="some-menu_foobar">
            <property name="manager">uimanager1</property>
            <property name="name">some-menu_foobar</property>
            <property name="label" translatable="yes">_Foobar</property>
           <property name="stock-id">somemenu-foobar</property>
          </object>
        </child>


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:

  <requires lib="psppire" version="2054.17080"/>
  <!-- interface-requires gtk+ 2.12 -->
  <!-- interface-naming-policy project-wide -->

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:

<!-- Generated with glade 3.18.3 -->
<interface>
  <requires lib="gtk+" version="3.12"/>

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?


