Fix bug when searching through an empty dataset.
[pspp] / src / ui / gui / memorandum.txt
1 This is a mini howto, describing the steps to add a new dialog box
2 to psppire.   This is document is only tested for the master branch.
3 Gtk3 is uncharted territory ....
4
5
6 How to add a new dialog to Psppire
7 ==================================
8
9
10 Before you start.
11
12 1. You will need to install Glade version 3.8.4 ---    ANY OTHER VERSION
13 IS UNLIKELY TO WORK  --- I know that 3.18.x does not!  If your
14 distro  doesn't have this version, you will need to download it and
15 build it from source.
16
17
18
19 2. Build and install the lastest pspp master HEAD, with the
20    "--with-gui-tools" option passed to configure.  Thus:
21
22    ./configure --with-gui-tools
23    make
24    sudo make install
25
26    This installs the pspp specific libraries and widgets that glade needs.
27
28    For this step, you must build inside the pspp source directory, and
29    you must install to the default directory.  DO NOT build from
30    outside the source, and do not pass a --prefix to configure.
31
32
33 Having done the above, you should be able to edit an existing dialog
34 definition, thus:
35
36             glade-3 src/ui/gui/descriptives.ui
37
38 You will probably get a lot of Gtk-Warnings/Criticals  most of them
39 are harmless.
40
41
42 If this works, now you are in a position to start ....
43
44 There are two basic steps.  Firstly, you need to define the layout of
45 the dialog.  This is done using glade, which you have installed as
46 described above.  The second step is to define the behaviour of the
47 dialog.  For this, you need to define a new object deriving from
48 PsppireDialogAction
49
50
51
52 Example:  Adding a new dialog: foobar
53 -------------------------------------
54
55
56 Defining the layout
57 ...................
58
59 Start glade and create a new GtkBuilder file, thus:
60       glade-3 src/ui/gui/foobar.ui
61
62 All dialogs need a top level window.  For Psppire, this must be an
63 instance of PsppireDialog.
64 On the left hand side of the Glade window, you will see a category
65 headed "Psppire".  Click on the first item in that category and a new
66 (empty) dialog will be created.
67
68 Most dialogs have a vertical button box on the right hand side, so
69 click on the "Vertical Button Box" icon.  Then on the right hand panel
70 of the new dialog.  You will be prompted to enter the number of items
71 in the button box.  For this example, chooose 5.
72
73 Note, that by default, the dialog box was created with an "internal
74 child" GtkVbox whose "Number of items" property is 2.  Change this
75 to 4.  You should see the extra empty panels appear.
76
77 Typically a dialog box needs a DictionaryTreeview in the left hand
78 panel.  But we normally put that inside a scrolled window, which in
79 turn is inside a Frame.
80
81 Under the "Containers" category there is a "Frame" item.  Click on
82 this.  Then on the leftmost panel of the dialog box.  The new GtkFrame
83 widget should appear.  Again in the "Containers" category click on
84 "Scrolled Window" and then in the frame you have just created.
85 This will put a new GtkScrolledWindow inside the GtkFrame.
86
87 Now click on the "Dictionary Treeview" widget (unde the "Psppire"
88 category, and put that inside the Scrolled window.
89
90 You should now have  a half complete dialog looking something like
91 this:
92
93 +---------------+-----------------+----------------+-----------------+
94 |Frame1         |                 |                | +--------------+|
95 |+-------------+|                 |                | |     OK       ||
96 ||             ||                 |                | +--------------+|
97 ||             ||                 |                |                 |
98 ||             ||                 |                | +--------------+|
99 ||             ||                 |                | |   Paste      ||
100 ||             ||                 |                | +--------------+|
101 ||             ||                 |                |                 |
102 ||             ||                 |                | +--------------+|
103 ||             ||                 |                | |   Cancel     ||
104 ||             ||                 |                | +--------------+|
105 ||             ||                 |                |                 |
106 ||             ||                 |                | +--------------+|
107 ||             ||                 |                | |   Reset      ||
108 ||             ||                 |                | +--------------+|
109 ||             ||                 |                |                 |
110 ||             ||                 |                | +--------------+|
111 ||             ||                 |                | |   Help       ||
112 |+-------------+|                 |                | +--------------+|
113 +---------------+-----------------+----------------+-----------------+
114
115 In the second panel from the left, we might want a selector button.
116 However, we don't want to to fill the entire panel.
117 So we put it in inside a GtkAlignment.  From the "Containers" category
118 click "Alignment". and then the panel.   Such a widget is of course
119 not visible,  but you will note its presence from the widget hierarchy
120 in the top right hand corner of the glade window.
121 Now, from the "Psppire" category, click on "Selector Button" and put
122 it inside the new GtkAlignment.  It will appear to occupy the entire
123 panel.  To fix this, select the GtkAlignment and change its Horizontal
124 Scale and Vertical Scale properties to zero.
125
126 If all is well, the dialog box now looks like this:
127
128
129 +---------------+-----------------+----------------+-----------------+
130 |Frame1         |                 |                | +--------------+|
131 |+-------------+|                 |                | |     OK       ||
132 ||             ||                 |                | +--------------+|
133 ||             ||                 |                |                 |
134 ||             ||                 |                | +--------------+|
135 ||             ||                 |                | |   Paste      ||
136 ||             ||                 |                | +--------------+|
137 ||             ||                 |                |                 |
138 ||             ||       |\        |                | +--------------+|
139 ||             ||       | \       |                | |   Cancel     ||
140 ||             ||       | /       |                | +--------------+|
141 ||             ||       |/        |                |                 |
142 ||             ||                 |                | +--------------+|
143 ||             ||                 |                | |   Reset      ||
144 ||             ||                 |                | +--------------+|
145 ||             ||                 |                |                 |
146 ||             ||                 |                | +--------------+|
147 ||             ||                 |                | |   Help       ||
148 |+-------------+|                 |                | +--------------+|
149 +---------------+-----------------+----------------+-----------------+
150
151
152 However you will notice that upon resizing the dialog, the selector
153 button's panel expands with it, which is probably not what we want.
154 To fix this, select the GtkAlignment which we created above, and set
155 its "Expand" packing-property to false.
156
157
158 We final panel might require a PsppireVarView widget, again inside a
159 GtkScrolled window (and possibly a GtkFrame).  Create this, in a
160 similar way to which you did the PsppireDictionaryTreeview above.
161
162 Now you have a fully populated dialog box.  It might need tweaking
163 which we can do later.  But we are now in a position to display our
164 defined dialog box.
165
166
167 Displaying the Dialog box in Psppire
168 ....................................
169
170
171 1. Define a new PsppireDialogAction Class
172
173 Create a new object class derived from PsppireDialogAction  (note that
174 PsppireDialogAction itself derives from GtkAction).  It's probably
175 best if you use an existing example as a template.  The minimum you
176 require is:
177
178
179 #include <config.h>
180 #include "psppire-dialog-action-foobar.h"
181 #include "psppire-var-view.h"
182 #include "psppire-dialog.h"
183 #include "builder-wrapper.h"
184
185 static void psppire_dialog_action_foobar_init            (PsppireDialogActionFoobar      *act);
186 static void psppire_dialog_action_foobar_class_init      (PsppireDialogActionFoobarClass *class);
187
188 G_DEFINE_TYPE (PsppireDialogActionFoobar, psppire_dialog_action_foobar, PSPPIRE_TYPE_DIALOG_ACTION);
189
190 static gboolean
191 dialog_state_valid (gpointer data)
192 {
193   PsppireDialogActionFoobar *ud = PSPPIRE_DIALOG_ACTION_FOOBAR (data);
194
195   // This function is a predicate to determine if the dialog box has
196   //   been set to a state where is is appropriate to click OK  /
197   //  Paste.
198
199   // If it returns FALSE, the OK and PASTE buttons are insensitive
200
201
202   return ....;
203 }
204
205 static void
206 refresh (PsppireDialogAction *rd_)
207 {
208   PsppireDialogActionFoobar *uv = PSPPIRE_DIALOG_ACTION_FOOBAR (rd_);
209
210   // This function is called when the Reset Button is clicked.
211   // It sets the dialog to its default state
212 }
213
214
215 // This function is called when the menuitem is activated.
216 // It is what pops up the dialog 
217 static void
218 psppire_dialog_action_foobar_activate (GtkAction *a)
219 {
220   PsppireDialogAction *pda = PSPPIRE_DIALOG_ACTION (a);
221   PsppireDialogActionFoobar *act = PSPPIRE_DIALOG_ACTION_FOOBAR (a);
222
223   // These three lines are (almost) always required
224   GtkBuilder *xml = builder_new ("foobar.ui");
225   pda->dialog = get_widget_assert   (xml, "foobar-dialog");
226   pda->source = get_widget_assert   (xml, "dict-view");
227
228   // ... here you can load any widgets that your dialog uses
229   act->this_widget = get_widget_assert (xml, "this_widget");
230   act->that_widget = get_widget_assert (xml, "that_widget");
231
232   // ... you will most probably need to have these here
233   psppire_dialog_action_set_valid_predicate (pda, dialog_state_valid);
234   psppire_dialog_action_set_refresh (pda, refresh);
235
236
237   // Everything below this line is necessary.
238   g_object_unref (xml);
239
240   if (PSPPIRE_DIALOG_ACTION_CLASS (psppire_dialog_action_foobar_parent_class)->activate)
241     PSPPIRE_DIALOG_ACTION_CLASS (psppire_dialog_action_foobar_parent_class)->activate (pda);
242 }
243
244
245 // Most often this function will not need any changes
246 static void
247 psppire_dialog_action_foobar_class_init (PsppireDialogActionFoobarClass *class)
248 {
249   GtkActionClass *action_class = GTK_ACTION_CLASS (class);
250
251   action_class->activate = psppire_dialog_action_foobar_activate;
252   PSPPIRE_DIALOG_ACTION_CLASS (class)->generate_syntax = generate_syntax;
253 }
254
255
256 static void
257 psppire_dialog_action_foobar_init (PsppireDialogActionFoobar *act)
258 {
259  // often, this function can be empty
260 }
261
262
263
264 static char *
265 generate_syntax (PsppireDialogAction *act)
266 {
267   PsppireDialogActionFoobar *uvd = PSPPIRE_DIALOG_ACTION_FOOBAR (act);
268
269   gchar *text = NULL;
270   GString *str = g_string_new ("FOOBAR ");
271
272   // ... this function generates the syntax to be interpreted by
273   //   PSPP's backend
274
275   g_string_append (str, ".\n");
276   text = str->str;
277   g_string_free (str, FALSE);
278
279   return text;
280 }
281
282 2. Declare the new PsppireDialogAction Class
283
284 You will also need to define the corresponding .h header file.  Best
285 to copy and search replace on an existing one.
286
287
288 3. Adding the entry point for the dialog.
289
290 Edit src/ui/gui/data-editor.ui  It is possible to do this with Glade,
291 but frankly is easier with a text editor.  Something like the
292 following is typical.
293
294
295         <child>
296           <object class="PsppireDialogActionFoobar" id="some-menu_foobar">
297             <property name="manager">uimanager1</property>
298             <property name="name">some-menu_foobar</property>
299             <property name="label" translatable="yes">_Foobar</property>
300            <property name="stock-id">somemenu-foobar</property>
301           </object>
302         </child>
303
304
305 4. Announce the new object to GtkBuilder.
306
307 Add the new PsppireDialogAction's get_type function to  to
308 src/ui/gui/widgets.c  Just have a look there and follow the pattern.
309 This is necessary to that GtkBuilder knows about the new object class.
310
311 5.  Putting it all together.
312
313 Don't forget to put any new files you've created in
314 src/ui/gui/automake.mk and to rerun make ; make install
315
316
317
318 That's about it, I think.  Did I forget anything?
319
320