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