2 * gtkdlg.c - GTK implementation of the PuTTY configuration box.
8 * - event handling, in general!
16 * + can't we _somehow_ have less leading between radio buttons?
17 * + wrapping text widgets, the horror, the horror
18 * + labels and their associated edit boxes don't line up
29 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
35 void *dlg_get_privdata(union control
*ctrl
, void *dlg
)
37 return NULL
; /* FIXME */
40 void dlg_set_privdata(union control
*ctrl
, void *dlg
, void *ptr
)
45 void *dlg_alloc_privdata(union control
*ctrl
, void *dlg
, size_t size
)
47 return NULL
; /* FIXME */
50 union control
*dlg_last_focused(void *dlg
)
52 return NULL
; /* FIXME */
55 void dlg_radiobutton_set(union control
*ctrl
, void *dlg
, int whichbutton
)
60 int dlg_radiobutton_get(union control
*ctrl
, void *dlg
)
65 void dlg_checkbox_set(union control
*ctrl
, void *dlg
, int checked
)
70 int dlg_checkbox_get(union control
*ctrl
, void *dlg
)
75 void dlg_editbox_set(union control
*ctrl
, void *dlg
, char const *text
)
80 void dlg_editbox_get(union control
*ctrl
, void *dlg
, char *buffer
, int length
)
85 /* The `listbox' functions can also apply to combo boxes. */
86 void dlg_listbox_clear(union control
*ctrl
, void *dlg
)
91 void dlg_listbox_del(union control
*ctrl
, void *dlg
, int index
)
96 void dlg_listbox_add(union control
*ctrl
, void *dlg
, char const *text
)
102 * Each listbox entry may have a numeric id associated with it.
103 * Note that some front ends only permit a string to be stored at
104 * each position, which means that _if_ you put two identical
105 * strings in any listbox then you MUST not assign them different
106 * IDs and expect to get meaningful results back.
108 void dlg_listbox_addwithindex(union control
*ctrl
, void *dlg
,
109 char const *text
, int id
)
114 int dlg_listbox_getid(union control
*ctrl
, void *dlg
, int index
)
116 return -1; /* FIXME */
119 /* dlg_listbox_index returns <0 if no single element is selected. */
120 int dlg_listbox_index(union control
*ctrl
, void *dlg
)
122 return -1; /* FIXME */
125 int dlg_listbox_issel(union control
*ctrl
, void *dlg
, int index
)
127 return 0; /* FIXME */
130 void dlg_listbox_select(union control
*ctrl
, void *dlg
, int index
)
135 void dlg_text_set(union control
*ctrl
, void *dlg
, char const *text
)
140 void dlg_filesel_set(union control
*ctrl
, void *dlg
, Filename fn
)
145 void dlg_filesel_get(union control
*ctrl
, void *dlg
, Filename
*fn
)
150 void dlg_fontsel_set(union control
*ctrl
, void *dlg
, FontSpec fs
)
155 void dlg_fontsel_get(union control
*ctrl
, void *dlg
, FontSpec
*fs
)
161 * Bracketing a large set of updates in these two functions will
162 * cause the front end (if possible) to delay updating the screen
163 * until it's all complete, thus avoiding flicker.
165 void dlg_update_start(union control
*ctrl
, void *dlg
)
170 void dlg_update_done(union control
*ctrl
, void *dlg
)
175 void dlg_set_focus(union control
*ctrl
, void *dlg
)
181 * During event processing, you might well want to give an error
182 * indication to the user. dlg_beep() is a quick and easy generic
183 * error; dlg_error() puts up a message-box or equivalent.
185 void dlg_beep(void *dlg
)
190 void dlg_error_msg(void *dlg
, char *msg
)
196 * This function signals to the front end that the dialog's
197 * processing is completed, and passes an integer value (typically
200 void dlg_end(void *dlg
, int value
)
205 void dlg_refresh(union control
*ctrl
, void *dlg
)
210 void dlg_coloursel_start(union control
*ctrl
, void *dlg
, int r
, int g
, int b
)
215 int dlg_coloursel_results(union control
*ctrl
, void *dlg
,
216 int *r
, int *g
, int *b
)
218 return 0; /* FIXME */
222 * This function does the main layout work: it reads a controlset,
223 * it creates the relevant GTK controls, and returns a GtkWidget
224 * containing the result. (This widget might be a title of some
225 * sort, it might be a Columns containing many controls, or it
226 * might be a GtkFrame containing a Columns; whatever it is, it's
227 * definitely a GtkWidget and should probably be added to a
230 GtkWidget
*layout_ctrls(struct controlset
*s
, int listitemheight
)
236 if (!s
->boxname
&& s
->boxtitle
) {
237 /* This controlset is a panel title. */
238 return gtk_label_new(s
->boxtitle
);
242 * Otherwise, we expect to be laying out actual controls, so
243 * we'll start by creating a Columns for the purpose.
245 cols
= COLUMNS(columns_new(4));
246 ret
= GTK_WIDGET(cols
);
247 gtk_widget_show(ret
);
250 * Create a containing frame if we have a box name.
253 ret
= gtk_frame_new(s
->boxtitle
); /* NULL is valid here */
254 gtk_container_set_border_width(GTK_CONTAINER(cols
), 4);
255 gtk_container_add(GTK_CONTAINER(ret
), GTK_WIDGET(cols
));
256 gtk_widget_show(ret
);
260 * Now iterate through the controls themselves, create them,
261 * and add them to the Columns.
263 for (i
= 0; i
< s
->ncontrols
; i
++) {
264 union control
*ctrl
= s
->ctrls
[i
];
267 switch (ctrl
->generic
.type
) {
270 static const int simplecols
[1] = { 100 };
271 columns_set_cols(cols
, ctrl
->columns
.ncols
,
272 (ctrl
->columns
.percentages ?
273 ctrl
->columns
.percentages
: simplecols
));
275 continue; /* no actual control created */
277 /* FIXME: we can do columns_taborder_last easily enough, but
278 * we need to be able to remember which GtkWidget(s) correspond
279 * to ctrl->tabdelay.ctrl. */
280 continue; /* no actual control created */
282 w
= gtk_button_new_with_label(ctrl
->generic
.label
);
285 w
= gtk_check_button_new_with_label(ctrl
->generic
.label
);
289 * Radio buttons get to go inside their own Columns, no
293 gint i
, *percentages
;
297 if (ctrl
->generic
.label
) {
298 GtkWidget
*label
= gtk_label_new(ctrl
->generic
.label
);
299 columns_add(COLUMNS(w
), label
, 0, 1);
300 columns_force_left_align(COLUMNS(w
), label
);
301 gtk_widget_show(label
);
303 percentages
= g_new(gint
, ctrl
->radio
.ncolumns
);
304 for (i
= 0; i
< ctrl
->radio
.ncolumns
; i
++) {
306 ((100 * (i
+1) / ctrl
->radio
.ncolumns
) -
307 100 * i
/ ctrl
->radio
.ncolumns
);
309 columns_set_cols(COLUMNS(w
), ctrl
->radio
.ncolumns
,
313 for (i
= 0; i
< ctrl
->radio
.nbuttons
; i
++) {
317 b
= (gtk_radio_button_new_with_label
318 (group
, ctrl
->radio
.buttons
[i
]));
319 group
= gtk_radio_button_group(GTK_RADIO_BUTTON(b
));
320 colstart
= i
% ctrl
->radio
.ncolumns
;
321 columns_add(COLUMNS(w
), b
, colstart
,
322 (i
== ctrl
->radio
.nbuttons
-1 ?
323 ctrl
->radio
.ncolumns
- colstart
: 1));
329 if (ctrl
->editbox
.has_list
) {
333 if (ctrl
->editbox
.password
)
334 gtk_entry_set_visibility(GTK_ENTRY(w
), FALSE
);
337 * Edit boxes, for some strange reason, have a minimum
338 * width of 150 in GTK 1.2. We don't want this - we'd
339 * rather the edit boxes acquired their natural width
340 * from the column layout of the rest of the box.
344 gtk_widget_size_request(w
, &req
);
345 gtk_widget_set_usize(w
, 10, req
.height
);
347 if (ctrl
->generic
.label
) {
348 GtkWidget
*label
, *container
;
350 label
= gtk_label_new(ctrl
->generic
.label
);
352 container
= columns_new(4);
353 if (ctrl
->editbox
.percentwidth
== 100) {
354 columns_add(COLUMNS(container
), label
, 0, 1);
355 columns_force_left_align(COLUMNS(container
), label
);
356 columns_add(COLUMNS(container
), w
, 0, 1);
359 percentages
[1] = ctrl
->editbox
.percentwidth
;
360 percentages
[0] = 100 - ctrl
->editbox
.percentwidth
;
361 columns_set_cols(COLUMNS(container
), 2, percentages
);
362 columns_add(COLUMNS(container
), label
, 0, 1);
363 columns_force_left_align(COLUMNS(container
), label
);
364 columns_add(COLUMNS(container
), w
, 1, 1);
366 gtk_widget_show(label
);
372 case CTRL_FILESELECT
:
373 case CTRL_FONTSELECT
:
378 (ctrl
->generic
.type
== CTRL_FILESELECT ?
379 "Browse..." : "Change...");
381 gint percentages
[] = { 75, 25 };
383 columns_set_cols(COLUMNS(w
), 2, percentages
);
385 if (ctrl
->generic
.label
) {
386 ww
= gtk_label_new(ctrl
->generic
.label
);
387 columns_add(COLUMNS(w
), ww
, 0, 2);
388 columns_force_left_align(COLUMNS(w
), ww
);
392 ww
= gtk_entry_new();
393 gtk_widget_size_request(ww
, &req
);
394 gtk_widget_set_usize(ww
, 10, req
.height
);
395 columns_add(COLUMNS(w
), ww
, 0, 1);
398 ww
= gtk_button_new_with_label(browsebtn
);
399 columns_add(COLUMNS(w
), ww
, 1, 1);
404 if (ctrl
->listbox
.height
== 0) {
405 w
= gtk_option_menu_new();
408 list
= gtk_list_new();
409 gtk_list_set_selection_mode(GTK_LIST(list
),
410 (ctrl
->listbox
.multisel ?
411 GTK_SELECTION_MULTIPLE
:
412 GTK_SELECTION_SINGLE
));
413 w
= gtk_scrolled_window_new(NULL
, NULL
);
414 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(w
),
416 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w
),
418 GTK_POLICY_AUTOMATIC
);
419 gtk_widget_show(list
);
422 * Adjust the height of the scrolled window to the
423 * minimum given by the height parameter.
425 * This piece of guesswork is a horrid hack based
426 * on looking inside the GTK 1.2 sources
427 * (specifically gtkviewport.c, which appears to be
428 * the widget which provides the border around the
429 * scrolling area). Anyone lets me know how I can
430 * do this in a way which isn't at risk from GTK
431 * upgrades, I'd be grateful.
434 int edge
= GTK_WIDGET(list
)->style
->klass
->ythickness
;
435 gtk_widget_set_usize(w
, 10,
436 2*edge
+ (ctrl
->listbox
.height
*
440 /* here is an example of a percentage-based tabbed list item */
441 { int i
; for (i
=0; i
<10; i
++) {
442 GtkWidget
*listitem
= gtk_list_item_new();
443 GtkWidget
*cols
= columns_new(4);
444 GtkWidget
*label1
= gtk_label_new("left");
445 GtkWidget
*label2
= gtk_label_new("right");
447 static const gint percents
[] = { 50, 50 };
448 columns_set_cols(COLUMNS(cols
), 2, percents
);
449 columns_add(COLUMNS(cols
), label1
, 0, 1);
450 columns_force_left_align(COLUMNS(cols
), label1
);
451 columns_add(COLUMNS(cols
), label2
, 1, 1);
452 columns_force_left_align(COLUMNS(cols
), label2
);
453 gtk_widget_show(label1
);
454 gtk_widget_show(label2
);
455 gtk_widget_show(cols
);
456 gtk_container_add(GTK_CONTAINER(listitem
), cols
);
457 itemlist
= g_list_append(NULL
, listitem
);
458 gtk_list_append_items(GTK_LIST(list
), itemlist
);
459 gtk_widget_show(listitem
);
463 if (ctrl
->listbox
.draglist
) {
465 * GTK doesn't appear to make it easy to
466 * implement a proper draggable list; so
467 * instead I'm just going to have to put an Up
468 * and a Down button to the right of the actual
471 GtkWidget
*cols
, *button
;
472 static const gint percentages
[2] = { 80, 20 };
474 cols
= columns_new(4);
475 columns_set_cols(COLUMNS(cols
), 2, percentages
);
476 columns_add(COLUMNS(cols
), w
, 0, 1);
478 button
= gtk_button_new_with_label("Up");
479 columns_add(COLUMNS(cols
), button
, 1, 1);
480 gtk_widget_show(button
);
481 button
= gtk_button_new_with_label("Down");
482 columns_add(COLUMNS(cols
), button
, 1, 1);
483 gtk_widget_show(button
);
489 if (ctrl
->generic
.label
) {
490 GtkWidget
*label
, *container
;
492 label
= gtk_label_new(ctrl
->generic
.label
);
494 container
= columns_new(4);
495 if (ctrl
->listbox
.percentwidth
== 100) {
496 columns_add(COLUMNS(container
), label
, 0, 1);
497 columns_force_left_align(COLUMNS(container
), label
);
498 columns_add(COLUMNS(container
), w
, 0, 1);
501 percentages
[1] = ctrl
->listbox
.percentwidth
;
502 percentages
[0] = 100 - ctrl
->listbox
.percentwidth
;
503 columns_set_cols(COLUMNS(container
), 2, percentages
);
504 columns_add(COLUMNS(container
), label
, 0, 1);
505 columns_force_left_align(COLUMNS(container
), label
);
506 columns_add(COLUMNS(container
), w
, 1, 1);
508 gtk_widget_show(label
);
515 w
= gtk_label_new(ctrl
->generic
.label
);
516 gtk_label_set_line_wrap(GTK_LABEL(w
), TRUE
);
517 /* FIXME: deal with wrapping! */
522 COLUMN_START(ctrl
->generic
.column
),
523 COLUMN_SPAN(ctrl
->generic
.column
));
533 GtkWidget
*panel
, *treeitem
;
536 static void treeitem_sel(GtkItem
*item
, gpointer data
)
538 struct selparam
*sp
= (struct selparam
*)data
;
540 panels_switch_to(sp
->panels
, sp
->panel
);
543 void destroy(GtkWidget
*widget
, gpointer data
)
548 void do_config_box(void)
550 GtkWidget
*window
, *hbox
, *vbox
, *cols
, *label
,
551 *tree
, *treescroll
, *panels
, *panelvbox
;
552 int index
, level
, listitemheight
;
553 struct controlbox
*ctrlbox
;
555 GtkTreeItem
*treeitemlevels
[8];
556 GtkTree
*treelevels
[8];
559 struct selparam
*selparams
= NULL
;
560 int nselparams
= 0, selparamsize
= 0;
562 do_defaults(NULL
, &cfg
);
565 GtkWidget
*listitem
= gtk_list_item_new_with_label("foo");
567 gtk_widget_size_request(listitem
, &req
);
568 listitemheight
= req
.height
;
569 gtk_widget_unref(listitem
);
572 ctrlbox
= ctrl_new_box();
573 setup_config_box(ctrlbox
, NULL
, FALSE
, 0);
574 unix_setup_config_box(ctrlbox
, FALSE
);
576 window
= gtk_dialog_new();
577 hbox
= gtk_hbox_new(FALSE
, 4);
578 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window
)->vbox
), hbox
, TRUE
, TRUE
, 0);
579 gtk_container_set_border_width(GTK_CONTAINER(hbox
), 10);
580 gtk_widget_show(hbox
);
581 vbox
= gtk_vbox_new(FALSE
, 4);
582 gtk_box_pack_start(GTK_BOX(hbox
), vbox
, FALSE
, FALSE
, 0);
583 gtk_widget_show(vbox
);
584 cols
= columns_new(4);
585 gtk_box_pack_start(GTK_BOX(vbox
), cols
, FALSE
, FALSE
, 0);
586 gtk_widget_show(cols
);
587 label
= gtk_label_new("Category:");
588 columns_add(COLUMNS(cols
), label
, 0, 1);
589 columns_force_left_align(COLUMNS(cols
), label
);
590 gtk_widget_show(label
);
591 treescroll
= gtk_scrolled_window_new(NULL
, NULL
);
592 tree
= gtk_tree_new();
593 gtk_tree_set_view_mode(GTK_TREE(tree
), GTK_TREE_VIEW_ITEM
);
594 gtk_tree_set_selection_mode(GTK_TREE(tree
), GTK_SELECTION_BROWSE
);
595 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(treescroll
),
597 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(treescroll
),
599 GTK_POLICY_AUTOMATIC
);
600 gtk_widget_show(tree
);
601 gtk_widget_show(treescroll
);
602 gtk_box_pack_start(GTK_BOX(vbox
), treescroll
, TRUE
, TRUE
, 0);
603 panels
= panels_new();
604 gtk_box_pack_start(GTK_BOX(hbox
), panels
, TRUE
, TRUE
, 0);
605 gtk_widget_show(panels
);
610 for (index
= 0; index
< ctrlbox
->nctrlsets
; index
++) {
611 struct controlset
*s
= ctrlbox
->ctrlsets
[index
];
612 GtkWidget
*w
= layout_ctrls(s
, listitemheight
);
615 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window
)->action_area
),
618 int j
= path ?
ctrl_path_compare(s
->pathname
, path
) : 0;
619 if (j
!= INT_MAX
) { /* add to treeview, start new panel */
625 * We expect never to find an implicit path
626 * component. For example, we expect never to see
627 * A/B/C followed by A/D/E, because that would
628 * _implicitly_ create A/D. All our path prefixes
629 * are expected to contain actual controls and be
630 * selectable in the treeview; so we would expect
631 * to see A/D _explicitly_ before encountering
634 assert(j
== ctrl_path_elements(s
->pathname
) - 1);
636 c
= strrchr(s
->pathname
, '/');
642 treeitem
= gtk_tree_item_new_with_label(c
);
645 if (!treelevels
[j
-1]) {
646 treelevels
[j
-1] = GTK_TREE(gtk_tree_new());
647 gtk_tree_item_set_subtree
648 (treeitemlevels
[j
-1],
649 GTK_WIDGET(treelevels
[j
-1]));
650 gtk_tree_item_expand(treeitemlevels
[j
-1]);
652 gtk_tree_append(treelevels
[j
-1], treeitem
);
654 gtk_tree_append(GTK_TREE(tree
), treeitem
);
656 treeitemlevels
[j
] = GTK_TREE_ITEM(treeitem
);
657 treelevels
[j
] = NULL
;
660 gtk_widget_show(treeitem
);
664 first
= (panelvbox
== NULL
);
666 panelvbox
= gtk_vbox_new(FALSE
, 4);
667 gtk_container_add(GTK_CONTAINER(panels
), panelvbox
);
669 panels_switch_to(PANELS(panels
), panelvbox
);
670 gtk_tree_select_child(GTK_TREE(tree
), treeitem
);
673 if (nselparams
>= selparamsize
) {
675 selparams
= srealloc(selparams
,
676 selparamsize
* sizeof(*selparams
));
678 selparams
[nselparams
].panels
= PANELS(panels
);
679 selparams
[nselparams
].panel
= panelvbox
;
680 selparams
[nselparams
].treeitem
= treeitem
;
685 gtk_box_pack_start(GTK_BOX(panelvbox
), w
, FALSE
, FALSE
, 0);
690 for (index
= 0; index
< nselparams
; index
++) {
691 gtk_signal_connect(GTK_OBJECT(selparams
[index
].treeitem
), "select",
692 GTK_SIGNAL_FUNC(treeitem_sel
),
696 gtk_widget_show(window
);
698 gtk_signal_connect(GTK_OBJECT(window
), "destroy",
699 GTK_SIGNAL_FUNC(destroy
), NULL
);
706 /* ======================================================================
707 * Below here is a stub main program which allows the dialog box
708 * code to be compiled and tested with a minimal amount of the rest
714 /* Compile command for testing:
716 gcc -g -o gtkdlg gtk{dlg,cols,panel}.c ../{config,dialog,settings}.c \
717 ../{misc,tree234,be_none}.c ux{store,misc,print,cfg}.c \
718 -I. -I.. -I../charset -DTESTMODE `gtk-config --cflags --libs`
721 void modalfatalbox(char *p
, ...)
724 fprintf(stderr
, "FATAL ERROR: ");
726 vfprintf(stderr
, p
, ap
);
732 char *cp_name(int codepage
)
734 return (codepage
== 123 ?
"testing123" :
735 codepage
== 234 ?
"testing234" :
736 codepage
== 345 ?
"testing345" :
740 char *cp_enumerate(int index
)
742 return (index
== 0 ?
"testing123" :
743 index
== 1 ?
"testing234" :
747 int decode_codepage(char *cp_name
)
749 return (!strcmp(cp_name
, "testing123") ?
123 :
750 !strcmp(cp_name
, "testing234") ?
234 :
751 !strcmp(cp_name
, "testing345") ?
345 :
755 struct printer_enum_tag
{ int dummy
; } printer_test
;
757 printer_enum
*printer_start_enum(int *nprinters_ptr
) {
759 return &printer_test
;
761 char *printer_get_name(printer_enum
*pe
, int i
) {
762 return (i
==0 ?
"lpr" : i
==1 ?
"lpr -Pfoobar" : NULL
);
764 void printer_finish_enum(printer_enum
*pe
) { }
766 char *platform_default_s(const char *name
)
771 int platform_default_i(const char *name
, int def
)
776 FontSpec
platform_default_fontspec(const char *name
)
783 Filename
platform_default_filename(const char *name
)
790 char *x_get_default(const char *key
)
795 int main(int argc
, char **argv
)
797 gtk_init(&argc
, &argv
);