d9b15094 |
1 | /* |
2 | * gtkdlg.c - GTK implementation of the PuTTY configuration box. |
3 | */ |
4 | |
5 | #include <assert.h> |
6 | #include <gtk/gtk.h> |
7 | |
8 | #include "gtkcols.h" |
9 | #include "gtkpanel.h" |
10 | |
11 | #ifdef TESTMODE |
12 | #define PUTTY_DO_GLOBALS /* actually _define_ globals */ |
13 | #endif |
14 | |
15 | #include "putty.h" |
16 | #include "dialog.h" |
17 | |
18 | |
19 | void *dlg_get_privdata(union control *ctrl, void *dlg) |
20 | { |
21 | return NULL; /* FIXME */ |
22 | } |
23 | |
24 | void dlg_set_privdata(union control *ctrl, void *dlg, void *ptr) |
25 | { |
26 | /* FIXME */ |
27 | } |
28 | |
29 | void *dlg_alloc_privdata(union control *ctrl, void *dlg, size_t size) |
30 | { |
31 | return NULL; /* FIXME */ |
32 | } |
33 | |
34 | union control *dlg_last_focused(void *dlg) |
35 | { |
36 | return NULL; /* FIXME */ |
37 | } |
38 | |
39 | void dlg_radiobutton_set(union control *ctrl, void *dlg, int whichbutton) |
40 | { |
41 | /* FIXME */ |
42 | } |
43 | |
44 | int dlg_radiobutton_get(union control *ctrl, void *dlg) |
45 | { |
46 | return 0; /* FIXME */ |
47 | } |
48 | |
49 | void dlg_checkbox_set(union control *ctrl, void *dlg, int checked) |
50 | { |
51 | /* FIXME */ |
52 | } |
53 | |
54 | int dlg_checkbox_get(union control *ctrl, void *dlg) |
55 | { |
56 | return 0; /* FIXME */ |
57 | } |
58 | |
59 | void dlg_editbox_set(union control *ctrl, void *dlg, char const *text) |
60 | { |
61 | /* FIXME */ |
62 | } |
63 | |
64 | void dlg_editbox_get(union control *ctrl, void *dlg, char *buffer, int length) |
65 | { |
66 | /* FIXME */ |
67 | } |
68 | |
69 | /* The `listbox' functions can also apply to combo boxes. */ |
70 | void dlg_listbox_clear(union control *ctrl, void *dlg) |
71 | { |
72 | /* FIXME */ |
73 | } |
74 | |
75 | void dlg_listbox_del(union control *ctrl, void *dlg, int index) |
76 | { |
77 | /* FIXME */ |
78 | } |
79 | |
80 | void dlg_listbox_add(union control *ctrl, void *dlg, char const *text) |
81 | { |
82 | /* FIXME */ |
83 | } |
84 | |
85 | /* |
86 | * Each listbox entry may have a numeric id associated with it. |
87 | * Note that some front ends only permit a string to be stored at |
88 | * each position, which means that _if_ you put two identical |
89 | * strings in any listbox then you MUST not assign them different |
90 | * IDs and expect to get meaningful results back. |
91 | */ |
92 | void dlg_listbox_addwithindex(union control *ctrl, void *dlg, |
93 | char const *text, int id) |
94 | { |
95 | /* FIXME */ |
96 | } |
97 | |
98 | int dlg_listbox_getid(union control *ctrl, void *dlg, int index) |
99 | { |
100 | return -1; /* FIXME */ |
101 | } |
102 | |
103 | /* dlg_listbox_index returns <0 if no single element is selected. */ |
104 | int dlg_listbox_index(union control *ctrl, void *dlg) |
105 | { |
106 | return -1; /* FIXME */ |
107 | } |
108 | |
109 | int dlg_listbox_issel(union control *ctrl, void *dlg, int index) |
110 | { |
111 | return 0; /* FIXME */ |
112 | } |
113 | |
114 | void dlg_listbox_select(union control *ctrl, void *dlg, int index) |
115 | { |
116 | /* FIXME */ |
117 | } |
118 | |
119 | void dlg_text_set(union control *ctrl, void *dlg, char const *text) |
120 | { |
121 | /* FIXME */ |
122 | } |
123 | |
124 | void dlg_filesel_set(union control *ctrl, void *dlg, Filename fn) |
125 | { |
126 | /* FIXME */ |
127 | } |
128 | |
129 | void dlg_filesel_get(union control *ctrl, void *dlg, Filename *fn) |
130 | { |
131 | /* FIXME */ |
132 | } |
133 | |
134 | void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec fs) |
135 | { |
136 | /* FIXME */ |
137 | } |
138 | |
139 | void dlg_fontsel_get(union control *ctrl, void *dlg, FontSpec *fs) |
140 | { |
141 | /* FIXME */ |
142 | } |
143 | |
144 | /* |
145 | * Bracketing a large set of updates in these two functions will |
146 | * cause the front end (if possible) to delay updating the screen |
147 | * until it's all complete, thus avoiding flicker. |
148 | */ |
149 | void dlg_update_start(union control *ctrl, void *dlg) |
150 | { |
151 | /* FIXME */ |
152 | } |
153 | |
154 | void dlg_update_done(union control *ctrl, void *dlg) |
155 | { |
156 | /* FIXME */ |
157 | } |
158 | |
159 | void dlg_set_focus(union control *ctrl, void *dlg) |
160 | { |
161 | /* FIXME */ |
162 | } |
163 | |
164 | /* |
165 | * During event processing, you might well want to give an error |
166 | * indication to the user. dlg_beep() is a quick and easy generic |
167 | * error; dlg_error() puts up a message-box or equivalent. |
168 | */ |
169 | void dlg_beep(void *dlg) |
170 | { |
171 | /* FIXME */ |
172 | } |
173 | |
174 | void dlg_error_msg(void *dlg, char *msg) |
175 | { |
176 | /* FIXME */ |
177 | } |
178 | |
179 | /* |
180 | * This function signals to the front end that the dialog's |
181 | * processing is completed, and passes an integer value (typically |
182 | * a success status). |
183 | */ |
184 | void dlg_end(void *dlg, int value) |
185 | { |
186 | /* FIXME */ |
187 | } |
188 | |
189 | void dlg_refresh(union control *ctrl, void *dlg) |
190 | { |
191 | /* FIXME */ |
192 | } |
193 | |
194 | void dlg_coloursel_start(union control *ctrl, void *dlg, int r, int g, int b) |
195 | { |
196 | /* FIXME */ |
197 | } |
198 | |
199 | int dlg_coloursel_results(union control *ctrl, void *dlg, |
200 | int *r, int *g, int *b) |
201 | { |
202 | return 0; /* FIXME */ |
203 | } |
204 | |
205 | /* |
206 | * This function does the main layout work: it reads a controlset, |
207 | * it creates the relevant GTK controls, and returns a GtkWidget |
208 | * containing the result. (This widget might be a title of some |
209 | * sort, it might be a Columns containing many controls, or it |
210 | * might be a GtkFrame containing a Columns; whatever it is, it's |
211 | * definitely a GtkWidget and should probably be added to a |
212 | * GtkVbox.) |
213 | */ |
214 | GtkWidget *layout_ctrls(struct controlset *s) |
215 | { |
216 | Columns *cols; |
217 | GtkWidget *ret; |
218 | int i; |
219 | |
220 | if (!s->boxname && s->boxtitle) { |
221 | /* This controlset is a panel title. */ |
222 | return gtk_label_new(s->boxtitle); |
223 | } |
224 | |
225 | /* |
226 | * Otherwise, we expect to be laying out actual controls, so |
227 | * we'll start by creating a Columns for the purpose. |
228 | */ |
229 | cols = COLUMNS(columns_new(4)); |
230 | ret = GTK_WIDGET(cols); |
231 | gtk_widget_show(ret); |
232 | |
233 | /* |
234 | * Create a containing frame if we have a box name. |
235 | */ |
236 | if (*s->boxname) { |
237 | ret = gtk_frame_new(s->boxtitle); /* NULL is valid here */ |
238 | gtk_container_set_border_width(GTK_CONTAINER(cols), 4); |
239 | gtk_container_add(GTK_CONTAINER(ret), GTK_WIDGET(cols)); |
240 | gtk_widget_show(ret); |
241 | } |
242 | |
243 | /* |
244 | * Now iterate through the controls themselves, create them, |
245 | * and add them to the Columns. |
246 | */ |
247 | for (i = 0; i < s->ncontrols; i++) { |
248 | union control *ctrl = s->ctrls[i]; |
249 | GtkWidget *w = NULL; |
250 | |
251 | switch (ctrl->generic.type) { |
252 | case CTRL_COLUMNS: |
253 | { |
254 | static const int simplecols[1] = { 100 }; |
255 | columns_set_cols(cols, ctrl->columns.ncols, |
256 | (ctrl->columns.percentages ? |
257 | ctrl->columns.percentages : simplecols)); |
258 | } |
259 | continue; /* no actual control created */ |
260 | case CTRL_TABDELAY: |
261 | /* FIXME: we can do columns_taborder_last easily enough, but |
262 | * we need to be able to remember which GtkWidget(s) correspond |
263 | * to ctrl->tabdelay.ctrl. */ |
264 | continue; /* no actual control created */ |
265 | case CTRL_BUTTON: |
266 | w = gtk_button_new_with_label(ctrl->generic.label); |
267 | break; |
268 | case CTRL_CHECKBOX: |
269 | w = gtk_check_button_new_with_label(ctrl->generic.label); |
270 | break; |
271 | case CTRL_RADIO: |
272 | /* |
273 | * Radio buttons get to go inside their own Columns, no |
274 | * matter what. |
275 | */ |
276 | { |
277 | gint i, *percentages; |
278 | GSList *group; |
279 | |
280 | w = columns_new(1); |
281 | if (ctrl->generic.label) { |
282 | GtkWidget *label = gtk_label_new(ctrl->generic.label); |
283 | columns_add(COLUMNS(w), label, 0, 1); |
284 | columns_force_left_align(COLUMNS(w), label); |
285 | gtk_widget_show(label); |
286 | } |
287 | percentages = g_new(gint, ctrl->radio.ncolumns); |
288 | for (i = 0; i < ctrl->radio.ncolumns; i++) { |
289 | percentages[i] = |
290 | ((100 * (i+1) / ctrl->radio.ncolumns) - |
291 | 100 * i / ctrl->radio.ncolumns); |
292 | } |
293 | columns_set_cols(COLUMNS(w), ctrl->radio.ncolumns, |
294 | percentages); |
295 | g_free(percentages); |
296 | group = NULL; |
297 | for (i = 0; i < ctrl->radio.nbuttons; i++) { |
298 | GtkWidget *b; |
299 | gint colstart; |
300 | |
301 | b = (gtk_radio_button_new_with_label |
302 | (group, ctrl->radio.buttons[i])); |
303 | group = gtk_radio_button_group(GTK_RADIO_BUTTON(b)); |
304 | colstart = i % ctrl->radio.ncolumns; |
305 | columns_add(COLUMNS(w), b, colstart, |
306 | (i == ctrl->radio.nbuttons-1 ? |
307 | ctrl->radio.ncolumns - colstart : 1)); |
308 | gtk_widget_show(b); |
309 | } |
310 | } |
311 | break; |
312 | case CTRL_EDITBOX: |
313 | if (ctrl->editbox.has_list) { |
314 | w = gtk_combo_new(); |
315 | } else { |
316 | w = gtk_entry_new(); |
317 | if (ctrl->editbox.password) |
318 | gtk_entry_set_visibility(GTK_ENTRY(w), FALSE); |
319 | } |
320 | /* |
321 | * Edit boxes, for some strange reason, have a minimum |
322 | * width of 150 in GTK 1.2. We don't want this - we'd |
323 | * rather the edit boxes acquired their natural width |
324 | * from the column layout of the rest of the box. |
325 | */ |
326 | { |
327 | GtkRequisition req; |
328 | gtk_widget_size_request(w, &req); |
329 | gtk_widget_set_usize(w, 10, req.height); |
330 | } |
331 | if (ctrl->generic.label) { |
332 | GtkWidget *label, *container; |
333 | |
334 | label = gtk_label_new(ctrl->generic.label); |
335 | |
336 | container = columns_new(4); |
337 | if (ctrl->editbox.percentwidth == 100) { |
338 | columns_add(COLUMNS(container), label, 0, 1); |
339 | columns_force_left_align(COLUMNS(container), label); |
340 | columns_add(COLUMNS(container), w, 0, 1); |
341 | } else { |
342 | gint percentages[2]; |
343 | percentages[1] = ctrl->editbox.percentwidth; |
344 | percentages[0] = 100 - ctrl->editbox.percentwidth; |
345 | columns_set_cols(COLUMNS(container), 2, percentages); |
346 | columns_add(COLUMNS(container), label, 0, 1); |
347 | columns_force_left_align(COLUMNS(container), label); |
348 | columns_add(COLUMNS(container), w, 1, 1); |
349 | } |
350 | gtk_widget_show(label); |
351 | gtk_widget_show(w); |
352 | |
353 | w = container; |
354 | } |
355 | break; |
356 | case CTRL_TEXT: |
357 | w = gtk_label_new(ctrl->generic.label); |
358 | gtk_label_set_line_wrap(GTK_LABEL(w), TRUE); |
359 | gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_FILL); |
360 | break; |
361 | } |
362 | if (w) { |
363 | columns_add(cols, w, |
364 | COLUMN_START(ctrl->generic.column), |
365 | COLUMN_SPAN(ctrl->generic.column)); |
366 | gtk_widget_show(w); |
367 | } |
368 | } |
369 | |
370 | return ret; |
371 | } |
372 | |
373 | struct selparam { |
374 | Panels *panels; |
375 | GtkWidget *panel, *treeitem; |
376 | }; |
377 | |
378 | static void treeitem_sel(GtkItem *item, gpointer data) |
379 | { |
380 | struct selparam *sp = (struct selparam *)data; |
381 | |
382 | panels_switch_to(sp->panels, sp->panel); |
383 | } |
384 | |
385 | void destroy(GtkWidget *widget, gpointer data) |
386 | { |
387 | gtk_main_quit(); |
388 | } |
389 | |
390 | void do_config_box(void) |
391 | { |
392 | GtkWidget *window, *hbox, *vbox, *cols, *label, |
393 | *tree, *treescroll, *panels, *panelvbox; |
394 | int index, level; |
395 | struct controlbox *ctrlbox; |
396 | char *path; |
397 | GtkTreeItem *treeitemlevels[8]; |
398 | GtkTree *treelevels[8]; |
399 | Config cfg; |
400 | |
401 | struct selparam *selparams = NULL; |
402 | int nselparams = 0, selparamsize = 0; |
403 | |
404 | do_defaults(NULL, &cfg); |
405 | |
406 | ctrlbox = ctrl_new_box(); |
407 | setup_config_box(ctrlbox, NULL, FALSE, 0); |
408 | |
409 | window = gtk_dialog_new(); |
410 | gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(window)->vbox), 4); |
411 | hbox = gtk_hbox_new(FALSE, 4); |
412 | gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), hbox, TRUE, TRUE, 0); |
413 | gtk_widget_show(hbox); |
414 | vbox = gtk_vbox_new(FALSE, 4); |
415 | gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0); |
416 | gtk_widget_show(vbox); |
417 | cols = columns_new(4); |
418 | gtk_box_pack_start(GTK_BOX(vbox), cols, FALSE, FALSE, 0); |
419 | gtk_widget_show(cols); |
420 | label = gtk_label_new("Category:"); |
421 | columns_add(COLUMNS(cols), label, 0, 1); |
422 | columns_force_left_align(COLUMNS(cols), label); |
423 | gtk_widget_show(label); |
424 | treescroll = gtk_scrolled_window_new(NULL, NULL); |
425 | tree = gtk_tree_new(); |
426 | gtk_tree_set_view_mode(GTK_TREE(tree), GTK_TREE_VIEW_ITEM); |
427 | gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(treescroll), |
428 | tree); |
429 | gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(treescroll), |
430 | GTK_POLICY_NEVER, |
431 | GTK_POLICY_AUTOMATIC); |
432 | gtk_widget_show(tree); |
433 | gtk_widget_show(treescroll); |
434 | gtk_box_pack_start(GTK_BOX(vbox), treescroll, TRUE, TRUE, 0); |
435 | panels = panels_new(); |
436 | gtk_box_pack_start(GTK_BOX(hbox), panels, TRUE, TRUE, 0); |
437 | gtk_widget_show(panels); |
438 | |
439 | panelvbox = NULL; |
440 | path = NULL; |
441 | level = 0; |
442 | for (index = 0; index < ctrlbox->nctrlsets; index++) { |
443 | struct controlset *s = ctrlbox->ctrlsets[index]; |
444 | GtkWidget *w = layout_ctrls(s); |
445 | |
446 | if (!*s->pathname) { |
447 | gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->action_area), |
448 | w, TRUE, TRUE, 0); |
449 | } else { |
450 | int j = path ? ctrl_path_compare(s->pathname, path) : 0; |
451 | if (j != INT_MAX) { /* add to treeview, start new panel */ |
452 | char *c; |
453 | GtkWidget *treeitem; |
454 | int first; |
455 | |
456 | /* |
457 | * We expect never to find an implicit path |
458 | * component. For example, we expect never to see |
459 | * A/B/C followed by A/D/E, because that would |
460 | * _implicitly_ create A/D. All our path prefixes |
461 | * are expected to contain actual controls and be |
462 | * selectable in the treeview; so we would expect |
463 | * to see A/D _explicitly_ before encountering |
464 | * A/D/E. |
465 | */ |
466 | assert(j == ctrl_path_elements(s->pathname) - 1); |
467 | |
468 | c = strrchr(s->pathname, '/'); |
469 | if (!c) |
470 | c = s->pathname; |
471 | else |
472 | c++; |
473 | |
474 | treeitem = gtk_tree_item_new_with_label(c); |
475 | assert(j-1 < level); |
476 | if (j > 0) { |
477 | if (!treelevels[j-1]) { |
478 | treelevels[j-1] = GTK_TREE(gtk_tree_new()); |
479 | gtk_tree_item_set_subtree |
480 | (treeitemlevels[j-1], |
481 | GTK_WIDGET(treelevels[j-1])); |
482 | gtk_tree_item_expand(treeitemlevels[j-1]); |
483 | } |
484 | gtk_tree_append(treelevels[j-1], treeitem); |
485 | } else { |
486 | gtk_tree_append(GTK_TREE(tree), treeitem); |
487 | } |
488 | treeitemlevels[j] = GTK_TREE_ITEM(treeitem); |
489 | treelevels[j] = NULL; |
490 | level = j+1; |
491 | |
492 | gtk_widget_show(treeitem); |
493 | |
494 | path = s->pathname; |
495 | |
496 | first = (panelvbox == NULL); |
497 | |
498 | panelvbox = gtk_vbox_new(FALSE, 4); |
499 | gtk_container_add(GTK_CONTAINER(panels), panelvbox); |
500 | if (first) { |
501 | panels_switch_to(PANELS(panels), panelvbox); |
502 | gtk_tree_select_child(GTK_TREE(tree), treeitem); |
503 | } |
504 | |
505 | if (nselparams >= selparamsize) { |
506 | selparamsize += 16; |
507 | selparams = srealloc(selparams, |
508 | selparamsize * sizeof(*selparams)); |
509 | } |
510 | selparams[nselparams].panels = PANELS(panels); |
511 | selparams[nselparams].panel = panelvbox; |
512 | selparams[nselparams].treeitem = treeitem; |
513 | nselparams++; |
514 | |
515 | } |
516 | |
517 | gtk_box_pack_start(GTK_BOX(panelvbox), w, FALSE, FALSE, 0); |
518 | } |
519 | } |
520 | |
521 | for (index = 0; index < nselparams; index++) { |
522 | gtk_signal_connect(GTK_OBJECT(selparams[index].treeitem), "select", |
523 | GTK_SIGNAL_FUNC(treeitem_sel), |
524 | &selparams[index]); |
525 | } |
526 | |
527 | gtk_widget_show(window); |
528 | |
529 | gtk_signal_connect(GTK_OBJECT(window), "destroy", |
530 | GTK_SIGNAL_FUNC(destroy), NULL); |
531 | |
532 | gtk_main(); |
533 | |
534 | sfree(selparams); |
535 | } |
536 | |
537 | /* ====================================================================== |
538 | * Below here is a stub main program which allows the dialog box |
539 | * code to be compiled and tested with a minimal amount of the rest |
540 | * of PuTTY. |
541 | */ |
542 | |
543 | #ifdef TESTMODE |
544 | |
545 | /* Compile command for testing: |
546 | |
547 | gcc -o gtkdlg gtk{dlg,cols,panel}.c ../{config,dialog,settings}.c \ |
548 | ../{misc,tree234,be_none}.c ux{store,misc,print}.c \ |
549 | -I. -I.. -I../charset -DTESTMODE `gtk-config --cflags --libs` |
550 | */ |
551 | |
552 | void modalfatalbox(char *p, ...) |
553 | { |
554 | va_list ap; |
555 | fprintf(stderr, "FATAL ERROR: "); |
556 | va_start(ap, p); |
557 | vfprintf(stderr, p, ap); |
558 | va_end(ap); |
559 | fputc('\n', stderr); |
560 | exit(1); |
561 | } |
562 | |
563 | char *cp_name(int codepage) |
564 | { |
565 | return (codepage == 123 ? "testing123" : |
566 | codepage == 234 ? "testing234" : |
567 | codepage == 345 ? "testing345" : |
568 | "unknown"); |
569 | } |
570 | |
571 | char *cp_enumerate(int index) |
572 | { |
573 | return (index == 0 ? "testing123" : |
574 | index == 1 ? "testing234" : |
575 | NULL); |
576 | } |
577 | |
578 | int decode_codepage(char *cp_name) |
579 | { |
580 | return (!strcmp(cp_name, "testing123") ? 123 : |
581 | !strcmp(cp_name, "testing234") ? 234 : |
582 | !strcmp(cp_name, "testing345") ? 345 : |
583 | -2); |
584 | } |
585 | |
586 | struct printer_enum_tag { int dummy; } printer_test; |
587 | |
588 | printer_enum *printer_start_enum(int *nprinters_ptr) { |
589 | *nprinters_ptr = 2; |
590 | return &printer_test; |
591 | } |
592 | char *printer_get_name(printer_enum *pe, int i) { |
593 | return (i==0 ? "lpr" : i==1 ? "lpr -Pfoobar" : NULL); |
594 | } |
595 | void printer_finish_enum(printer_enum *pe) { } |
596 | |
597 | char *platform_default_s(const char *name) |
598 | { |
599 | return NULL; |
600 | } |
601 | |
602 | int platform_default_i(const char *name, int def) |
603 | { |
604 | return def; |
605 | } |
606 | |
607 | FontSpec platform_default_fontspec(const char *name) |
608 | { |
609 | FontSpec ret; |
610 | *ret.name = '\0'; |
611 | return ret; |
612 | } |
613 | |
614 | Filename platform_default_filename(const char *name) |
615 | { |
616 | Filename ret; |
617 | *ret.path = '\0'; |
618 | return ret; |
619 | } |
620 | |
621 | char *x_get_default(const char *key) |
622 | { |
623 | return NULL; |
624 | } |
625 | |
626 | int main(int argc, char **argv) |
627 | { |
628 | gtk_init(&argc, &argv); |
629 | do_config_box(); |
630 | return 0; |
631 | } |
632 | |
633 | #endif |