From d1d918b6f146845440950a8a5c99621a2eace4bb Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 25 Jan 2007 19:33:29 +0000 Subject: [PATCH 01/53] Commit Colin Watson's original GTK2 patch, exactly as mailed to me on 1st January except that I've had to fiddle with it a bit to take account of r7117 having happened since then. [originally from svn r7157] [r7117 == 174bb7f1fd1b1a4823905ca931eec67ab0fc9976] --- unix/gtkcols.c | 25 ++++++ unix/gtkdlg.c | 215 +++++++++++++++++++++++++++++++++++++++---------- unix/gtkwin.c | 34 +++++--- 3 files changed, 222 insertions(+), 52 deletions(-) diff --git a/unix/gtkcols.c b/unix/gtkcols.c index c0a0ed8d..e70400de 100644 --- a/unix/gtkcols.c +++ b/unix/gtkcols.c @@ -3,18 +3,23 @@ */ #include "gtkcols.h" +#include static void columns_init(Columns *cols); static void columns_class_init(ColumnsClass *klass); static void columns_map(GtkWidget *widget); static void columns_unmap(GtkWidget *widget); +#if !GTK_CHECK_VERSION(2,0,0) static void columns_draw(GtkWidget *widget, GdkRectangle *area); static gint columns_expose(GtkWidget *widget, GdkEventExpose *event); +#endif static void columns_base_add(GtkContainer *container, GtkWidget *widget); static void columns_remove(GtkContainer *container, GtkWidget *widget); static void columns_forall(GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data); +#if !GTK_CHECK_VERSION(2,0,0) static gint columns_focus(GtkContainer *container, GtkDirectionType dir); +#endif static GtkType columns_child_type(GtkContainer *container); static void columns_size_request(GtkWidget *widget, GtkRequisition *req); static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc); @@ -43,8 +48,10 @@ GtkType columns_get_type(void) return columns_type; } +#if !GTK_CHECK_VERSION(2,0,0) static gint (*columns_inherited_focus)(GtkContainer *container, GtkDirectionType direction); +#endif static void columns_class_init(ColumnsClass *klass) { @@ -65,8 +72,10 @@ static void columns_class_init(ColumnsClass *klass) widget_class->map = columns_map; widget_class->unmap = columns_unmap; +#if !GTK_CHECK_VERSION(2,0,0) widget_class->draw = columns_draw; widget_class->expose_event = columns_expose; +#endif widget_class->size_request = columns_size_request; widget_class->size_allocate = columns_size_allocate; @@ -74,10 +83,12 @@ static void columns_class_init(ColumnsClass *klass) container_class->remove = columns_remove; container_class->forall = columns_forall; container_class->child_type = columns_child_type; +#if !GTK_CHECK_VERSION(2,0,0) /* Save the previous value of this method. */ if (!columns_inherited_focus) columns_inherited_focus = container_class->focus; container_class->focus = columns_focus; +#endif } static void columns_init(Columns *cols) @@ -135,6 +146,7 @@ static void columns_unmap(GtkWidget *widget) gtk_widget_unmap(child->widget); } } +#if !GTK_CHECK_VERSION(2,0,0) static void columns_draw(GtkWidget *widget, GdkRectangle *area) { Columns *cols; @@ -186,6 +198,7 @@ static gint columns_expose(GtkWidget *widget, GdkEventExpose *event) } return FALSE; } +#endif static void columns_base_add(GtkContainer *container, GtkWidget *widget) { @@ -241,6 +254,9 @@ static void columns_remove(GtkContainer *container, GtkWidget *widget) cols->taborder = g_list_remove_link(cols->taborder, children); g_list_free(children); +#if GTK_CHECK_VERSION(2,0,0) + gtk_container_set_focus_chain(container, cols->taborder); +#endif break; } } @@ -332,6 +348,10 @@ void columns_add(Columns *cols, GtkWidget *child, gtk_widget_set_parent(child, GTK_WIDGET(cols)); +#if GTK_CHECK_VERSION(2,0,0) + gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder); +#endif + if (GTK_WIDGET_REALIZED(cols)) gtk_widget_realize(child); @@ -382,10 +402,14 @@ void columns_taborder_last(Columns *cols, GtkWidget *widget) cols->taborder = g_list_remove_link(cols->taborder, children); g_list_free(children); cols->taborder = g_list_append(cols->taborder, widget); +#if GTK_CHECK_VERSION(2,0,0) + gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder); +#endif break; } } +#if !GTK_CHECK_VERSION(2,0,0) /* * Override GtkContainer's focus movement so the user can * explicitly specify the tab order. @@ -449,6 +473,7 @@ static gint columns_focus(GtkContainer *container, GtkDirectionType dir) } else return columns_inherited_focus(container, dir); } +#endif /* * Now here comes the interesting bit. The actual layout part is diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index 0a870ca3..ae8dad48 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -5,8 +5,8 @@ /* * TODO when porting to GTK 2.0: * - * - GtkTree is apparently deprecated and we should switch to - * GtkTreeView instead. + * - GtkList is deprecated and we should switch to GtkTreeView instead + * (done for GtkTree). * - GtkLabel has a built-in mnemonic scheme, so we should at * least consider switching to that from the current adhockery. */ @@ -67,9 +67,12 @@ struct dlgparam { * due to automatic processing and should not flag a user event. */ int flags; struct Shortcuts *shortcuts; - GtkWidget *window, *cancelbutton, *currtreeitem, **treeitems; + GtkWidget *window, *cancelbutton; union control *currfocus, *lastfocus; +#if !GTK_CHECK_VERSION(2,0,0) + GtkWidget *currtreeitem, **treeitems; int ntreeitems; +#endif int retval; }; #define FLAG_UPDATING_COMBO_LIST 1 @@ -83,6 +86,14 @@ enum { /* values for Shortcut.action */ SHORTCUT_UCTRL_DOWN, /* uctrl is a draglist, move Down */ }; +#if GTK_CHECK_VERSION(2,0,0) +enum { + TREESTORE_PATH, + TREESTORE_PARAMS, + TREESTORE_NUM +}; +#endif + /* * Forward references. */ @@ -151,8 +162,11 @@ static void dlg_init(struct dlgparam *dp) dp->byctrl = newtree234(uctrl_cmp_byctrl); dp->bywidget = newtree234(uctrl_cmp_bywidget); dp->coloursel_result.ok = FALSE; + dp->window = dp->cancelbutton = NULL; +#if !GTK_CHECK_VERSION(2,0,0) dp->treeitems = NULL; - dp->window = dp->cancelbutton = dp->currtreeitem = NULL; + dp->currtreeitem = NULL; +#endif dp->flags = 0; dp->currfocus = NULL; } @@ -172,7 +186,9 @@ static void dlg_cleanup(struct dlgparam *dp) } freetree234(dp->bywidget); dp->bywidget = NULL; +#if !GTK_CHECK_VERSION(2,0,0) sfree(dp->treeitems); +#endif } static void dlg_add_uctrl(struct dlgparam *dp, struct uctrl *uc) @@ -735,7 +751,11 @@ void dlg_set_focus(union control *ctrl, void *dlg) gtk_widget_grab_focus(uc->optmenu); } else { assert(uc->list != NULL); +#if GTK_CHECK_VERSION(2,0,0) + gtk_widget_grab_focus(uc->list); +#else gtk_container_focus(GTK_CONTAINER(uc->list), GTK_DIR_TAB_FORWARD); +#endif } break; } @@ -863,7 +883,11 @@ void dlg_coloursel_start(union control *ctrl, void *dlg, int r, int g, int b) dp->coloursel_result.ok = FALSE; gtk_window_set_modal(GTK_WINDOW(coloursel), TRUE); +#if GTK_CHECK_VERSION(2,0,0) + gtk_color_selection_set_has_opacity_control(GTK_COLOR_SELECTION(ccs->colorsel), FALSE); +#else gtk_color_selection_set_opacity(GTK_COLOR_SELECTION(ccs->colorsel), FALSE); +#endif cvals[0] = r / 255.0; cvals[1] = g / 255.0; cvals[2] = b / 255.0; @@ -1157,7 +1181,8 @@ static void filesel_ok(GtkButton *button, gpointer data) /* struct dlgparam *dp = (struct dlgparam *)data; */ gpointer filesel = gtk_object_get_data(GTK_OBJECT(button), "user-data"); struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(filesel), "user-data"); - char *name = gtk_file_selection_get_filename(GTK_FILE_SELECTION(filesel)); + const char *name = gtk_file_selection_get_filename + (GTK_FILE_SELECTION(filesel)); gtk_entry_set_text(GTK_ENTRY(uc->entry), name); } @@ -1166,7 +1191,7 @@ static void fontsel_ok(GtkButton *button, gpointer data) /* struct dlgparam *dp = (struct dlgparam *)data; */ gpointer fontsel = gtk_object_get_data(GTK_OBJECT(button), "user-data"); struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(fontsel), "user-data"); - char *name = gtk_font_selection_dialog_get_font_name + const char *name = gtk_font_selection_dialog_get_font_name (GTK_FONT_SELECTION_DIALOG(fontsel)); gtk_entry_set_text(GTK_ENTRY(uc->entry), name); } @@ -1222,15 +1247,20 @@ static void filefont_clicked(GtkButton *button, gpointer data) } if (uc->ctrl->generic.type == CTRL_FONTSELECT) { +#if !GTK_CHECK_VERSION(2,0,0) gchar *spacings[] = { "c", "m", NULL }; - gchar *fontname = gtk_entry_get_text(GTK_ENTRY(uc->entry)); +#endif + const gchar *fontname = gtk_entry_get_text(GTK_ENTRY(uc->entry)); + /* TODO: In GTK 2, this only seems to offer client-side fonts. */ GtkWidget *fontsel = gtk_font_selection_dialog_new("Select a font"); gtk_window_set_modal(GTK_WINDOW(fontsel), TRUE); +#if !GTK_CHECK_VERSION(2,0,0) gtk_font_selection_dialog_set_filter (GTK_FONT_SELECTION_DIALOG(fontsel), GTK_FONT_FILTER_BASE, GTK_FONT_ALL, NULL, NULL, NULL, NULL, spacings, NULL); +#endif if (!gtk_font_selection_dialog_set_font_name (GTK_FONT_SELECTION_DIALOG(fontsel), fontname)) { /* @@ -1245,6 +1275,7 @@ static void filefont_clicked(GtkButton *button, gpointer data) Display *disp = GDK_FONT_XDISPLAY(font); Atom fontprop = XInternAtom(disp, "FONT", False); unsigned long ret; + gdk_font_ref(font); if (XGetFontProperty(xfs, fontprop, &ret)) { char *name = XGetAtomName(disp, (Atom)ret); if (name) @@ -1620,7 +1651,12 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, * upgrades, I'd be grateful. */ { - int edge = GTK_WIDGET(uc->list)->style->klass->ythickness; + int edge; +#if GTK_CHECK_VERSION(2,0,0) + edge = GTK_WIDGET(uc->list)->style->ythickness; +#else + edge = GTK_WIDGET(uc->list)->style->klass->ythickness; +#endif gtk_widget_set_usize(w, 10, 2*edge + (ctrl->listbox.height * listitemheight)); @@ -1741,10 +1777,37 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, struct selparam { struct dlgparam *dp; GtkNotebook *panels; - GtkWidget *panel, *treeitem; + GtkWidget *panel; +#if !GTK_CHECK_VERSION(2,0,0) + GtkWidget *treeitem; +#endif struct Shortcuts shortcuts; }; +#if GTK_CHECK_VERSION(2,0,0) +static void treeselection_changed(GtkTreeSelection *treeselection, + gpointer data) +{ + struct selparam *sps = (struct selparam *)data, *sp; + GtkTreeModel *treemodel; + GtkTreeIter treeiter; + gint spindex; + gint page_num; + + if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter)) + return; + + gtk_tree_model_get(treemodel, &treeiter, TREESTORE_PARAMS, &spindex, -1); + sp = &sps[spindex]; + + page_num = gtk_notebook_page_num(sp->panels, sp->panel); + gtk_notebook_set_page(sp->panels, page_num); + + dlg_refresh(NULL, sp->dp); + + sp->dp->shortcuts = &sp->shortcuts; +} +#else static void treeitem_sel(GtkItem *item, gpointer data) { struct selparam *sp = (struct selparam *)data; @@ -1758,12 +1821,14 @@ static void treeitem_sel(GtkItem *item, gpointer data) sp->dp->shortcuts = &sp->shortcuts; sp->dp->currtreeitem = sp->treeitem; } +#endif static void window_destroy(GtkWidget *widget, gpointer data) { gtk_main_quit(); } +#if !GTK_CHECK_VERSION(2,0,0) static int tree_grab_focus(struct dlgparam *dp) { int i, f; @@ -1799,6 +1864,7 @@ gint tree_focus(GtkContainer *container, GtkDirectionType direction, */ return tree_grab_focus(dp); } +#endif int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) { @@ -1817,7 +1883,11 @@ int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) switch (sc->action) { case SHORTCUT_TREE: +#if GTK_CHECK_VERSION(2,0,0) + gtk_widget_grab_focus(sc->widget); +#else tree_grab_focus(dp); +#endif break; case SHORTCUT_FOCUS: gtk_widget_grab_focus(sc->widget); @@ -1893,8 +1963,12 @@ int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) } else { assert(sc->uc->list != NULL); +#if GTK_CHECK_VERSION(2,0,0) + gtk_widget_grab_focus(sc->uc->list); +#else gtk_container_focus(GTK_CONTAINER(sc->uc->list), GTK_DIR_TAB_FORWARD); +#endif } break; } @@ -1905,6 +1979,7 @@ int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) return FALSE; } +#if !GTK_CHECK_VERSION(2,0,0) int tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) { struct dlgparam *dp = (struct dlgparam *)data; @@ -1973,6 +2048,7 @@ int tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) return FALSE; } +#endif static void shortcut_highlight(GtkWidget *labelw, int chr) { @@ -2024,7 +2100,7 @@ int get_listitemheight(void) GtkWidget *listitem = gtk_list_item_new_with_label("foo"); GtkRequisition req; gtk_widget_size_request(listitem, &req); - gtk_widget_unref(listitem); + gtk_object_sink(GTK_OBJECT(listitem)); return req.height; } @@ -2036,8 +2112,16 @@ int do_config_box(const char *title, Config *cfg, int midsession, int index, level, listitemheight; struct controlbox *ctrlbox; char *path; +#if GTK_CHECK_VERSION(2,0,0) + GtkTreeStore *treestore; + GtkCellRenderer *treerenderer; + GtkTreeViewColumn *treecolumn; + GtkTreeSelection *treeselection; + GtkTreeIter treeiterlevels[8]; +#else GtkTreeItem *treeitemlevels[8]; GtkTree *treelevels[8]; +#endif struct dlgparam dp; struct Shortcuts scs; @@ -2075,14 +2159,28 @@ int do_config_box(const char *title, Config *cfg, int midsession, columns_force_left_align(COLUMNS(cols), label); gtk_widget_show(label); treescroll = gtk_scrolled_window_new(NULL, NULL); +#if GTK_CHECK_VERSION(2,0,0) + treestore = gtk_tree_store_new + (TREESTORE_NUM, G_TYPE_STRING, G_TYPE_INT); + tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(treestore)); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), FALSE); + treerenderer = gtk_cell_renderer_text_new(); + treecolumn = gtk_tree_view_column_new_with_attributes + ("Label", treerenderer, "text", 0, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(tree), treecolumn); + treeselection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree)); + gtk_tree_selection_set_mode(treeselection, GTK_SELECTION_BROWSE); + gtk_container_add(GTK_CONTAINER(treescroll), tree); +#else tree = gtk_tree_new(); - gtk_signal_connect(GTK_OBJECT(tree), "focus_in_event", - GTK_SIGNAL_FUNC(widget_focus), &dp); - shortcut_add(&scs, label, 'g', SHORTCUT_TREE, tree); gtk_tree_set_view_mode(GTK_TREE(tree), GTK_TREE_VIEW_ITEM); gtk_tree_set_selection_mode(GTK_TREE(tree), GTK_SELECTION_BROWSE); gtk_signal_connect(GTK_OBJECT(tree), "focus", GTK_SIGNAL_FUNC(tree_focus), &dp); +#endif + gtk_signal_connect(GTK_OBJECT(tree), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), &dp); + shortcut_add(&scs, label, 'g', SHORTCUT_TREE, tree); gtk_widget_show(treescroll); gtk_box_pack_start(GTK_BOX(vbox), treescroll, TRUE, TRUE, 0); panels = gtk_notebook_new(); @@ -2106,7 +2204,11 @@ int do_config_box(const char *title, Config *cfg, int midsession, int j = path ? ctrl_path_compare(s->pathname, path) : 0; if (j != INT_MAX) { /* add to treeview, start new panel */ char *c; +#if GTK_CHECK_VERSION(2,0,0) + GtkTreeIter treeiter; +#else GtkWidget *treeitem; +#endif int first; /* @@ -2127,8 +2229,50 @@ int do_config_box(const char *title, Config *cfg, int midsession, else c++; - treeitem = gtk_tree_item_new_with_label(c); + path = s->pathname; + + first = (panelvbox == NULL); + + panelvbox = gtk_vbox_new(FALSE, 4); + gtk_widget_show(panelvbox); + gtk_notebook_append_page(GTK_NOTEBOOK(panels), panelvbox, + NULL); + if (first) { + gint page_num; + + page_num = gtk_notebook_page_num(GTK_NOTEBOOK(panels), + panelvbox); + gtk_notebook_set_page(GTK_NOTEBOOK(panels), page_num); + } + + if (nselparams >= selparamsize) { + selparamsize += 16; + selparams = sresize(selparams, selparamsize, + struct selparam); + } + selparams[nselparams].dp = &dp; + selparams[nselparams].panels = GTK_NOTEBOOK(panels); + selparams[nselparams].panel = panelvbox; + selparams[nselparams].shortcuts = scs; /* structure copy */ + assert(j-1 < level); + +#if GTK_CHECK_VERSION(2,0,0) + if (j > 0) + /* treeiterlevels[j-1] will always be valid because we + * don't allow implicit path components; see above. + */ + gtk_tree_store_append(treestore, &treeiter, + &treeiterlevels[j-1]); + else + gtk_tree_store_append(treestore, &treeiter, NULL); + gtk_tree_store_set(treestore, &treeiter, + TREESTORE_PATH, c, + TREESTORE_PARAMS, nselparams, + -1); + treeiterlevels[j] = treeiter; +#else + treeitem = gtk_tree_item_new_with_label(c); if (j > 0) { if (!treelevels[j-1]) { treelevels[j-1] = GTK_TREE(gtk_tree_new()); @@ -2146,7 +2290,6 @@ int do_config_box(const char *title, Config *cfg, int midsession, } treeitemlevels[j] = GTK_TREE_ITEM(treeitem); treelevels[j] = NULL; - level = j+1; gtk_signal_connect(GTK_OBJECT(treeitem), "key_press_event", GTK_SIGNAL_FUNC(tree_key_press), &dp); @@ -2155,35 +2298,13 @@ int do_config_box(const char *title, Config *cfg, int midsession, gtk_widget_show(treeitem); - path = s->pathname; - - first = (panelvbox == NULL); - - panelvbox = gtk_vbox_new(FALSE, 4); - gtk_widget_show(panelvbox); - gtk_notebook_append_page(GTK_NOTEBOOK(panels), panelvbox, - NULL); - if (first) { - gint page_num; - - page_num = gtk_notebook_page_num(GTK_NOTEBOOK(panels), - panelvbox); - gtk_notebook_set_page(GTK_NOTEBOOK(panels), page_num); + if (first) gtk_tree_select_child(GTK_TREE(tree), treeitem); - } - - if (nselparams >= selparamsize) { - selparamsize += 16; - selparams = sresize(selparams, selparamsize, - struct selparam); - } - selparams[nselparams].dp = &dp; - selparams[nselparams].panels = GTK_NOTEBOOK(panels); - selparams[nselparams].panel = panelvbox; - selparams[nselparams].shortcuts = scs; /* structure copy */ selparams[nselparams].treeitem = treeitem; - nselparams++; +#endif + level = j+1; + nselparams++; } w = layout_ctrls(&dp, @@ -2194,6 +2315,11 @@ int do_config_box(const char *title, Config *cfg, int midsession, } } +#if GTK_CHECK_VERSION(2,0,0) + gtk_tree_view_expand_all(GTK_TREE_VIEW(tree)); + g_signal_connect(G_OBJECT(treeselection), "changed", + G_CALLBACK(treeselection_changed), selparams); +#else dp.ntreeitems = nselparams; dp.treeitems = snewn(dp.ntreeitems, GtkWidget *); @@ -2203,12 +2329,15 @@ int do_config_box(const char *title, Config *cfg, int midsession, &selparams[index]); dp.treeitems[index] = selparams[index].treeitem; } +#endif dp.data = cfg; dlg_refresh(NULL, &dp); dp.shortcuts = &selparams[0].shortcuts; +#if !GTK_CHECK_VERSION(2,0,0) dp.currtreeitem = dp.treeitems[0]; +#endif dp.lastfocus = NULL; dp.retval = 0; dp.window = window; @@ -2223,8 +2352,10 @@ int do_config_box(const char *title, Config *cfg, int midsession, set_window_icon(window, cfg_icon, n_cfg_icon); } +#if !GTK_CHECK_VERSION(2,0,0) gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(treescroll), tree); +#endif gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(treescroll), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); @@ -2378,7 +2509,7 @@ static int string_width(char *text) GtkWidget *label = gtk_label_new(text); GtkRequisition req; gtk_widget_size_request(label, &req); - gtk_widget_unref(label); + gtk_object_sink(GTK_OBJECT(label)); return req.width; } diff --git a/unix/gtkwin.c b/unix/gtkwin.c index d0b46931..e121971b 100644 --- a/unix/gtkwin.c +++ b/unix/gtkwin.c @@ -327,7 +327,7 @@ void set_zoomed(void *frontend, int zoomed) */ #if GTK_CHECK_VERSION(2,0,0) struct gui_data *inst = (struct gui_data *)frontend; - if (iconic) + if (zoomed) gtk_window_maximize(GTK_WINDOW(inst->window)); else gtk_window_unmaximize(GTK_WINDOW(inst->window)); @@ -1347,17 +1347,13 @@ void request_resize(void *frontend, int w, int h) */ #if GTK_CHECK_VERSION(2,0,0) gtk_widget_set_size_request(inst->area, area_x, area_y); -#else - gtk_widget_set_usize(inst->area, area_x, area_y); - gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), area_x, area_y); -#endif - - gtk_container_dequeue_resize_handler(GTK_CONTAINER(inst->window)); - -#if GTK_CHECK_VERSION(2,0,0) + _gtk_container_dequeue_resize_handler(GTK_CONTAINER(inst->window)); gtk_window_resize(GTK_WINDOW(inst->window), area_x + offset_x, area_y + offset_y); #else + gtk_widget_set_usize(inst->area, area_x, area_y); + gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), area_x, area_y); + gtk_container_dequeue_resize_handler(GTK_CONTAINER(inst->window)); gdk_window_resize(inst->window->window, area_x + offset_x, area_y + offset_y); #endif @@ -2286,8 +2282,11 @@ GdkCursor *make_mouse_ptr(struct gui_data *inst, int cursor_val) return NULL; } - if (cursor_val >= 0 && !cursor_font) + if (cursor_val >= 0 && !cursor_font) { cursor_font = gdk_font_load("cursor"); + if (cursor_font) + gdk_font_ref(cursor_font); + } /* * Get the text extent of the cursor in question. We use the @@ -2776,6 +2775,7 @@ void setup_fonts_ucs(struct gui_data *inst) inst->cfg.font.name); exit(1); } + gdk_font_ref(inst->fonts[0]); font_charset = set_font_info(inst, 0); if (inst->cfg.shadowbold) { @@ -2790,6 +2790,7 @@ void setup_fonts_ucs(struct gui_data *inst) } inst->fonts[1] = name ? gdk_font_load(name) : NULL; if (inst->fonts[1]) { + gdk_font_ref(inst->fonts[1]); set_font_info(inst, 1); } else if (!guessed) { fprintf(stderr, "%s: unable to load bold font \"%s\"\n", appname, @@ -2809,6 +2810,7 @@ void setup_fonts_ucs(struct gui_data *inst) } inst->fonts[2] = name ? gdk_font_load(name) : NULL; if (inst->fonts[2]) { + gdk_font_ref(inst->fonts[2]); set_font_info(inst, 2); } else if (!guessed) { fprintf(stderr, "%s: unable to load wide font \"%s\"\n", appname, @@ -2840,6 +2842,7 @@ void setup_fonts_ucs(struct gui_data *inst) } inst->fonts[3] = name ? gdk_font_load(name) : NULL; if (inst->fonts[3]) { + gdk_font_ref(inst->fonts[3]); set_font_info(inst, 3); } else if (!guessed) { fprintf(stderr, "%s: unable to load wide/bold font \"%s\"\n", appname, @@ -2851,6 +2854,17 @@ void setup_fonts_ucs(struct gui_data *inst) } inst->font_width = gdk_char_width(inst->fonts[0], ' '); + if (!inst->font_width) { + /* Maybe this is a 16-bit font? If so, GDK 2 actually expects a + * pointer to an XChar2b. This is pretty revolting. Can Pango do + * this more neatly even for server-side fonts? + */ + XChar2b space; + space.byte1 = 0; + space.byte2 = ' '; + inst->font_width = gdk_text_width(inst->fonts[0], + (const gchar *)&space, 2); + } inst->font_height = inst->fonts[0]->ascent + inst->fonts[0]->descent; inst->direct_to_font = init_ucs(&inst->ucsdata, inst->cfg.line_codepage, From 35309b56834a81eb1a47ad96605b663237a31f0e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 25 Jan 2007 19:36:11 +0000 Subject: [PATCH 02/53] Add Colin to the licence. (I must remember to add him to the licence on the website, when we merge this back into the trunk.) [originally from svn r7158] --- LICENCE | 2 +- doc/licence.but | 4 ++-- mac/mac_res.r | 2 +- mac/macpgen.r | 2 +- unix/gtkdlg.c | 2 +- windows/pageant.rc | 2 +- windows/puttygen.rc | 2 +- windows/win_res.rc2 | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/LICENCE b/LICENCE index 1960cc2b..d14e667a 100644 --- a/LICENCE +++ b/LICENCE @@ -3,7 +3,7 @@ PuTTY is copyright 1997-2007 Simon Tatham. Portions copyright Robert de Bath, Joris van Rantwijk, Delian Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus -Kuhn, and CORE SDI S.A. +Kuhn, Colin Watson, and CORE SDI S.A. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files diff --git a/doc/licence.but b/doc/licence.but index 1e3b2561..6a71a809 100644 --- a/doc/licence.but +++ b/doc/licence.but @@ -6,8 +6,8 @@ PuTTY is \i{copyright} 1997-2007 Simon Tatham. Portions copyright Robert de Bath, Joris van Rantwijk, Delian Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, -Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus Kuhn, -and CORE SDI S.A. +Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus +Kuhn, Colin Watson, and CORE SDI S.A. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files diff --git a/mac/mac_res.r b/mac/mac_res.r index 32d3960f..c5ec94e0 100644 --- a/mac/mac_res.r +++ b/mac/mac_res.r @@ -1247,7 +1247,7 @@ resource 'TEXT' (wLicence, "licence", purgeable) { "Portions copyright Robert de Bath, Joris van Rantwijk, Delian " "Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, " "Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus " - "Kuhn, and CORE SDI S.A.\n" + "Kuhn, Colin Watson, and CORE SDI S.A.\n" "\n" "Permission is hereby granted, free of charge, to any person " "obtaining a copy of this software and associated documentation " diff --git a/mac/macpgen.r b/mac/macpgen.r index 5dafc6de..8f85b29f 100644 --- a/mac/macpgen.r +++ b/mac/macpgen.r @@ -448,7 +448,7 @@ resource 'TEXT' (wLicence, "licence", purgeable) { "Portions copyright Robert de Bath, Joris van Rantwijk, Delian " "Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, " "Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus " - "Kuhn, and CORE SDI S.A.\n" + "Kuhn, Colin Watson, and CORE SDI S.A.\n" "\n" "Permission is hereby granted, free of charge, to any person " "obtaining a copy of this software and associated documentation " diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index ae8dad48..b34fcedc 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -2661,7 +2661,7 @@ static void licence_clicked(GtkButton *button, gpointer data) "Portions copyright Robert de Bath, Joris van Rantwijk, Delian " "Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas " "Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, " - "Markus Kuhn, and CORE SDI S.A.\n\n" + "Markus Kuhn, Colin Watson, and CORE SDI S.A.\n\n" "Permission is hereby granted, free of charge, to any person " "obtaining a copy of this software and associated documentation " diff --git a/windows/pageant.rc b/windows/pageant.rc index c2b5b7cd..ea143a5b 100644 --- a/windows/pageant.rc +++ b/windows/pageant.rc @@ -62,7 +62,7 @@ BEGIN LTEXT "Portions copyright Robert de Bath, Joris van Rantwijk, Delian", 1001, 10, 26, 206, 8 LTEXT "Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas", 1002, 10, 34, 206, 8 LTEXT "Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa,", 1003, 10, 42, 206, 8 - LTEXT "Markus Kuhn, and CORE SDI S.A.", 1004, 10, 50, 206, 8 + LTEXT "Markus Kuhn, Colin Watson, and CORE SDI S.A.", 1004, 10, 50, 206, 8 LTEXT "Permission is hereby granted, free of charge, to any person", 1005, 10, 66, 206, 8 LTEXT "obtaining a copy of this software and associated documentation", 1006, 10, 74, 206, 8 diff --git a/windows/puttygen.rc b/windows/puttygen.rc index df755fee..ffdb136d 100644 --- a/windows/puttygen.rc +++ b/windows/puttygen.rc @@ -55,7 +55,7 @@ BEGIN LTEXT "Portions copyright Robert de Bath, Joris van Rantwijk, Delian", 1001, 10, 26, 206, 8 LTEXT "Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas", 1002, 10, 34, 206, 8 LTEXT "Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa,", 1003, 10, 42, 206, 8 - LTEXT "Markus Kuhn, and CORE SDI S.A.", 1004, 10, 50, 206, 8 + LTEXT "Markus Kuhn, Colin Watson, and CORE SDI S.A.", 1004, 10, 50, 206, 8 LTEXT "Permission is hereby granted, free of charge, to any person", 1005, 10, 66, 206, 8 LTEXT "obtaining a copy of this software and associated documentation", 1006, 10, 74, 206, 8 diff --git a/windows/win_res.rc2 b/windows/win_res.rc2 index be9b95e7..63951abc 100644 --- a/windows/win_res.rc2 +++ b/windows/win_res.rc2 @@ -63,7 +63,7 @@ BEGIN LTEXT "Portions copyright Robert de Bath, Joris van Rantwijk, Delian", 1001, 10, 26, 206, 8 LTEXT "Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas", 1002, 10, 34, 206, 8 LTEXT "Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa,", 1003, 10, 42, 206, 8 - LTEXT "Markus Kuhn, and CORE SDI S.A.", 1004, 10, 50, 206, 8 + LTEXT "Markus Kuhn, Colin Watson, and CORE SDI S.A.", 1004, 10, 50, 206, 8 LTEXT "Permission is hereby granted, free of charge, to any person", 1005, 10, 66, 206, 8 LTEXT "obtaining a copy of this software and associated documentation", 1006, 10, 74, 206, 8 From f46e4e380cc1933ac4ac27fd663af51ab1417aab Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 25 Jan 2007 19:44:12 +0000 Subject: [PATCH 03/53] Tweak the GTK Makefile to build with GTK2 by default, while leaving a means (`make GTK_CONFIG=gtk-config') of falling back to GTK1. [originally from svn r7159] --- mkfiles.pl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mkfiles.pl b/mkfiles.pl index ca51b765..3cc580cb 100755 --- a/mkfiles.pl +++ b/mkfiles.pl @@ -924,12 +924,18 @@ if (defined $makefiles{'gtk'}) { "# You can define this path to point at your tools if you need to\n". "# TOOLPATH = /opt/gcc/bin\n". "CC = \$(TOOLPATH)cc\n". + "# You can manually set this to `gtk-config' or `pkg-config gtk+-1.2'\n". + "# (depending on what works on your system) if you want to enforce\n". + "# building with GTK 1.2, or you can set it to `pkg-config gtk+-2.0'\n". + "# if you want to enforce 2.0. The default is to try 2.0 and fall back\n". + "# to 1.2 if it isn't found.\n". + "GTK_CONFIG = sh -c 'pkg-config gtk+-2.0 \$\$0 2>/dev/null || gtk-config \$\$0'\n". "\n". &splitline("CFLAGS = -O2 -Wall -Werror -g " . (join " ", map {"-I$dirpfx$_"} @srcdirs) . - " `gtk-config --cflags`"). + " `\$(GTK_CONFIG) --cflags`"). " -D _FILE_OFFSET_BITS=64\n". - "XLDFLAGS = `gtk-config --libs`\n". + "XLDFLAGS = `\$(GTK_CONFIG) --libs`\n". "ULDFLAGS =#\n". "INSTALL=install\n", "INSTALL_PROGRAM=\$(INSTALL)\n", From a3da28f607fc073be9368f908adf15acca726898 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 25 Jan 2007 19:45:50 +0000 Subject: [PATCH 04/53] Fix the incorrect layout of the buttons at the bottom of the main config box. [originally from svn r7160] --- unix/gtkdlg.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 70 insertions(+), 6 deletions(-) diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index b34fcedc..fc1075e8 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -2104,6 +2104,72 @@ int get_listitemheight(void) return req.height; } +void set_dialog_action_area(GtkDialog *dlg, GtkWidget *w) +{ +#if !GTK_CHECK_VERSION(2,0,0) + + /* + * In GTK 1, laying out the buttons at the bottom of the + * configuration box is nice and easy, because a GtkDialog's + * action_area is a GtkHBox which stretches to cover the full + * width of the dialog. So we just put our Columns widget + * straight into that hbox, and it ends up just where we want + * it. + */ + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->action_area), + w, TRUE, TRUE, 0); + +#else + /* + * In GTK 2, the action area is now a GtkHButtonBox and its + * layout behaviour seems to be different: it doesn't stretch + * to cover the full width of the window, but instead finds its + * own preferred width and right-aligns that within the window. + * This isn't what we want, because we have both left-aligned + * and right-aligned buttons coming out of the above call to + * layout_ctrls(), and right-aligning the whole thing will + * result in the former being centred and looking weird. + * + * So instead we abandon the dialog's action area completely: + * we gtk_widget_hide() it in the below code, and we also call + * gtk_dialog_set_has_separator() to remove the separator above + * it. We then insert our own action area into the end of the + * dialog's main vbox, and add our own separator above that. + * + * (Ideally, if we were a native GTK app, we would use the + * GtkHButtonBox's _own_ innate ability to support one set of + * buttons being right-aligned and one left-aligned. But to do + * that here, we would have to either (a) pick apart our cross- + * platform layout structures and treat them specially for this + * particular set of controls, which would be painful, or else + * (b) develop a special and simpler cross-platform + * representation for these particular controls, and introduce + * special-case code into all the _other_ platforms to handle + * it. Neither appeals. Therefore, I regretfully discard the + * GTKHButtonBox and go it alone.) + */ + + GtkWidget *align; + align = gtk_alignment_new(0, 0, 1, 1); + gtk_container_add(GTK_CONTAINER(align), w); + /* + * The purpose of this GtkAlignment is to provide padding + * around the buttons. The padding we use is twice the padding + * used in our GtkColumns, because we nest two GtkColumns most + * of the time (one separating the tree view from the main + * controls, and another for the main controls themselves). + */ + gtk_alignment_set_padding(GTK_ALIGNMENT(align), 8, 8, 8, 8); + gtk_widget_show(align); + gtk_box_pack_end(GTK_BOX(dlg->vbox), align, FALSE, TRUE, 0); + w = gtk_hseparator_new(); + gtk_box_pack_end(GTK_BOX(dlg->vbox), w, FALSE, TRUE, 0); + gtk_widget_show(w); + gtk_widget_hide(dlg->action_area); + gtk_dialog_set_has_separator(dlg, FALSE); +#endif +} + int do_config_box(const char *title, Config *cfg, int midsession, int protcfginfo) { @@ -2198,8 +2264,8 @@ int do_config_box(const char *title, Config *cfg, int midsession, if (!*s->pathname) { w = layout_ctrls(&dp, &scs, s, listitemheight, GTK_WINDOW(window)); - gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->action_area), - w, TRUE, TRUE, 0); + + set_dialog_action_area(GTK_DIALOG(window), w); } else { int j = path ? ctrl_path_compare(s->pathname, path) : 0; if (j != INT_MAX) { /* add to treeview, start new panel */ @@ -2467,8 +2533,7 @@ int messagebox(GtkWidget *parentwin, char *title, char *msg, int minwid, ...) window = gtk_dialog_new(); gtk_window_set_title(GTK_WINDOW(window), title); w0 = layout_ctrls(&dp, &scs, s0, 0, GTK_WINDOW(window)); - gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->action_area), - w0, TRUE, TRUE, 0); + set_dialog_action_area(GTK_DIALOG(window), w0); gtk_widget_show(w0); w1 = layout_ctrls(&dp, &scs, s1, 0, GTK_WINDOW(window)); gtk_container_set_border_width(GTK_CONTAINER(w1), 10); @@ -2913,8 +2978,7 @@ void showeventlog(void *estuff, void *parentwin) sfree(title); w0 = layout_ctrls(&es->dp, &es->scs, s0, listitemheight, GTK_WINDOW(window)); - gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->action_area), - w0, TRUE, TRUE, 0); + set_dialog_action_area(GTK_DIALOG(window), w0); gtk_widget_show(w0); w1 = layout_ctrls(&es->dp, &es->scs, s1, listitemheight, GTK_WINDOW(window)); From ac7870a63530a4d548bb786bbca529d47a73178c Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 25 Jan 2007 19:47:36 +0000 Subject: [PATCH 05/53] A nasty GTK signal cascade was causing any edit box whose contents was modified on session load to be blanked. More details in the comment in dlg_editbox_set(). [originally from svn r7161] --- unix/gtkdlg.c | 69 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index fc1075e8..b4e06f1d 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -56,6 +56,7 @@ struct uctrl { GtkWidget *text; /* for text */ GtkWidget *label; /* for dlg_label_change */ GtkAdjustment *adj; /* for the scrollbar in a list box */ + guint entrysig; guint textsig; }; @@ -102,12 +103,12 @@ static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event, static void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw, int chr, int action, void *ptr); static void shortcut_highlight(GtkWidget *label, int chr); -static int listitem_single_key(GtkWidget *item, GdkEventKey *event, - gpointer data); -static int listitem_multi_key(GtkWidget *item, GdkEventKey *event, - gpointer data); -static int listitem_button(GtkWidget *item, GdkEventButton *event, - gpointer data); +static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event, + gpointer data); +static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event, + gpointer data); +static gboolean listitem_button(GtkWidget *item, GdkEventButton *event, + gpointer data); static void menuitem_activate(GtkMenuItem *item, gpointer data); static void coloursel_ok(GtkButton *button, gpointer data); static void coloursel_cancel(GtkButton *button, gpointer data); @@ -300,7 +301,27 @@ void dlg_editbox_set(union control *ctrl, void *dlg, char const *text) struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_EDITBOX); assert(uc->entry != NULL); + /* + * GTK 2 implements gtk_entry_set_text by means of two separate + * operations: first delete the previous text leaving the empty + * string, then insert the new text. This causes two calls to + * the "changed" signal. + * + * The first call to "changed", if allowed to proceed normally, + * will cause an EVENT_VALCHANGE event on the edit box, causing + * a call to dlg_editbox_get() which will read the empty string + * out of the GtkEntry - and promptly write it straight into + * the Config structure, which is precisely where our `text' + * pointer is probably pointing, so the second editing + * operation will insert that instead of the string we + * originally asked for. + * + * Hence, we must block our "changed" signal handler for the + * duration of this call to gtk_entry_set_text. + */ + g_signal_handler_block(uc->entry, uc->entrysig); gtk_entry_set_text(GTK_ENTRY(uc->entry), text); + g_signal_handler_unblock(uc->entry, uc->entrysig); } void dlg_editbox_get(union control *ctrl, void *dlg, char *buffer, int length) @@ -963,7 +984,8 @@ static void button_toggled(GtkToggleButton *tb, gpointer data) uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE); } -static int editbox_key(GtkWidget *widget, GdkEventKey *event, gpointer data) +static gboolean editbox_key(GtkWidget *widget, GdkEventKey *event, + gpointer data) { /* * GtkEntry has a nasty habit of eating the Return key, which @@ -975,7 +997,7 @@ static int editbox_key(GtkWidget *widget, GdkEventKey *event, gpointer data) * in the dialog just like it will everywhere else. */ if (event->keyval == GDK_Return && widget->parent != NULL) { - gint return_val; + gboolean return_val; gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event"); gtk_signal_emit_by_name(GTK_OBJECT(widget->parent), "key_press_event", event, &return_val); @@ -993,16 +1015,17 @@ static void editbox_changed(GtkEditable *ed, gpointer data) } } -static void editbox_lostfocus(GtkWidget *ed, GdkEventFocus *event, - gpointer data) +static gboolean editbox_lostfocus(GtkWidget *ed, GdkEventFocus *event, + gpointer data) { struct dlgparam *dp = (struct dlgparam *)data; struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(ed)); uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_REFRESH); + return FALSE; } -static int listitem_key(GtkWidget *item, GdkEventKey *event, gpointer data, - int multiple) +static gboolean listitem_key(GtkWidget *item, GdkEventKey *event, + gpointer data, int multiple) { GtkAdjustment *adj = GTK_ADJUSTMENT(data); @@ -1095,20 +1118,20 @@ static int listitem_key(GtkWidget *item, GdkEventKey *event, gpointer data, return FALSE; } -static int listitem_single_key(GtkWidget *item, GdkEventKey *event, - gpointer data) +static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event, + gpointer data) { return listitem_key(item, event, data, FALSE); } -static int listitem_multi_key(GtkWidget *item, GdkEventKey *event, - gpointer data) +static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event, + gpointer data) { return listitem_key(item, event, data, TRUE); } -static int listitem_button(GtkWidget *item, GdkEventButton *event, - gpointer data) +static gboolean listitem_button(GtkWidget *item, GdkEventButton *event, + gpointer data) { struct dlgparam *dp = (struct dlgparam *)data; if (event->type == GDK_2BUTTON_PRESS || @@ -1500,8 +1523,9 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, gtk_entry_set_visibility(GTK_ENTRY(w), FALSE); uc->entry = w; } - gtk_signal_connect(GTK_OBJECT(uc->entry), "changed", - GTK_SIGNAL_FUNC(editbox_changed), dp); + uc->entrysig = + gtk_signal_connect(GTK_OBJECT(uc->entry), "changed", + GTK_SIGNAL_FUNC(editbox_changed), dp); gtk_signal_connect(GTK_OBJECT(uc->entry), "key_press_event", GTK_SIGNAL_FUNC(editbox_key), dp); gtk_signal_connect(GTK_OBJECT(uc->entry), "focus_in_event", @@ -1592,8 +1616,9 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, gtk_signal_connect(GTK_OBJECT(uc->entry), "key_press_event", GTK_SIGNAL_FUNC(editbox_key), dp); - gtk_signal_connect(GTK_OBJECT(uc->entry), "changed", - GTK_SIGNAL_FUNC(editbox_changed), dp); + uc->entrysig = + gtk_signal_connect(GTK_OBJECT(uc->entry), "changed", + GTK_SIGNAL_FUNC(editbox_changed), dp); gtk_signal_connect(GTK_OBJECT(uc->entry), "focus_in_event", GTK_SIGNAL_FUNC(widget_focus), dp); gtk_signal_connect(GTK_OBJECT(uc->button), "focus_in_event", From c5953f16807ece1ce88af1f4182c561cbca6049c Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 25 Jan 2007 19:56:05 +0000 Subject: [PATCH 06/53] Begin tracking a TODO list for this branch. [originally from svn r7162] --- unix/GTK2.TODO | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 unix/GTK2.TODO diff --git a/unix/GTK2.TODO b/unix/GTK2.TODO new file mode 100644 index 00000000..b2d122fb --- /dev/null +++ b/unix/GTK2.TODO @@ -0,0 +1,48 @@ +TODO for PuTTY GTK2 port before merging back into main trunk code +----------------------------------------------------------------- + +Items from Colin's original mail: + + - Font handling is the biggie. Current problems with it: + * The GTK2 font selection dialog only mentions client-side + fonts, but the actual text display code can't cope with them. + + Clearly one or the other needs to be fixed: the font + selection dialog certainly needs to agree with the fonts + actually available in the program. + + I want to keep being able to use my server-side fonts. + + People used to GTK2 applications will probably want to use + their client-side fonts. + * Also, the GTK2 font selection dialog doesn't allow filtering + to monospaced fonts only (and gnome-terminal, for example, + just has to cope if the user selects a proportional font). + + We can live with this problem if we really have to, but + it'd be nice not to have to. + * Colin's idea is that we should simply cook up a font selection + dialog entirely of our own, which handles both client- _and_ + server-side fonts, and correspondingly soup up the text + display code to deal with whichever it's given (if necessary + by switching between two totally separate pieces of code). + This sounds like a sensible plan to me, or at least the most + sensible response to a generally insane situation. + + - The call to _gtk_container_dequeue_resize_handler wants + revisiting, and preferably removing in favour of a cleaner way to + do the job. + + - gtkcols.c is currently a minimal-work GTK2 port of my original + GTK1 implementation. Someone should go through it and compare it + to a real GTK2 container class, to make sure there aren't any + large chunks we should have reimplemented and haven't, or indeed + that we shouldn't have reimplemented and have. + + - Uses of GtkList should be replaced with the non-deprecated + GtkTreeView. + +Other items: + + - The host key security alert dialog box is coming up the right + size but totally blank! Find out why and fix it. + +At point of merge: + + - Mention Colin in the website's licence page. From 5d3306f98d986ec42cae0c22b0dd82626e63ecb9 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 25 Jan 2007 19:59:55 +0000 Subject: [PATCH 07/53] Another TODO item. (There's always one.) [originally from svn r7163] --- unix/GTK2.TODO | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/unix/GTK2.TODO b/unix/GTK2.TODO index b2d122fb..be34c96b 100644 --- a/unix/GTK2.TODO +++ b/unix/GTK2.TODO @@ -43,6 +43,11 @@ Other items: - The host key security alert dialog box is coming up the right size but totally blank! Find out why and fix it. + - Since Colin's patch was originally prepared I committed r7117 + (fold up treeview branches at depth 2 or greater), and this is + not currently replicated in the GTK2 version of the treeview + code. It should be. + At point of merge: - Mention Colin in the website's licence page. From 65f9735b95cc8bbf5e7e23f165c01fd3cddf9ec5 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 26 Jan 2007 07:28:55 +0000 Subject: [PATCH 08/53] Stop calling gdk_input_add() with a zero flags word. If we don't want to know about any input events on a socket, it's simpler not to call gdk_input_add() on it at all. I hesitate to say `fixes', but ... this change _causes to go away_ the weird problem I had with blank host key dialogs. I have no understanding of the chain of cause and effect between gdk_input_add with zero flags and missing redraw events, but it seems like a change I should make anyway, so I'm going to do so and hope the problem doesn't come back :-/ [originally from svn r7164] --- unix/GTK2.TODO | 3 --- unix/gtkwin.c | 8 ++++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/unix/GTK2.TODO b/unix/GTK2.TODO index be34c96b..1f1c8b0e 100644 --- a/unix/GTK2.TODO +++ b/unix/GTK2.TODO @@ -40,9 +40,6 @@ Items from Colin's original mail: Other items: - - The host key security alert dialog box is coming up the right - size but totally blank! Find out why and fix it. - - Since Colin's patch was originally prepared I committed r7117 (fold up treeview branches at depth 2 or greater), and this is not currently replicated in the GTK2 version of the treeview diff --git a/unix/gtkwin.c b/unix/gtkwin.c index e121971b..c615367a 100644 --- a/unix/gtkwin.c +++ b/unix/gtkwin.c @@ -2695,11 +2695,15 @@ int uxsel_input_add(int fd, int rwx) { if (rwx & 1) flags |= GDK_INPUT_READ; if (rwx & 2) flags |= GDK_INPUT_WRITE; if (rwx & 4) flags |= GDK_INPUT_EXCEPTION; - return gdk_input_add(fd, flags, fd_input_func, NULL); + if (flags) + return gdk_input_add(fd, flags, fd_input_func, NULL); + else + return -1; } void uxsel_input_remove(int id) { - gdk_input_remove(id); + if (id > 0) + gdk_input_remove(id); } char *guess_derived_font_name(GdkFont *font, int bold, int wide) From 6d81ee9cc0e62cddf339b5e4030d5edeca192047 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 26 Jan 2007 08:01:47 +0000 Subject: [PATCH 09/53] Collapse tree view branches deeper than level 2, bringing the GTK2 tree code into line with the GTK1. [originally from svn r7165] --- unix/GTK2.TODO | 7 ------- unix/gtkdlg.c | 17 ++++++++++++++++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/unix/GTK2.TODO b/unix/GTK2.TODO index 1f1c8b0e..7eb2605e 100644 --- a/unix/GTK2.TODO +++ b/unix/GTK2.TODO @@ -38,13 +38,6 @@ Items from Colin's original mail: - Uses of GtkList should be replaced with the non-deprecated GtkTreeView. -Other items: - - - Since Colin's patch was originally prepared I committed r7117 - (fold up treeview branches at depth 2 or greater), and this is - not currently replicated in the GTK2 version of the treeview - code. It should be. - At point of merge: - Mention Colin in the website's licence page. diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index b4e06f1d..1c31ed34 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -2362,6 +2362,22 @@ int do_config_box(const char *title, Config *cfg, int midsession, TREESTORE_PARAMS, nselparams, -1); treeiterlevels[j] = treeiter; + + treeindices[j]++; + treeindices[j+1] = -1; + + if (j > 0) { + GtkTreePath *path; + + path = gtk_tree_model_get_path(GTK_TREE_MODEL(treestore), + &treeiterlevels[j-1]); + if (j < 2) + gtk_tree_view_expand_row(GTK_TREE_VIEW(tree), path, + FALSE); + else + gtk_tree_view_collapse_row(GTK_TREE_VIEW(tree), path); + gtk_tree_path_free(path); + } #else treeitem = gtk_tree_item_new_with_label(c); if (j > 0) { @@ -2407,7 +2423,6 @@ int do_config_box(const char *title, Config *cfg, int midsession, } #if GTK_CHECK_VERSION(2,0,0) - gtk_tree_view_expand_all(GTK_TREE_VIEW(tree)); g_signal_connect(G_OBJECT(treeselection), "changed", G_CALLBACK(treeselection_changed), selparams); #else From 13ad541cb5c7f54a5eb8b781dcea1f48c9f05896 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 26 Jan 2007 08:03:07 +0000 Subject: [PATCH 10/53] Er, whoops. Remove two lines from a previous attempt at r7165, which broke the build. Ahem. [originally from svn r7166] [r7165 == 6d81ee9cc0e62cddf339b5e4030d5edeca192047] --- unix/gtkdlg.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index 1c31ed34..8b349f88 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -2363,9 +2363,6 @@ int do_config_box(const char *title, Config *cfg, int midsession, -1); treeiterlevels[j] = treeiter; - treeindices[j]++; - treeindices[j+1] = -1; - if (j > 0) { GtkTreePath *path; From 0ed390d44a6243edf7847c2b50ff1f59a025f07b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 26 Jan 2007 20:00:32 +0000 Subject: [PATCH 11/53] Changed my mind about r7164. Instead of checking for zero flags inside one single uxsel front end, better to do it centrally and avoid passing zero flags on to the front end in the first place. I'm sure other similarly structured front ends could get confused by it too. [originally from svn r7171] [r7164 == 65f9735b95cc8bbf5e7e23f165c01fd3cddf9ec5] --- unix/gtkwin.c | 9 +++------ unix/uxsel.c | 22 +++++++++------------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/unix/gtkwin.c b/unix/gtkwin.c index c615367a..3743672a 100644 --- a/unix/gtkwin.c +++ b/unix/gtkwin.c @@ -2695,15 +2695,12 @@ int uxsel_input_add(int fd, int rwx) { if (rwx & 1) flags |= GDK_INPUT_READ; if (rwx & 2) flags |= GDK_INPUT_WRITE; if (rwx & 4) flags |= GDK_INPUT_EXCEPTION; - if (flags) - return gdk_input_add(fd, flags, fd_input_func, NULL); - else - return -1; + assert(flags); + return gdk_input_add(fd, flags, fd_input_func, NULL); } void uxsel_input_remove(int id) { - if (id > 0) - gdk_input_remove(id); + gdk_input_remove(id); } char *guess_derived_font_name(GdkFont *font, int bold, int wide) diff --git a/unix/uxsel.c b/unix/uxsel.c index 0383faa6..e2979c9a 100644 --- a/unix/uxsel.c +++ b/unix/uxsel.c @@ -62,22 +62,18 @@ void uxsel_init(void) void uxsel_set(int fd, int rwx, uxsel_callback_fn callback) { - struct fd *newfd = snew(struct fd); - struct fd *oldfd; + struct fd *newfd; - newfd->fd = fd; - newfd->rwx = rwx; - newfd->callback = callback; + uxsel_del(fd); - oldfd = find234(fds, newfd, NULL); - if (oldfd) { - uxsel_input_remove(oldfd->id); - del234(fds, oldfd); - sfree(oldfd); + if (rwx) { + newfd = snew(struct fd); + newfd->fd = fd; + newfd->rwx = rwx; + newfd->callback = callback; + newfd->id = uxsel_input_add(fd, rwx); + add234(fds, newfd); } - - add234(fds, newfd); - newfd->id = uxsel_input_add(fd, rwx); } void uxsel_del(int fd) From 1d829f97cc49ad94c25b11f356302add9eba6be0 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 27 Jan 2007 17:21:06 +0000 Subject: [PATCH 12/53] Move the TODO items from the top of gtkdlg.c into the main TODO file. [originally from svn r7173] --- unix/GTK2.TODO | 7 +++++-- unix/gtkdlg.c | 9 --------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/unix/GTK2.TODO b/unix/GTK2.TODO index 7eb2605e..b6999956 100644 --- a/unix/GTK2.TODO +++ b/unix/GTK2.TODO @@ -1,7 +1,7 @@ TODO for PuTTY GTK2 port before merging back into main trunk code ----------------------------------------------------------------- -Items from Colin's original mail: +Things to do before deciding a merge is feasible: - Font handling is the biggie. Current problems with it: * The GTK2 font selection dialog only mentions client-side @@ -38,6 +38,9 @@ Items from Colin's original mail: - Uses of GtkList should be replaced with the non-deprecated GtkTreeView. -At point of merge: + - Investigate the shortcut mechanism in GTK2's GtkLabel, and see if + it's worth switching to it from the current ad-hockery. + +Things to do at point of actual merge: - Mention Colin in the website's licence page. diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index 8b349f88..966164ac 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -2,15 +2,6 @@ * gtkdlg.c - GTK implementation of the PuTTY configuration box. */ -/* - * TODO when porting to GTK 2.0: - * - * - GtkList is deprecated and we should switch to GtkTreeView instead - * (done for GtkTree). - * - GtkLabel has a built-in mnemonic scheme, so we should at - * least consider switching to that from the current adhockery. - */ - #include #include #include From 12e019bafc75cb441e965c63e15dfceeaf71ca1e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 27 Jan 2007 17:47:48 +0000 Subject: [PATCH 13/53] Better not forget to make sure GTK1 doesn't break. [originally from svn r7174] --- unix/GTK2.TODO | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/unix/GTK2.TODO b/unix/GTK2.TODO index b6999956..f3caf82f 100644 --- a/unix/GTK2.TODO +++ b/unix/GTK2.TODO @@ -41,6 +41,10 @@ Things to do before deciding a merge is feasible: - Investigate the shortcut mechanism in GTK2's GtkLabel, and see if it's worth switching to it from the current ad-hockery. +Things to do once GTK2 development is complete: + + - Make sure we haven't broken GTK1. + Things to do at point of actual merge: - Mention Colin in the website's licence page. From 71d802bdb6a1885f01210ac18f9d656120f087c5 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 22 Mar 2008 11:40:23 +0000 Subject: [PATCH 14/53] Refactor the font handling code: I've moved all the code that explicitly deals with GdkFont out into a new module, behind a polymorphic interface (done by ad-hoc explicit vtable management in C). This should allow me to drop in a Pango font handling module in parallel with the existing one, meaning that GTK2 PuTTY will be able to seamlessly switch between X11 server-side fonts and Pango client- side ones as the user chooses, or even use a mixture of the two (e.g. an X11 font for narrow characters and a Pango one for wide characters, or vice versa). In the process, incidentally, I got to the bottom of the `weird bug' mentioned in the old do_text_internal(). It's not a bug in gdk_draw_text_wc() as I had thought: it's simply that GdkWChar is a 32-bit type rather than a 16-bit one, so no wonder you have to specify twice the length to find all the characters in the string! However, there _is_ a bug in GTK2's gdk_draw_text_wc(), which causes it to strip off everything above the low byte of each GdkWChar, sigh. Solution to both problems is to use an array of the underlying Xlib type XChar2b instead, and pass it to gdk_draw_text() cast to gchar *. Grotty, but it works. (And it'll become significantly less grotty if and when we have to stop using the GDK font handling wrappers in favour of going direct to Xlib.) [originally from svn r7933] --- Recipe | 2 +- unix/gtkfont.c | 435 +++++++++++++++++++++++++++++++++++++++++++++++++ unix/gtkfont.h | 46 ++++++ unix/gtkwin.c | 359 +++++++++------------------------------- 4 files changed, 559 insertions(+), 283 deletions(-) create mode 100644 unix/gtkfont.c create mode 100644 unix/gtkfont.h diff --git a/Recipe b/Recipe index fab512bc..8854c402 100644 --- a/Recipe +++ b/Recipe @@ -245,7 +245,7 @@ GUITERM = TERMINAL window windlg winctrls sizetip winucs winprint # Same thing on Unix. UXTERM = TERMINAL uxcfg sercfg uxucs uxprint timing -GTKTERM = UXTERM gtkwin gtkcfg gtkdlg gtkcols xkeysym +GTKTERM = UXTERM gtkwin gtkcfg gtkdlg gtkfont gtkcols xkeysym OSXTERM = UXTERM osxwin osxdlg osxctrls # Non-SSH back ends (putty, puttytel, plink). diff --git a/unix/gtkfont.c b/unix/gtkfont.c new file mode 100644 index 00000000..8fabdd23 --- /dev/null +++ b/unix/gtkfont.c @@ -0,0 +1,435 @@ +/* + * Unified font management for GTK. + * + * PuTTY is willing to use both old-style X server-side bitmap + * fonts _and_ GTK2/Pango client-side fonts. This requires us to + * do a bit of work to wrap the two wildly different APIs into + * forms the rest of the code can switch between seamlessly, and + * also requires a custom font selector capable of handling both + * types of font. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "gtkfont.h" + +/* + * To do: + * + * - import flags to do VT100 double-width, and import the icky + * pixmap stretch code for it. + * + * - add the Pango back end! + */ + +/* + * Future work: + * + * - all the GDK font functions used in the x11font subclass are + * deprecated, so one day they may go away. When this happens - + * or before, if I'm feeling proactive - it oughtn't to be too + * difficult in principle to convert the whole thing to use + * actual Xlib font calls. + */ + +/* + * Ad-hoc vtable mechanism to allow font structures to be + * polymorphic. + * + * Any instance of `unifont' used in the vtable functions will + * actually be the first element of a larger structure containing + * data specific to the subtype. This is permitted by the ISO C + * provision that one may safely cast between a pointer to a + * structure and a pointer to its first element. + */ + +struct unifont_vtable { + /* + * `Methods' of the `class'. + */ + unifont *(*create)(char *name, int wide, int bold, + int shadowoffset, int shadowalways); + void (*destroy)(unifont *font); + void (*draw_text)(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const char *string, int len, int wide, + int bold); + /* + * `Static data members' of the `class'. + */ + const char *prefix; +}; + +/* ---------------------------------------------------------------------- + * GDK-based X11 font implementation. + */ + +static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const char *string, int len, + int wide, int bold); +static unifont *x11font_create(char *name, int wide, int bold, + int shadowoffset, int shadowalways); +static void x11font_destroy(unifont *font); + +struct x11font { + struct unifont u; + /* + * Actual font objects. We store a number of these, for + * automatically guessed bold and wide variants. + * + * The parallel array `allocated' indicates whether we've + * tried to fetch a subfont already (thus distinguishing NULL + * because we haven't tried yet from NULL because we tried and + * failed, so that we don't keep trying and failing + * subsequently). + */ + GdkFont *fonts[4]; + int allocated[4]; + /* + * `sixteen_bit' is true iff the font object is indexed by + * values larger than a byte. That is, this flag tells us + * whether we use gdk_draw_text_wc() or gdk_draw_text(). + */ + int sixteen_bit; + /* + * Font charsets. public_charset and real_charset can differ + * for X11 fonts, because many X fonts use CS_ISO8859_1_X11. + */ + int public_charset, real_charset; + /* + * Data passed in to unifont_create(). + */ + int wide, bold, shadowoffset, shadowalways; +}; + +static const struct unifont_vtable x11font_vtable = { + x11font_create, + x11font_destroy, + x11font_draw_text, + "x11" +}; + +char *x11_guess_derived_font_name(GdkFont *font, int bold, int wide) +{ + XFontStruct *xfs = GDK_FONT_XFONT(font); + Display *disp = GDK_FONT_XDISPLAY(font); + Atom fontprop = XInternAtom(disp, "FONT", False); + unsigned long ret; + if (XGetFontProperty(xfs, fontprop, &ret)) { + char *name = XGetAtomName(disp, (Atom)ret); + if (name && name[0] == '-') { + char *strings[13]; + char *dupname, *extrafree = NULL, *ret; + char *p, *q; + int nstr; + + p = q = dupname = dupstr(name); /* skip initial minus */ + nstr = 0; + + while (*p && nstr < lenof(strings)) { + if (*p == '-') { + *p = '\0'; + strings[nstr++] = p+1; + } + p++; + } + + if (nstr < lenof(strings)) + return NULL; /* XLFD was malformed */ + + if (bold) + strings[2] = "bold"; + + if (wide) { + /* 4 is `wideness', which obviously may have changed. */ + /* 5 is additional style, which may be e.g. `ja' or `ko'. */ + strings[4] = strings[5] = "*"; + strings[11] = extrafree = dupprintf("%d", 2*atoi(strings[11])); + } + + ret = dupcat("-", strings[ 0], "-", strings[ 1], "-", strings[ 2], + "-", strings[ 3], "-", strings[ 4], "-", strings[ 5], + "-", strings[ 6], "-", strings[ 7], "-", strings[ 8], + "-", strings[ 9], "-", strings[10], "-", strings[11], + "-", strings[12], NULL); + sfree(extrafree); + sfree(dupname); + + return ret; + } + } + return NULL; +} + +static int x11_font_width(GdkFont *font, int sixteen_bit) +{ + if (sixteen_bit) { + XChar2b space; + space.byte1 = 0; + space.byte2 = ' '; + return gdk_text_width(font, (const gchar *)&space, 2); + } else { + return gdk_char_width(font, ' '); + } +} + +static unifont *x11font_create(char *name, int wide, int bold, + int shadowoffset, int shadowalways) +{ + struct x11font *xfont; + GdkFont *font; + XFontStruct *xfs; + Display *disp; + Atom charset_registry, charset_encoding; + unsigned long registry_ret, encoding_ret; + int pubcs, realcs, sixteen_bit; + int i; + + font = gdk_font_load(name); + if (!font) + return NULL; + + xfs = GDK_FONT_XFONT(font); + disp = GDK_FONT_XDISPLAY(font); + + charset_registry = XInternAtom(disp, "CHARSET_REGISTRY", False); + charset_encoding = XInternAtom(disp, "CHARSET_ENCODING", False); + + pubcs = realcs = CS_NONE; + sixteen_bit = FALSE; + + if (XGetFontProperty(xfs, charset_registry, ®istry_ret) && + XGetFontProperty(xfs, charset_encoding, &encoding_ret)) { + char *reg, *enc; + reg = XGetAtomName(disp, (Atom)registry_ret); + enc = XGetAtomName(disp, (Atom)encoding_ret); + if (reg && enc) { + char *encoding = dupcat(reg, "-", enc, NULL); + pubcs = realcs = charset_from_xenc(encoding); + + /* + * iso10646-1 is the only wide font encoding we + * support. In this case, we expect clients to give us + * UTF-8, which this module must internally convert + * into 16-bit Unicode. + */ + if (!strcasecmp(encoding, "iso10646-1")) { + sixteen_bit = TRUE; + pubcs = realcs = CS_UTF8; + } + + /* + * Hack for X line-drawing characters: if the primary + * font is encoded as ISO-8859-1, and has valid glyphs + * in the first 32 char positions, it is assumed that + * those glyphs are the VT100 line-drawing character + * set. + * + * Actually, we'll hack even harder by only checking + * position 0x19 (vertical line, VT100 linedrawing + * `x'). Then we can check it easily by seeing if the + * ascent and descent differ. + */ + if (pubcs == CS_ISO8859_1) { + int lb, rb, wid, asc, desc; + gchar text[2]; + + text[1] = '\0'; + text[0] = '\x12'; + gdk_string_extents(font, text, &lb, &rb, &wid, &asc, &desc); + if (asc != desc) + realcs = CS_ISO8859_1_X11; + } + + sfree(encoding); + } + } + + xfont = snew(struct x11font); + xfont->u.vt = &x11font_vtable; + xfont->u.width = x11_font_width(font, sixteen_bit); + xfont->u.ascent = font->ascent; + xfont->u.descent = font->descent; + xfont->u.height = xfont->u.ascent + xfont->u.descent; + xfont->u.public_charset = pubcs; + xfont->u.real_charset = realcs; + xfont->fonts[0] = font; + xfont->allocated[0] = TRUE; + xfont->sixteen_bit = sixteen_bit; + xfont->wide = wide; + xfont->bold = bold; + xfont->shadowoffset = shadowoffset; + xfont->shadowalways = shadowalways; + + for (i = 1; i < lenof(xfont->fonts); i++) { + xfont->fonts[i] = NULL; + xfont->allocated[i] = FALSE; + } + + return (unifont *)xfont; +} + +static void x11font_destroy(unifont *font) +{ + struct x11font *xfont = (struct x11font *)font; + int i; + + for (i = 0; i < lenof(xfont->fonts); i++) + if (xfont->fonts[i]) + gdk_font_unref(xfont->fonts[i]); + sfree(font); +} + +static void x11_alloc_subfont(struct x11font *xfont, int sfid) +{ + char *derived_name = x11_guess_derived_font_name + (xfont->fonts[0], sfid & 1, !!(sfid & 2)); + xfont->fonts[sfid] = gdk_font_load(derived_name); /* may be NULL */ + xfont->allocated[sfid] = TRUE; + sfree(derived_name); +} + +static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const char *string, int len, + int wide, int bold) +{ + struct x11font *xfont = (struct x11font *)font; + int sfid; + int shadowbold = FALSE; + + wide -= xfont->wide; + bold -= xfont->bold; + + /* + * Decide which subfont we're using, and whether we have to + * use shadow bold. + */ + if (xfont->shadowalways && bold) { + shadowbold = TRUE; + bold = 0; + } + sfid = 2 * wide + bold; + if (!xfont->allocated[sfid]) + x11_alloc_subfont(xfont, sfid); + if (bold && !xfont->fonts[sfid]) { + bold = 0; + shadowbold = TRUE; + sfid = 2 * wide + bold; + if (!xfont->allocated[sfid]) + x11_alloc_subfont(xfont, sfid); + } + + if (!xfont->fonts[sfid]) + return; /* we've tried our best, but no luck */ + + if (xfont->sixteen_bit) { + /* + * This X font has 16-bit character indices, which means + * we expect our string to have been passed in UTF-8. + */ + XChar2b *xcs; + wchar_t *wcs; + int nchars, maxchars, i; + + /* + * Convert the input string to wide-character Unicode. + */ + maxchars = 0; + for (i = 0; i < len; i++) + if ((unsigned char)string[i] <= 0x7F || + (unsigned char)string[i] >= 0xC0) + maxchars++; + wcs = snewn(maxchars+1, wchar_t); + nchars = charset_to_unicode((char **)&string, &len, wcs, maxchars, + CS_UTF8, NULL, NULL, 0); + assert(nchars <= maxchars); + wcs[nchars] = L'\0'; + + xcs = snewn(nchars, XChar2b); + for (i = 0; i < nchars; i++) { + xcs[i].byte1 = wcs[i] >> 8; + xcs[i].byte2 = wcs[i]; + } + + gdk_draw_text(target, xfont->fonts[sfid], gc, + x, y, (gchar *)xcs, nchars*2); + if (shadowbold) + gdk_draw_text(target, xfont->fonts[sfid], gc, + x + xfont->shadowoffset, y, (gchar *)xcs, nchars*2); + sfree(xcs); + sfree(wcs); + } else { + gdk_draw_text(target, xfont->fonts[sfid], gc, x, y, string, len); + if (shadowbold) + gdk_draw_text(target, xfont->fonts[sfid], gc, + x + xfont->shadowoffset, y, string, len); + } +} + +/* ---------------------------------------------------------------------- + * Outermost functions which do the vtable dispatch. + */ + +/* + * This function is the only one which needs to know the full set + * of font implementations available, because it has to try each + * in turn until one works, or condition on an override prefix in + * the font name. + */ +static const struct unifont_vtable *unifont_types[] = { + &x11font_vtable, +}; +unifont *unifont_create(char *name, int wide, int bold, + int shadowoffset, int shadowalways) +{ + int colonpos = strcspn(name, ":"); + int i; + + if (name[colonpos]) { + /* + * There's a colon prefix on the font name. Use it to work + * out which subclass to try to create. + */ + for (i = 0; i < lenof(unifont_types); i++) { + if (strlen(unifont_types[i]->prefix) == colonpos && + !strncmp(unifont_types[i]->prefix, name, colonpos)) + break; + } + if (i == lenof(unifont_types)) + return NULL; /* prefix not recognised */ + return unifont_types[i]->create(name+colonpos+1, wide, bold, + shadowoffset, shadowalways); + } else { + /* + * No colon prefix, so just go through all the subclasses. + */ + for (i = 0; i < lenof(unifont_types); i++) { + unifont *ret = unifont_types[i]->create(name, wide, bold, + shadowoffset, + shadowalways); + if (ret) + return ret; + } + return NULL; /* font not found in any scheme */ + } +} + +void unifont_destroy(unifont *font) +{ + font->vt->destroy(font); +} + +void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const char *string, int len, + int wide, int bold) +{ + font->vt->draw_text(target, gc, font, x, y, string, len, wide, bold); +} diff --git a/unix/gtkfont.h b/unix/gtkfont.h new file mode 100644 index 00000000..9cb66783 --- /dev/null +++ b/unix/gtkfont.h @@ -0,0 +1,46 @@ +/* + * Header file for gtkfont.c. Has to be separate from unix.h + * because it depends on GTK data types, hence can't be included + * from cross-platform code (which doesn't go near GTK). + */ + +#ifndef PUTTY_GTKFONT_H +#define PUTTY_GTKFONT_H + +/* + * Exports from gtkfont.c. + */ +struct unifont_vtable; /* contents internal to gtkfont.c */ +typedef struct unifont { + const struct unifont_vtable *vt; + /* + * `Non-static data members' of the `class', accessible to + * external code. + */ + + /* + * public_charset is the charset used when the user asks for + * `Use font encoding'. + * + * real_charset is the charset used when translating text into + * a form suitable for sending to unifont_draw_text(). + * + * They can differ. For example, public_charset might be + * CS_ISO8859_1 while real_charset is CS_ISO8859_1_X11. + */ + int public_charset, real_charset; + + /* + * Font dimensions needed by clients. + */ + int width, height, ascent, descent; +} unifont; + +unifont *unifont_create(char *name, int wide, int bold, + int shadowoffset, int shadowalways); +void unifont_destroy(unifont *font); +void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const char *string, int len, + int wide, int bold); + +#endif /* PUTTY_GTKFONT_H */ diff --git a/unix/gtkwin.c b/unix/gtkwin.c index e6ff13c3..2972dba1 100644 --- a/unix/gtkwin.c +++ b/unix/gtkwin.c @@ -28,6 +28,7 @@ #include "putty.h" #include "terminal.h" +#include "gtkfont.h" #define CAT2(x,y) x ## y #define CAT(x,y) CAT2(x,y) @@ -58,11 +59,7 @@ struct gui_data { *restartitem; GtkWidget *sessionsmenu; GdkPixmap *pixmap; - GdkFont *fonts[4]; /* normal, bold, wide, widebold */ - struct { - int charset; - int is_wide; - } fontinfo[4]; + unifont *fonts[4]; /* normal, bold, wide, widebold */ int xpos, ypos, gotpos, gravity; GdkCursor *rawcursor, *textcursor, *blankcursor, *waitcursor, *currcursor; GdkColor cols[NALLCOLOURS]; @@ -1880,6 +1877,8 @@ int char_width(Context ctx, int uc) * Under X, any fixed-width font really _is_ fixed-width. * Double-width characters will be dealt with using a separate * font. For the moment we can simply return 1. + * + * FIXME: but is that also true of Pango? */ return 1; } @@ -1920,7 +1919,7 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, struct gui_data *inst = dctx->inst; GdkGC *gc = dctx->gc; int ncombining, combining; - int nfg, nbg, t, fontid, shadow, rlen, widefactor; + int nfg, nbg, t, fontid, shadow, rlen, widefactor, bold; int monochrome = gtk_widget_get_visual(inst->area)->depth == 1; if (attr & TATTR_COMBINING) { @@ -1959,10 +1958,27 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, } if ((attr & ATTR_BOLD) && !inst->cfg.bold_colour) { - if (inst->fonts[fontid | 1]) - fontid |= 1; - else - shadow = 1; + bold = 1; + fontid |= 1; + } else { + bold = 0; + } + + if (!inst->fonts[fontid]) { + int i; + /* + * Fall back through font ids with subsets of this one's + * set bits, in order. + */ + for (i = fontid; i-- > 0 ;) { + if (i & ~fontid) + continue; /* some other bit is set */ + if (inst->fonts[i]) { + fontid = i; + break; + } + } + assert(inst->fonts[fontid]); /* we should at least have hit zero */ } if ((lattr & LATTR_MODE) != LATTR_NORM) { @@ -1993,82 +2009,27 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, gdk_gc_set_foreground(gc, &inst->cols[nfg]); { - GdkWChar *gwcs; gchar *gcs; - wchar_t *wcs; - int i; - wcs = snewn(len*ncombining+1, wchar_t); - for (i = 0; i < len*ncombining; i++) { - wcs[i] = text[i]; - } + /* + * FIXME: this length is hardwired on the assumption that + * conversions from wide to multibyte characters will + * never generate more than 10 bytes for a single wide + * character. + */ + gcs = snewn(len*10+1, gchar); - if (inst->fonts[fontid] == NULL && (fontid & 2)) { - /* - * We've been given ATTR_WIDE, but have no wide font. - * Fall back to the non-wide font. - */ - fontid &= ~2; - } - - if (inst->fonts[fontid] == NULL) { - /* - * The font for this contingency does not exist. So we - * display nothing at all; such is life. - */ - } else if (inst->fontinfo[fontid].is_wide) { - /* - * At least one version of gdk_draw_text_wc() has a - * weird bug whereby it reads `len' elements of the - * input string, but only draws `len/2'. Hence I'm - * going to make its input array twice as long as it - * theoretically needs to be, and pass in twice the - * actual number of characters. If a fixed gdk actually - * takes the doubled length seriously, then (a) the - * array will stand scrutiny up to the full length, (b) - * the spare elements of the array are full of zeroes - * which will probably be an empty glyph in the font, - * and (c) the clip rectangle should prevent it causing - * trouble anyway. - */ - gwcs = snewn(len*2+1, GdkWChar); - memset(gwcs, 0, sizeof(GdkWChar) * (len*2+1)); - /* - * FIXME: when we have a wide-char equivalent of - * from_unicode, use it instead of this. - */ - for (combining = 0; combining < ncombining; combining++) { - for (i = 0; i <= len; i++) - gwcs[i] = wcs[i + combining]; - gdk_draw_text_wc(inst->pixmap, inst->fonts[fontid], gc, - x*inst->font_width+inst->cfg.window_border, - y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent, - gwcs, len*2); - if (shadow) - gdk_draw_text_wc(inst->pixmap, inst->fonts[fontid], gc, - x*inst->font_width+inst->cfg.window_border+inst->cfg.shadowboldoffset, - y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent, - gwcs, len*2); - } - sfree(gwcs); - } else { - gcs = snewn(len+1, gchar); - for (combining = 0; combining < ncombining; combining++) { - wc_to_mb(inst->fontinfo[fontid].charset, 0, - wcs + combining, len, gcs, len, ".", NULL, NULL); - gdk_draw_text(inst->pixmap, inst->fonts[fontid], gc, + for (combining = 0; combining < ncombining; combining++) { + int mblen = wc_to_mb(inst->fonts[fontid]->real_charset, 0, + text + combining, len, gcs, len*10+1, ".", + NULL, NULL); + unifont_draw_text(inst->pixmap, gc, inst->fonts[fontid], x*inst->font_width+inst->cfg.window_border, y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent, - gcs, len); - if (shadow) - gdk_draw_text(inst->pixmap, inst->fonts[fontid], gc, - x*inst->font_width+inst->cfg.window_border+inst->cfg.shadowboldoffset, - y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent, - gcs, len); - } - sfree(gcs); + gcs, mblen, widefactor > 1, bold); } - sfree(wcs); + + sfree(gcs); } if (attr & ATTR_UNDER) { @@ -2633,70 +2594,6 @@ int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch, return err; } -/* - * This function retrieves the character set encoding of a font. It - * returns the character set without the X11 hack (in case the user - * asks to use the font's own encoding). - */ -static int set_font_info(struct gui_data *inst, int fontid) -{ - GdkFont *font = inst->fonts[fontid]; - XFontStruct *xfs = GDK_FONT_XFONT(font); - Display *disp = GDK_FONT_XDISPLAY(font); - Atom charset_registry, charset_encoding; - unsigned long registry_ret, encoding_ret; - int retval = CS_NONE; - - charset_registry = XInternAtom(disp, "CHARSET_REGISTRY", False); - charset_encoding = XInternAtom(disp, "CHARSET_ENCODING", False); - inst->fontinfo[fontid].charset = CS_NONE; - inst->fontinfo[fontid].is_wide = 0; - if (XGetFontProperty(xfs, charset_registry, ®istry_ret) && - XGetFontProperty(xfs, charset_encoding, &encoding_ret)) { - char *reg, *enc; - reg = XGetAtomName(disp, (Atom)registry_ret); - enc = XGetAtomName(disp, (Atom)encoding_ret); - if (reg && enc) { - char *encoding = dupcat(reg, "-", enc, NULL); - retval = inst->fontinfo[fontid].charset = - charset_from_xenc(encoding); - /* FIXME: when libcharset supports wide encodings fix this. */ - if (!strcasecmp(encoding, "iso10646-1")) { - inst->fontinfo[fontid].is_wide = 1; - retval = CS_UTF8; - } - - /* - * Hack for X line-drawing characters: if the primary - * font is encoded as ISO-8859-anything, and has valid - * glyphs in the first 32 char positions, it is assumed - * that those glyphs are the VT100 line-drawing - * character set. - * - * Actually, we'll hack even harder by only checking - * position 0x19 (vertical line, VT100 linedrawing - * `x'). Then we can check it easily by seeing if the - * ascent and descent differ. - */ - if (inst->fontinfo[fontid].charset == CS_ISO8859_1) { - int lb, rb, wid, asc, desc; - gchar text[2]; - - text[1] = '\0'; - text[0] = '\x12'; - gdk_string_extents(inst->fonts[fontid], text, - &lb, &rb, &wid, &asc, &desc); - if (asc != desc) - inst->fontinfo[fontid].charset = CS_ISO8859_1_X11; - } - - sfree(encoding); - } - } - - return retval; -} - int uxsel_input_add(int fd, int rwx) { int flags = 0; if (rwx & 1) flags |= GDK_INPUT_READ; @@ -2710,173 +2607,71 @@ void uxsel_input_remove(int id) { gdk_input_remove(id); } -char *guess_derived_font_name(GdkFont *font, int bold, int wide) -{ - XFontStruct *xfs = GDK_FONT_XFONT(font); - Display *disp = GDK_FONT_XDISPLAY(font); - Atom fontprop = XInternAtom(disp, "FONT", False); - unsigned long ret; - if (XGetFontProperty(xfs, fontprop, &ret)) { - char *name = XGetAtomName(disp, (Atom)ret); - if (name && name[0] == '-') { - char *strings[13]; - char *dupname, *extrafree = NULL, *ret; - char *p, *q; - int nstr; - - p = q = dupname = dupstr(name); /* skip initial minus */ - nstr = 0; - - while (*p && nstr < lenof(strings)) { - if (*p == '-') { - *p = '\0'; - strings[nstr++] = p+1; - } - p++; - } - - if (nstr < lenof(strings)) - return NULL; /* XLFD was malformed */ - - if (bold) - strings[2] = "bold"; - - if (wide) { - /* 4 is `wideness', which obviously may have changed. */ - /* 5 is additional style, which may be e.g. `ja' or `ko'. */ - strings[4] = strings[5] = "*"; - strings[11] = extrafree = dupprintf("%d", 2*atoi(strings[11])); - } - - ret = dupcat("-", strings[ 0], "-", strings[ 1], "-", strings[ 2], - "-", strings[ 3], "-", strings[ 4], "-", strings[ 5], - "-", strings[ 6], "-", strings[ 7], "-", strings[ 8], - "-", strings[ 9], "-", strings[10], "-", strings[11], - "-", strings[12], NULL); - sfree(extrafree); - sfree(dupname); - - return ret; - } - } - return NULL; -} - void setup_fonts_ucs(struct gui_data *inst) { - int font_charset; - char *name; - int guessed; - if (inst->fonts[0]) - gdk_font_unref(inst->fonts[0]); + unifont_destroy(inst->fonts[0]); if (inst->fonts[1]) - gdk_font_unref(inst->fonts[1]); + unifont_destroy(inst->fonts[1]); if (inst->fonts[2]) - gdk_font_unref(inst->fonts[2]); + unifont_destroy(inst->fonts[2]); if (inst->fonts[3]) - gdk_font_unref(inst->fonts[3]); + unifont_destroy(inst->fonts[3]); - inst->fonts[0] = gdk_font_load(inst->cfg.font.name); + inst->fonts[0] = unifont_create(inst->cfg.font.name, FALSE, FALSE, + inst->cfg.shadowboldoffset, + inst->cfg.shadowbold); if (!inst->fonts[0]) { fprintf(stderr, "%s: unable to load font \"%s\"\n", appname, inst->cfg.font.name); exit(1); } - gdk_font_ref(inst->fonts[0]); - font_charset = set_font_info(inst, 0); - if (inst->cfg.shadowbold) { + if (inst->cfg.shadowbold || !inst->cfg.boldfont.name[0]) { inst->fonts[1] = NULL; } else { - if (inst->cfg.boldfont.name[0]) { - name = inst->cfg.boldfont.name; - guessed = FALSE; - } else { - name = guess_derived_font_name(inst->fonts[0], TRUE, FALSE); - guessed = TRUE; - } - inst->fonts[1] = name ? gdk_font_load(name) : NULL; - if (inst->fonts[1]) { - gdk_font_ref(inst->fonts[1]); - set_font_info(inst, 1); - } else if (!guessed) { + inst->fonts[1] = unifont_create(inst->cfg.boldfont.name, FALSE, TRUE, + inst->cfg.shadowboldoffset, + inst->cfg.shadowbold); + if (!inst->fonts[1]) { fprintf(stderr, "%s: unable to load bold font \"%s\"\n", appname, inst->cfg.boldfont.name); exit(1); } - if (guessed) - sfree(name); } if (inst->cfg.widefont.name[0]) { - name = inst->cfg.widefont.name; - guessed = FALSE; - } else { - name = guess_derived_font_name(inst->fonts[0], FALSE, TRUE); - guessed = TRUE; - } - inst->fonts[2] = name ? gdk_font_load(name) : NULL; - if (inst->fonts[2]) { - gdk_font_ref(inst->fonts[2]); - set_font_info(inst, 2); - } else if (!guessed) { - fprintf(stderr, "%s: unable to load wide font \"%s\"\n", appname, - inst->cfg.widefont.name); - exit(1); - } - if (guessed) - sfree(name); - - if (inst->cfg.shadowbold) { - inst->fonts[3] = NULL; - } else { - if (inst->cfg.wideboldfont.name[0]) { - name = inst->cfg.wideboldfont.name; - guessed = FALSE; - } else { - /* - * Here we have some choices. We can widen the bold font, - * bolden the wide font, or widen and bolden the standard - * font. Try them all, in that order! - */ - if (inst->cfg.widefont.name[0]) - name = guess_derived_font_name(inst->fonts[2], TRUE, FALSE); - else if (inst->cfg.boldfont.name[0]) - name = guess_derived_font_name(inst->fonts[1], FALSE, TRUE); - else - name = guess_derived_font_name(inst->fonts[0], TRUE, TRUE); - guessed = TRUE; - } - inst->fonts[3] = name ? gdk_font_load(name) : NULL; - if (inst->fonts[3]) { - gdk_font_ref(inst->fonts[3]); - set_font_info(inst, 3); - } else if (!guessed) { - fprintf(stderr, "%s: unable to load wide/bold font \"%s\"\n", appname, - inst->cfg.wideboldfont.name); + inst->fonts[2] = unifont_create(inst->cfg.widefont.name, TRUE, FALSE, + inst->cfg.shadowboldoffset, + inst->cfg.shadowbold); + if (!inst->fonts[2]) { + fprintf(stderr, "%s: unable to load wide font \"%s\"\n", appname, + inst->cfg.widefont.name); exit(1); } - if (guessed) - sfree(name); + } else { + inst->fonts[2] = NULL; } - inst->font_width = gdk_char_width(inst->fonts[0], ' '); - if (!inst->font_width) { - /* Maybe this is a 16-bit font? If so, GDK 2 actually expects a - * pointer to an XChar2b. This is pretty revolting. Can Pango do - * this more neatly even for server-side fonts? - */ - XChar2b space; - space.byte1 = 0; - space.byte2 = ' '; - inst->font_width = gdk_text_width(inst->fonts[0], - (const gchar *)&space, 2); + if (inst->cfg.shadowbold || !inst->cfg.wideboldfont.name[0]) { + inst->fonts[3] = NULL; + } else { + inst->fonts[3] = unifont_create(inst->cfg.wideboldfont.name, TRUE, + TRUE, inst->cfg.shadowboldoffset, + inst->cfg.shadowbold); + if (!inst->fonts[3]) { + fprintf(stderr, "%s: unable to load wide bold font \"%s\"\n", appname, + inst->cfg.boldfont.name); + exit(1); + } } - inst->font_height = inst->fonts[0]->ascent + inst->fonts[0]->descent; + + inst->font_width = inst->fonts[0]->width; + inst->font_height = inst->fonts[0]->height; inst->direct_to_font = init_ucs(&inst->ucsdata, inst->cfg.line_codepage, - inst->cfg.utf8_override, font_charset, + inst->cfg.utf8_override, + inst->fonts[0]->public_charset, inst->cfg.vtmode); } From debbee0fe4f5f62823673f59dc82afccf33dc08f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 22 Mar 2008 18:11:17 +0000 Subject: [PATCH 15/53] Implemented a Pango back end. GTK 2 PuTTY can now switch seamlessly back and forth between X fonts and Pango fonts, provided you're willing to type in the names of the former by hand. [originally from svn r7937] --- unix/gtkfont.c | 196 ++++++++++++++++++++++++++++++++++++++++++++----- unix/gtkfont.h | 4 +- unix/gtkwin.c | 19 +++-- 3 files changed, 191 insertions(+), 28 deletions(-) diff --git a/unix/gtkfont.c b/unix/gtkfont.c index 8fabdd23..24c19dbf 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -25,10 +25,11 @@ /* * To do: * - * - import flags to do VT100 double-width, and import the icky - * pixmap stretch code for it. + * - import flags to do VT100 double-width; import the icky + * pixmap stretch code on to the X11 side, and do something + * nicer in Pango. * - * - add the Pango back end! + * - unified font selector dialog, arrgh! */ /* @@ -56,12 +57,12 @@ struct unifont_vtable { /* * `Methods' of the `class'. */ - unifont *(*create)(char *name, int wide, int bold, + unifont *(*create)(GtkWidget *widget, char *name, int wide, int bold, int shadowoffset, int shadowalways); void (*destroy)(unifont *font); void (*draw_text)(GdkDrawable *target, GdkGC *gc, unifont *font, int x, int y, const char *string, int len, int wide, - int bold); + int bold, int cellwidth); /* * `Static data members' of the `class'. */ @@ -74,8 +75,9 @@ struct unifont_vtable { static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, int x, int y, const char *string, int len, - int wide, int bold); -static unifont *x11font_create(char *name, int wide, int bold, + int wide, int bold, int cellwidth); +static unifont *x11font_create(GtkWidget *widget, char *name, + int wide, int bold, int shadowoffset, int shadowalways); static void x11font_destroy(unifont *font); @@ -99,11 +101,6 @@ struct x11font { * whether we use gdk_draw_text_wc() or gdk_draw_text(). */ int sixteen_bit; - /* - * Font charsets. public_charset and real_charset can differ - * for X11 fonts, because many X fonts use CS_ISO8859_1_X11. - */ - int public_charset, real_charset; /* * Data passed in to unifont_create(). */ @@ -181,7 +178,8 @@ static int x11_font_width(GdkFont *font, int sixteen_bit) } } -static unifont *x11font_create(char *name, int wide, int bold, +static unifont *x11font_create(GtkWidget *widget, char *name, + int wide, int bold, int shadowoffset, int shadowalways) { struct x11font *xfont; @@ -299,7 +297,7 @@ static void x11_alloc_subfont(struct x11font *xfont, int sfid) static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, int x, int y, const char *string, int len, - int wide, int bold) + int wide, int bold, int cellwidth) { struct x11font *xfont = (struct x11font *)font; int sfid; @@ -374,6 +372,164 @@ static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, } } +/* ---------------------------------------------------------------------- + * Pango font implementation. + */ + +static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const char *string, int len, + int wide, int bold, int cellwidth); +static unifont *pangofont_create(GtkWidget *widget, char *name, + int wide, int bold, + int shadowoffset, int shadowalways); +static void pangofont_destroy(unifont *font); + +struct pangofont { + struct unifont u; + /* + * Pango objects. + */ + PangoFontDescription *desc; + PangoFontset *fset; + /* + * The containing widget. + */ + GtkWidget *widget; + /* + * Data passed in to unifont_create(). + */ + int bold, shadowoffset, shadowalways; +}; + +static const struct unifont_vtable pangofont_vtable = { + pangofont_create, + pangofont_destroy, + pangofont_draw_text, + "pango" +}; + +static unifont *pangofont_create(GtkWidget *widget, char *name, + int wide, int bold, + int shadowoffset, int shadowalways) +{ + struct pangofont *pfont; + PangoContext *ctx; + PangoFontMap *map; + PangoFontDescription *desc; + PangoFontset *fset; + PangoFontMetrics *metrics; + + desc = pango_font_description_from_string(name); + if (!desc) + return NULL; + ctx = gtk_widget_get_pango_context(widget); + if (!ctx) { + pango_font_description_free(desc); + return NULL; + } + map = pango_context_get_font_map(ctx); + if (!map) { + pango_font_description_free(desc); + return NULL; + } + fset = pango_font_map_load_fontset(map, ctx, desc, + pango_context_get_language(ctx)); + if (!fset) { + pango_font_description_free(desc); + return NULL; + } + metrics = pango_fontset_get_metrics(fset); + if (!metrics || + pango_font_metrics_get_approximate_digit_width(metrics) == 0) { + pango_font_description_free(desc); + g_object_unref(fset); + return NULL; + } + + pfont = snew(struct pangofont); + pfont->u.vt = &pangofont_vtable; + pfont->u.width = + PANGO_PIXELS(pango_font_metrics_get_approximate_digit_width(metrics)); + pfont->u.ascent = PANGO_PIXELS(pango_font_metrics_get_ascent(metrics)); + pfont->u.descent = PANGO_PIXELS(pango_font_metrics_get_descent(metrics)); + pfont->u.height = pfont->u.ascent + pfont->u.descent; + /* The Pango API is hardwired to UTF-8 */ + pfont->u.public_charset = CS_UTF8; + pfont->u.real_charset = CS_UTF8; + pfont->desc = desc; + pfont->fset = fset; + pfont->widget = widget; + pfont->bold = bold; + pfont->shadowoffset = shadowoffset; + pfont->shadowalways = shadowalways; + + return (unifont *)pfont; +} + +static void pangofont_destroy(unifont *font) +{ + struct pangofont *pfont = (struct pangofont *)font; + pfont = pfont; /* FIXME */ + pango_font_description_free(pfont->desc); + g_object_unref(pfont->fset); + sfree(font); +} + +static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, + int x, int y, const char *string, int len, + int wide, int bold, int cellwidth) +{ + struct pangofont *pfont = (struct pangofont *)font; + PangoLayout *layout; + PangoRectangle rect; + int shadowbold = FALSE; + + if (wide) + cellwidth *= 2; + + y -= pfont->u.ascent; + + layout = pango_layout_new(gtk_widget_get_pango_context(pfont->widget)); + pango_layout_set_font_description(layout, pfont->desc); + if (bold > pfont->bold) { + if (pfont->shadowalways) + shadowbold = TRUE; + else { + PangoFontDescription *desc2 = + pango_font_description_copy_static(pfont->desc); + pango_font_description_set_weight(desc2, PANGO_WEIGHT_BOLD); + pango_layout_set_font_description(layout, desc2); + } + } + + while (len > 0) { + int clen; + + /* + * Extract a single UTF-8 character from the string. + */ + clen = 1; + while (clen < len && + (unsigned char)string[clen] >= 0x80 && + (unsigned char)string[clen] < 0xC0) + clen++; + + pango_layout_set_text(layout, string, clen); + pango_layout_get_pixel_extents(layout, NULL, &rect); + gdk_draw_layout(target, gc, x + (cellwidth - rect.width)/2, + y + (pfont->u.height - rect.height)/2, layout); + if (shadowbold) + gdk_draw_layout(target, gc, x + (cellwidth - rect.width)/2 + pfont->shadowoffset, + y + (pfont->u.height - rect.height)/2, layout); + + len -= clen; + string += clen; + x += cellwidth; + } + + g_object_unref(layout); +} + /* ---------------------------------------------------------------------- * Outermost functions which do the vtable dispatch. */ @@ -385,9 +541,10 @@ static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, * the font name. */ static const struct unifont_vtable *unifont_types[] = { + &pangofont_vtable, &x11font_vtable, }; -unifont *unifont_create(char *name, int wide, int bold, +unifont *unifont_create(GtkWidget *widget, char *name, int wide, int bold, int shadowoffset, int shadowalways) { int colonpos = strcspn(name, ":"); @@ -405,14 +562,14 @@ unifont *unifont_create(char *name, int wide, int bold, } if (i == lenof(unifont_types)) return NULL; /* prefix not recognised */ - return unifont_types[i]->create(name+colonpos+1, wide, bold, + return unifont_types[i]->create(widget, name+colonpos+1, wide, bold, shadowoffset, shadowalways); } else { /* * No colon prefix, so just go through all the subclasses. */ for (i = 0; i < lenof(unifont_types); i++) { - unifont *ret = unifont_types[i]->create(name, wide, bold, + unifont *ret = unifont_types[i]->create(widget, name, wide, bold, shadowoffset, shadowalways); if (ret) @@ -429,7 +586,8 @@ void unifont_destroy(unifont *font) void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, int x, int y, const char *string, int len, - int wide, int bold) + int wide, int bold, int cellwidth) { - font->vt->draw_text(target, gc, font, x, y, string, len, wide, bold); + font->vt->draw_text(target, gc, font, x, y, string, len, + wide, bold, cellwidth); } diff --git a/unix/gtkfont.h b/unix/gtkfont.h index 9cb66783..5c36fee5 100644 --- a/unix/gtkfont.h +++ b/unix/gtkfont.h @@ -36,11 +36,11 @@ typedef struct unifont { int width, height, ascent, descent; } unifont; -unifont *unifont_create(char *name, int wide, int bold, +unifont *unifont_create(GtkWidget *widget, char *name, int wide, int bold, int shadowoffset, int shadowalways); void unifont_destroy(unifont *font); void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, int x, int y, const char *string, int len, - int wide, int bold); + int wide, int bold, int cellwidth); #endif /* PUTTY_GTKFONT_H */ diff --git a/unix/gtkwin.c b/unix/gtkwin.c index 2972dba1..5e4c6946 100644 --- a/unix/gtkwin.c +++ b/unix/gtkwin.c @@ -1455,7 +1455,7 @@ void palette_reset(void *frontend) /* Since Default Background may have changed, ensure that space * between text area and window border is refreshed. */ set_window_background(inst); - if (inst->area) { + if (inst->area && inst->area->window) { draw_backing_rect(inst); gtk_widget_queue_draw(inst->area); } @@ -2026,7 +2026,7 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, unifont_draw_text(inst->pixmap, gc, inst->fonts[fontid], x*inst->font_width+inst->cfg.window_border, y*inst->font_height+inst->cfg.window_border+inst->fonts[0]->ascent, - gcs, mblen, widefactor > 1, bold); + gcs, mblen, widefactor > 1, bold, inst->font_width); } sfree(gcs); @@ -2618,7 +2618,8 @@ void setup_fonts_ucs(struct gui_data *inst) if (inst->fonts[3]) unifont_destroy(inst->fonts[3]); - inst->fonts[0] = unifont_create(inst->cfg.font.name, FALSE, FALSE, + inst->fonts[0] = unifont_create(inst->area, inst->cfg.font.name, + FALSE, FALSE, inst->cfg.shadowboldoffset, inst->cfg.shadowbold); if (!inst->fonts[0]) { @@ -2630,7 +2631,8 @@ void setup_fonts_ucs(struct gui_data *inst) if (inst->cfg.shadowbold || !inst->cfg.boldfont.name[0]) { inst->fonts[1] = NULL; } else { - inst->fonts[1] = unifont_create(inst->cfg.boldfont.name, FALSE, TRUE, + inst->fonts[1] = unifont_create(inst->area, inst->cfg.boldfont.name, + FALSE, TRUE, inst->cfg.shadowboldoffset, inst->cfg.shadowbold); if (!inst->fonts[1]) { @@ -2641,7 +2643,8 @@ void setup_fonts_ucs(struct gui_data *inst) } if (inst->cfg.widefont.name[0]) { - inst->fonts[2] = unifont_create(inst->cfg.widefont.name, TRUE, FALSE, + inst->fonts[2] = unifont_create(inst->area, inst->cfg.widefont.name, + TRUE, FALSE, inst->cfg.shadowboldoffset, inst->cfg.shadowbold); if (!inst->fonts[2]) { @@ -2656,7 +2659,8 @@ void setup_fonts_ucs(struct gui_data *inst) if (inst->cfg.shadowbold || !inst->cfg.wideboldfont.name[0]) { inst->fonts[3] = NULL; } else { - inst->fonts[3] = unifont_create(inst->cfg.wideboldfont.name, TRUE, + inst->fonts[3] = unifont_create(inst->area, + inst->cfg.wideboldfont.name, TRUE, TRUE, inst->cfg.shadowboldoffset, inst->cfg.shadowbold); if (!inst->fonts[3]) { @@ -3320,6 +3324,8 @@ int pt_main(int argc, char **argv) if (!utf8_string_atom) utf8_string_atom = gdk_atom_intern("UTF8_STRING", FALSE); + inst->area = gtk_drawing_area_new(); + setup_fonts_ucs(inst); init_cutbuffers(); @@ -3333,7 +3339,6 @@ int pt_main(int argc, char **argv) inst->width = inst->cfg.width; inst->height = inst->cfg.height; - inst->area = gtk_drawing_area_new(); gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), inst->font_width * inst->cfg.width + 2*inst->cfg.window_border, inst->font_height * inst->cfg.height + 2*inst->cfg.window_border); From 82a586792f5a5a95ba59aa4632760bcbc4597362 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 25 Mar 2008 21:49:14 +0000 Subject: [PATCH 16/53] Unified font selector dialog box. _Extremely_ unfinished - there's a sizable TODO at the top of gtkfont.c - but it's basically functional enough to select fonts of both types, so I'm checking it in now before I accidentally break it. [originally from svn r7938] --- unix/gtkdlg.c | 81 +-- unix/gtkfont.c | 1387 ++++++++++++++++++++++++++++++++++++++++++++++-- unix/gtkfont.h | 23 +- 3 files changed, 1398 insertions(+), 93 deletions(-) diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index ee520a37..0c248779 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -13,6 +13,7 @@ #include #include "gtkcols.h" +#include "gtkfont.h" #ifdef TESTMODE #define PUTTY_DO_GLOBALS /* actually _define_ globals */ @@ -1221,11 +1222,12 @@ static void filesel_ok(GtkButton *button, gpointer data) static void fontsel_ok(GtkButton *button, gpointer data) { /* struct dlgparam *dp = (struct dlgparam *)data; */ - gpointer fontsel = gtk_object_get_data(GTK_OBJECT(button), "user-data"); - struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(fontsel), "user-data"); - const char *name = gtk_font_selection_dialog_get_font_name - (GTK_FONT_SELECTION_DIALOG(fontsel)); + unifontsel *fontsel = (unifontsel *)gtk_object_get_data + (GTK_OBJECT(button), "user-data"); + struct uctrl *uc = (struct uctrl *)fontsel->user_data; + char *name = unifontsel_get_name(fontsel); gtk_entry_set_text(GTK_ENTRY(uc->entry), name); + sfree(name); } static void coloursel_ok(GtkButton *button, gpointer data) @@ -1279,60 +1281,25 @@ static void filefont_clicked(GtkButton *button, gpointer data) } if (uc->ctrl->generic.type == CTRL_FONTSELECT) { -#if !GTK_CHECK_VERSION(2,0,0) - gchar *spacings[] = { "c", "m", NULL }; -#endif const gchar *fontname = gtk_entry_get_text(GTK_ENTRY(uc->entry)); - /* TODO: In GTK 2, this only seems to offer client-side fonts. */ - GtkWidget *fontsel = - gtk_font_selection_dialog_new("Select a font"); - gtk_window_set_modal(GTK_WINDOW(fontsel), TRUE); -#if !GTK_CHECK_VERSION(2,0,0) - gtk_font_selection_dialog_set_filter - (GTK_FONT_SELECTION_DIALOG(fontsel), - GTK_FONT_FILTER_BASE, GTK_FONT_ALL, - NULL, NULL, NULL, NULL, spacings, NULL); -#endif - if (!gtk_font_selection_dialog_set_font_name - (GTK_FONT_SELECTION_DIALOG(fontsel), fontname)) { - /* - * If the font name wasn't found as it was, try opening - * it and extracting its FONT property. This should - * have the effect of mapping short aliases into true - * XLFDs. - */ - GdkFont *font = gdk_font_load(fontname); - if (font) { - XFontStruct *xfs = GDK_FONT_XFONT(font); - Display *disp = GDK_FONT_XDISPLAY(font); - Atom fontprop = XInternAtom(disp, "FONT", False); - unsigned long ret; - gdk_font_ref(font); - if (XGetFontProperty(xfs, fontprop, &ret)) { - char *name = XGetAtomName(disp, (Atom)ret); - if (name) - gtk_font_selection_dialog_set_font_name - (GTK_FONT_SELECTION_DIALOG(fontsel), name); - } - gdk_font_unref(font); - } - } - gtk_object_set_data - (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), - "user-data", (gpointer)fontsel); - gtk_object_set_data(GTK_OBJECT(fontsel), "user-data", (gpointer)uc); - gtk_signal_connect - (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), - "clicked", GTK_SIGNAL_FUNC(fontsel_ok), (gpointer)dp); - gtk_signal_connect_object - (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), - "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), - (gpointer)fontsel); - gtk_signal_connect_object - (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->cancel_button), - "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), - (gpointer)fontsel); - gtk_widget_show(fontsel); + unifontsel *fontsel = unifontsel_new("Select a font"); + + gtk_window_set_modal(fontsel->window, TRUE); + unifontsel_set_name(fontsel, fontname); + + gtk_object_set_data(GTK_OBJECT(fontsel->ok_button), + "user-data", (gpointer)fontsel); + fontsel->user_data = uc; + gtk_signal_connect(GTK_OBJECT(fontsel->ok_button), "clicked", + GTK_SIGNAL_FUNC(fontsel_ok), (gpointer)dp); + gtk_signal_connect_object(GTK_OBJECT(fontsel->ok_button), "clicked", + GTK_SIGNAL_FUNC(unifontsel_destroy), + (gpointer)fontsel); + gtk_signal_connect_object(GTK_OBJECT(fontsel->cancel_button),"clicked", + GTK_SIGNAL_FUNC(unifontsel_destroy), + (gpointer)fontsel); + + gtk_widget_show(GTK_WIDGET(fontsel->window)); } } diff --git a/unix/gtkfont.c b/unix/gtkfont.c index 24c19dbf..19f9bafd 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -21,15 +21,35 @@ #include "putty.h" #include "gtkfont.h" +#include "tree234.h" /* - * To do: + * TODO on fontsel + * --------------- * - * - import flags to do VT100 double-width; import the icky - * pixmap stretch code on to the X11 side, and do something - * nicer in Pango. + * - implement the preview pane * - * - unified font selector dialog, arrgh! + * - extend the font style language for X11 fonts so that we + * never get unexplained double size elements? Or, at least, so + * that _my_ font collection never produces them; that'd be a + * decent start. + * + * - decide what _should_ happen about font aliases. Should we + * resolve them as soon as they're clicked? Or be able to + * resolve them on demand, er, somehow? Or resolve them on exit + * from the function? Or what? If we resolve on demand, should + * we stop canonifying them on input, on the basis that we'd + * prefer to let the user _tell_ us when to canonify them? + * + * - think about points versus pixels, harder than I already have + * + * - work out why the list boxes don't go all the way to the RHS + * of the dialog box + * + * - develop a sensible sorting order for the font styles - + * Regular / Roman / non-bold-or-italic should come at the top! + * + * - big testing and shakedown! */ /* @@ -40,6 +60,16 @@ * or before, if I'm feeling proactive - it oughtn't to be too * difficult in principle to convert the whole thing to use * actual Xlib font calls. + * + * - it would be nice if we could move the processing of + * underline and VT100 double width into this module, so that + * instead of using the ghastly pixmap-stretching technique + * everywhere we could tell the Pango backend to scale its + * fonts to double size properly and at full resolution. + * However, this requires me to learn how to make Pango stretch + * text to an arbitrary aspect ratio (for double-width only + * text, which perversely is harder than DW+DH), and right now + * I haven't the energy. */ /* @@ -53,16 +83,31 @@ * structure and a pointer to its first element. */ +#define FONTFLAG_CLIENTSIDE 0x0001 +#define FONTFLAG_SERVERSIDE 0x0002 +#define FONTFLAG_SERVERALIAS 0x0004 +#define FONTFLAG_NONMONOSPACED 0x0008 + +typedef void (*fontsel_add_entry)(void *ctx, const char *realfontname, + const char *family, const char *charset, + const char *style, int size, int flags, + const struct unifont_vtable *fontclass); + struct unifont_vtable { /* * `Methods' of the `class'. */ - unifont *(*create)(GtkWidget *widget, char *name, int wide, int bold, + unifont *(*create)(GtkWidget *widget, const char *name, int wide, int bold, int shadowoffset, int shadowalways); void (*destroy)(unifont *font); void (*draw_text)(GdkDrawable *target, GdkGC *gc, unifont *font, int x, int y, const char *string, int len, int wide, int bold, int cellwidth); + void (*enum_fonts)(GtkWidget *widget, + fontsel_add_entry callback, void *callback_ctx); + char *(*canonify_fontname)(GtkWidget *widget, const char *name, int *size); + char *(*scale_fontname)(GtkWidget *widget, const char *name, int size); + /* * `Static data members' of the `class'. */ @@ -76,10 +121,16 @@ struct unifont_vtable { static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, int x, int y, const char *string, int len, int wide, int bold, int cellwidth); -static unifont *x11font_create(GtkWidget *widget, char *name, +static unifont *x11font_create(GtkWidget *widget, const char *name, int wide, int bold, int shadowoffset, int shadowalways); static void x11font_destroy(unifont *font); +static void x11font_enum_fonts(GtkWidget *widget, + fontsel_add_entry callback, void *callback_ctx); +static char *x11font_canonify_fontname(GtkWidget *widget, const char *name, + int *size); +static char *x11font_scale_fontname(GtkWidget *widget, const char *name, + int size); struct x11font { struct unifont u; @@ -111,7 +162,10 @@ static const struct unifont_vtable x11font_vtable = { x11font_create, x11font_destroy, x11font_draw_text, - "x11" + x11font_enum_fonts, + x11font_canonify_fontname, + x11font_scale_fontname, + "server" }; char *x11_guess_derived_font_name(GdkFont *font, int bold, int wide) @@ -178,7 +232,7 @@ static int x11_font_width(GdkFont *font, int sixteen_bit) } } -static unifont *x11font_create(GtkWidget *widget, char *name, +static unifont *x11font_create(GtkWidget *widget, const char *name, int wide, int bold, int shadowoffset, int shadowalways) { @@ -372,6 +426,182 @@ static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, } } +static void x11font_enum_fonts(GtkWidget *widget, + fontsel_add_entry callback, void *callback_ctx) +{ + char **fontnames; + char *tmp = NULL; + int nnames, i, max, tmpsize; + + max = 32768; + while (1) { + fontnames = XListFonts(GDK_DISPLAY(), "*", max, &nnames); + if (nnames >= max) { + XFreeFontNames(fontnames); + max *= 2; + } else + break; + } + + tmpsize = 0; + + for (i = 0; i < nnames; i++) { + if (fontnames[i][0] == '-') { + /* + * Dismember an XLFD and convert it into the format + * we'll be using in the font selector. + */ + char *components[14]; + char *p, *font, *style, *charset; + int j, thistmpsize, fontsize, flags; + + thistmpsize = 3 * strlen(fontnames[i]) + 256; + if (tmpsize < thistmpsize) { + tmpsize = thistmpsize; + tmp = sresize(tmp, tmpsize, char); + } + strcpy(tmp, fontnames[i]); + + p = tmp; + for (j = 0; j < 14; j++) { + if (*p) + *p++ = '\0'; + components[j] = p; + while (*p && *p != '-') + p++; + } + *p++ = '\0'; + + /* + * Font name is made up of fields 0 and 1, in reverse + * order with parentheses. (This is what the GTK 1.2 X + * font selector does, and it seems to come out + * looking reasonably sensible.) + */ + font = p; + p += 1 + sprintf(p, "%s (%s)", components[1], components[0]); + + /* + * Charset is made up of fields 12 and 13. + */ + charset = p; + p += 1 + sprintf(p, "%s-%s", components[12], components[13]); + + /* + * Style is a mixture of the weight, slant, set_width + * and spacing fields (respectively 2, 3, 4 and 10) + * with some strange formatting. (Again, cribbed + * entirely from the GTK 1.2 font selector.) + */ + style = p; + p += sprintf(p, "%s", components[2][0] ? components[2] : + "regular"); + if (!g_strcasecmp(components[3], "i")) + p += sprintf(p, " italic"); + else if (!g_strcasecmp(components[3], "o")) + p += sprintf(p, " oblique"); + else if (!g_strcasecmp(components[3], "ri")) + p += sprintf(p, " reverse italic"); + else if (!g_strcasecmp(components[3], "ro")) + p += sprintf(p, " reverse oblique"); + else if (!g_strcasecmp(components[3], "ot")) + p += sprintf(p, " other-slant"); + if (components[4][0] && g_strcasecmp(components[4], "normal")) + p += sprintf(p, " %s", components[4]); + if (!g_strcasecmp(components[10], "m")) + p += sprintf(p, " [M]"); + if (!g_strcasecmp(components[10], "c")) + p += sprintf(p, " [C]"); + + /* + * Size is in pixels, for our application, so we + * derive it directly from the pixel size field, + * number 6. + */ + fontsize = atoi(components[6]); + + /* + * Flags: we need to know whether this is a monospaced + * font, which we do by examining the spacing field + * again. + */ + flags = FONTFLAG_SERVERSIDE; + if (!strchr("CcMm", components[10][0])) + flags |= FONTFLAG_NONMONOSPACED; + + /* + * Not sure why, but sometimes the X server will + * deliver dummy font types in which fontsize comes + * out as zero. Filter those out. + */ + if (fontsize) + callback(callback_ctx, fontnames[i], font, charset, + style, fontsize, flags, &x11font_vtable); + } else { + /* + * This isn't an XLFD, so it must be an alias. + * Transmit it with mostly null data. + * + * It would be nice to work out if it's monospaced + * here, but at the moment I can't see that being + * anything but computationally hideous. Ah well. + */ + callback(callback_ctx, fontnames[i], fontnames[i], NULL, + NULL, 0, FONTFLAG_SERVERALIAS, &x11font_vtable); + } + } + XFreeFontNames(fontnames); +} + +static char *x11font_canonify_fontname(GtkWidget *widget, const char *name, + int *size) +{ + /* + * When given an X11 font name to try to make sense of for a + * font selector, we must attempt to load it (to see if it + * exists), and then canonify it by extracting its FONT + * property, which should give its full XLFD even if what we + * originally had was an alias. + */ + GdkFont *font = gdk_font_load(name); + XFontStruct *xfs; + Display *disp; + Atom fontprop, fontprop2; + unsigned long ret; + + if (!font) + return NULL; /* didn't make sense to us, sorry */ + + gdk_font_ref(font); + + xfs = GDK_FONT_XFONT(font); + disp = GDK_FONT_XDISPLAY(font); + fontprop = XInternAtom(disp, "FONT", False); + + if (XGetFontProperty(xfs, fontprop, &ret)) { + char *name = XGetAtomName(disp, (Atom)ret); + if (name) { + unsigned long fsize = 12; + + fontprop2 = XInternAtom(disp, "PIXEL_SIZE", False); + if (XGetFontProperty(xfs, fontprop2, &fsize)) { + *size = fsize; + gdk_font_unref(font); + return dupstr(name); + } + } + } + + gdk_font_unref(font); + return NULL; /* something went wrong */ +} + +static char *x11font_scale_fontname(GtkWidget *widget, const char *name, + int size) +{ + return NULL; /* shan't */ +} + /* ---------------------------------------------------------------------- * Pango font implementation. */ @@ -379,10 +609,16 @@ static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, int x, int y, const char *string, int len, int wide, int bold, int cellwidth); -static unifont *pangofont_create(GtkWidget *widget, char *name, +static unifont *pangofont_create(GtkWidget *widget, const char *name, int wide, int bold, int shadowoffset, int shadowalways); static void pangofont_destroy(unifont *font); +static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, + void *callback_ctx); +static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name, + int *size); +static char *pangofont_scale_fontname(GtkWidget *widget, const char *name, + int size); struct pangofont { struct unifont u; @@ -405,10 +641,13 @@ static const struct unifont_vtable pangofont_vtable = { pangofont_create, pangofont_destroy, pangofont_draw_text, - "pango" + pangofont_enum_fonts, + pangofont_canonify_fontname, + pangofont_scale_fontname, + "client" }; -static unifont *pangofont_create(GtkWidget *widget, char *name, +static unifont *pangofont_create(GtkWidget *widget, const char *name, int wide, int bold, int shadowoffset, int shadowalways) { @@ -463,13 +702,14 @@ static unifont *pangofont_create(GtkWidget *widget, char *name, pfont->shadowoffset = shadowoffset; pfont->shadowalways = shadowalways; + pango_font_metrics_unref(metrics); + return (unifont *)pfont; } static void pangofont_destroy(unifont *font) { struct pangofont *pfont = (struct pangofont *)font; - pfont = pfont; /* FIXME */ pango_font_description_free(pfont->desc); g_object_unref(pfont->fset); sfree(font); @@ -530,22 +770,222 @@ static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, g_object_unref(layout); } +/* + * Dummy size value to be used when converting a + * PangoFontDescription of a scalable font to a string for + * internal use. + */ +#define PANGO_DUMMY_SIZE 12 + +static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, + void *callback_ctx) +{ + PangoContext *ctx; + PangoFontMap *map; + PangoFontFamily **families; + int i, nfamilies; + + /* + * Find the active font map. + */ + ctx = gtk_widget_get_pango_context(widget); + if (!ctx) + return; + map = pango_context_get_font_map(ctx); + if (!map) + return; + + /* + * Ask the font map for a list of font families, and iterate + * through them. + */ + pango_font_map_list_families(map, &families, &nfamilies); + for (i = 0; i < nfamilies; i++) { + PangoFontFamily *family = families[i]; + const char *familyname; + int flags; + PangoFontFace **faces; + int j, nfaces; + + /* + * Set up our flags for this font family, and get the name + * string. + */ + flags = FONTFLAG_CLIENTSIDE; + if (!pango_font_family_is_monospace(family)) + flags |= FONTFLAG_NONMONOSPACED; + familyname = pango_font_family_get_name(family); + + /* + * Go through the available font faces in this family. + */ + pango_font_family_list_faces(family, &faces, &nfaces); + for (j = 0; j < nfaces; j++) { + PangoFontFace *face = faces[j]; + PangoFontDescription *desc; + const char *facename; + int *sizes; + int k, nsizes, dummysize; + + /* + * Get the face name string. + */ + facename = pango_font_face_get_face_name(face); + + /* + * Set up a font description with what we've got so + * far. We'll fill in the size field manually and then + * call pango_font_description_to_string() to give the + * full real name of the specific font. + */ + desc = pango_font_face_describe(face); + + /* + * See if this font has a list of specific sizes. + */ + pango_font_face_list_sizes(face, &sizes, &nsizes); + if (!sizes) { + /* + * Write a single entry with a dummy size. + */ + dummysize = PANGO_DUMMY_SIZE * PANGO_SCALE; + sizes = &dummysize; + nsizes = 1; + } + + /* + * If so, go through them one by one. + */ + for (k = 0; k < nsizes; k++) { + char *fullname; + + pango_font_description_set_size(desc, sizes[k]); + + fullname = pango_font_description_to_string(desc); + + /* + * Got everything. Hand off to the callback. + * (The charset string is NULL, because only + * server-side X fonts use it.) + */ + callback(callback_ctx, fullname, familyname, NULL, facename, + (sizes == &dummysize ? 0 : PANGO_PIXELS(sizes[k])), + flags, &pangofont_vtable); + + g_free(fullname); + } + if (sizes != &dummysize) + g_free(sizes); + + pango_font_description_free(desc); + } + g_free(faces); + } + g_free(families); +} + +static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name, + int *size) +{ + /* + * When given a Pango font name to try to make sense of for a + * font selector, we must normalise it to PANGO_DUMMY_SIZE and + * extract its original size (in pixels) into the `size' field. + */ + PangoContext *ctx; + PangoFontMap *map; + PangoFontDescription *desc; + PangoFontset *fset; + PangoFontMetrics *metrics; + char *newname, *retname; + + desc = pango_font_description_from_string(name); + if (!desc) + return NULL; + ctx = gtk_widget_get_pango_context(widget); + if (!ctx) { + pango_font_description_free(desc); + return NULL; + } + map = pango_context_get_font_map(ctx); + if (!map) { + pango_font_description_free(desc); + return NULL; + } + fset = pango_font_map_load_fontset(map, ctx, desc, + pango_context_get_language(ctx)); + if (!fset) { + pango_font_description_free(desc); + return NULL; + } + metrics = pango_fontset_get_metrics(fset); + if (!metrics || + pango_font_metrics_get_approximate_digit_width(metrics) == 0) { + pango_font_description_free(desc); + g_object_unref(fset); + return NULL; + } + + *size = PANGO_PIXELS(pango_font_description_get_size(desc)); + pango_font_description_set_size(desc, PANGO_DUMMY_SIZE * PANGO_SCALE); + newname = pango_font_description_to_string(desc); + retname = dupstr(newname); + g_free(newname); + + pango_font_metrics_unref(metrics); + pango_font_description_free(desc); + g_object_unref(fset); + + return retname; +} + +static char *pangofont_scale_fontname(GtkWidget *widget, const char *name, + int size) +{ + PangoFontDescription *desc; + char *newname, *retname; + + desc = pango_font_description_from_string(name); + if (!desc) + return NULL; + pango_font_description_set_size(desc, size * PANGO_SCALE); + newname = pango_font_description_to_string(desc); + retname = dupstr(newname); + g_free(newname); + pango_font_description_free(desc); + + return retname; +} + /* ---------------------------------------------------------------------- * Outermost functions which do the vtable dispatch. */ /* - * This function is the only one which needs to know the full set - * of font implementations available, because it has to try each - * in turn until one works, or condition on an override prefix in - * the font name. + * Complete list of font-type subclasses. Listed in preference + * order for unifont_create(). (That is, in the extremely unlikely + * event that the same font name is valid as both a Pango and an + * X11 font, it will be interpreted as the former in the absence + * of an explicit type-disambiguating prefix.) */ static const struct unifont_vtable *unifont_types[] = { &pangofont_vtable, &x11font_vtable, }; -unifont *unifont_create(GtkWidget *widget, char *name, int wide, int bold, - int shadowoffset, int shadowalways) + +/* + * Function which takes a font name and processes the optional + * scheme prefix. Returns the tail of the font name suitable for + * passing to individual font scheme functions, and also provides + * a subrange of the unifont_types[] array above. + * + * The return values `start' and `end' denote a half-open interval + * in unifont_types[]; that is, the correct way to iterate over + * them is + * + * for (i = start; i < end; i++) {...} + */ +static const char *unifont_do_prefix(const char *name, int *start, int *end) { int colonpos = strcspn(name, ":"); int i; @@ -553,32 +993,48 @@ unifont *unifont_create(GtkWidget *widget, char *name, int wide, int bold, if (name[colonpos]) { /* * There's a colon prefix on the font name. Use it to work - * out which subclass to try to create. + * out which subclass to use. */ for (i = 0; i < lenof(unifont_types); i++) { if (strlen(unifont_types[i]->prefix) == colonpos && - !strncmp(unifont_types[i]->prefix, name, colonpos)) - break; + !strncmp(unifont_types[i]->prefix, name, colonpos)) { + *start = i; + *end = i+1; + return name + colonpos + 1; + } } - if (i == lenof(unifont_types)) - return NULL; /* prefix not recognised */ - return unifont_types[i]->create(widget, name+colonpos+1, wide, bold, - shadowoffset, shadowalways); + /* + * None matched, so return an empty scheme list to prevent + * any scheme from being called at all. + */ + *start = *end = 0; + return name + colonpos + 1; } else { /* - * No colon prefix, so just go through all the subclasses. + * No colon prefix, so just use all the subclasses. */ - for (i = 0; i < lenof(unifont_types); i++) { - unifont *ret = unifont_types[i]->create(widget, name, wide, bold, - shadowoffset, - shadowalways); - if (ret) - return ret; - } - return NULL; /* font not found in any scheme */ + *start = 0; + *end = lenof(unifont_types); + return name; } } +unifont *unifont_create(GtkWidget *widget, const char *name, int wide, + int bold, int shadowoffset, int shadowalways) +{ + int i, start, end; + + name = unifont_do_prefix(name, &start, &end); + + for (i = start; i < end; i++) { + unifont *ret = unifont_types[i]->create(widget, name, wide, bold, + shadowoffset, shadowalways); + if (ret) + return ret; + } + return NULL; /* font not found in any scheme */ +} + void unifont_destroy(unifont *font) { font->vt->destroy(font); @@ -591,3 +1047,864 @@ void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, font->vt->draw_text(target, gc, font, x, y, string, len, wide, bold, cellwidth); } + +/* ---------------------------------------------------------------------- + * Implementation of a unified font selector. + */ + +typedef struct fontinfo fontinfo; + +typedef struct unifontsel_internal { + /* This must be the structure's first element, for cross-casting */ + unifontsel u; + GtkListStore *family_model, *style_model, *size_model; + GtkWidget *family_list, *style_list, *size_entry, *size_list; + GtkWidget *filter_buttons[4]; + int filter_flags; + tree234 *fonts_by_realname, *fonts_by_selorder; + fontinfo *selected; + int selsize; + int inhibit_response; /* inhibit callbacks when we change GUI controls */ +} unifontsel_internal; + +/* + * The structure held in the tree234s. All the string members are + * part of the same allocated area, so don't need freeing + * separately. + */ +struct fontinfo { + char *realname; + char *family, *charset, *style; + int size, flags; + /* + * Fallback sorting key, to permit multiple identical entries + * to exist in the selorder tree. + */ + int index; + /* + * Indices mapping fontinfo structures to indices in the list + * boxes. sizeindex is irrelevant if the font is scalable + * (size==0). + */ + int familyindex, styleindex, sizeindex; + /* + * The class of font. + */ + const struct unifont_vtable *fontclass; +}; + +static int fontinfo_realname_compare(void *av, void *bv) +{ + fontinfo *a = (fontinfo *)av; + fontinfo *b = (fontinfo *)bv; + return g_strcasecmp(a->realname, b->realname); +} + +static int fontinfo_realname_find(void *av, void *bv) +{ + const char *a = (const char *)av; + fontinfo *b = (fontinfo *)bv; + return g_strcasecmp(a, b->realname); +} + +static int strnullcasecmp(const char *a, const char *b) +{ + int i; + + /* + * If exactly one of the inputs is NULL, it compares before + * the other one. + */ + if ((i = (!b) - (!a)) != 0) + return i; + + /* + * NULL compares equal. + */ + if (!a) + return 0; + + /* + * Otherwise, ordinary strcasecmp. + */ + return g_strcasecmp(a, b); +} + +static int fontinfo_selorder_compare(void *av, void *bv) +{ + fontinfo *a = (fontinfo *)av; + fontinfo *b = (fontinfo *)bv; + int i; + if ((i = strnullcasecmp(a->family, b->family)) != 0) + return i; + if ((i = strnullcasecmp(a->charset, b->charset)) != 0) + return i; + if ((i = strnullcasecmp(a->style, b->style)) != 0) + return i; + if (a->size != b->size) + return (a->size < b->size ? -1 : +1); + if (a->index != b->index) + return (a->index < b->index ? -1 : +1); + return 0; +} + +static void unifontsel_setup_familylist(unifontsel_internal *fs) +{ + GtkTreeIter iter; + int i, listindex, minpos = -1, maxpos = -1; + char *currfamily = NULL; + fontinfo *info; + + gtk_list_store_clear(fs->family_model); + listindex = 0; + + /* + * Search through the font tree for anything matching our + * current filter criteria. When we find one, add its font + * name to the list box. + */ + for (i = 0 ;; i++) { + info = (fontinfo *)index234(fs->fonts_by_selorder, i); + /* + * info may be NULL if we've just run off the end of the + * tree. We must still do a processing pass in that + * situation, in case we had an unfinished font record in + * progress. + */ + if (info && (info->flags &~ fs->filter_flags)) { + info->familyindex = -1; + continue; /* we're filtering out this font */ + } + if (!info || strnullcasecmp(currfamily, info->family)) { + /* + * We've either finished a family, or started a new + * one, or both. + */ + if (currfamily) { + gtk_list_store_append(fs->family_model, &iter); + gtk_list_store_set(fs->family_model, &iter, + 0, currfamily, 1, minpos, 2, maxpos+1, -1); + listindex++; + } + if (info) { + minpos = i; + currfamily = info->family; + } + } + if (!info) + break; /* now we're done */ + info->familyindex = listindex; + maxpos = i; + } +} + +static void unifontsel_setup_stylelist(unifontsel_internal *fs, + int start, int end) +{ + GtkTreeIter iter; + int i, listindex, minpos = -1, maxpos = -1, started = FALSE; + char *currcs = NULL, *currstyle = NULL; + fontinfo *info; + + gtk_list_store_clear(fs->style_model); + listindex = 0; + started = FALSE; + + /* + * Search through the font tree for anything matching our + * current filter criteria. When we find one, add its charset + * and/or style name to the list box. + */ + for (i = start; i <= end; i++) { + if (i == end) + info = NULL; + else + info = (fontinfo *)index234(fs->fonts_by_selorder, i); + /* + * info may be NULL if we've just run off the end of the + * relevant data. We must still do a processing pass in + * that situation, in case we had an unfinished font + * record in progress. + */ + if (info && (info->flags &~ fs->filter_flags)) { + info->styleindex = -1; + continue; /* we're filtering out this font */ + } + if (!info || !started || strnullcasecmp(currcs, info->charset) || + strnullcasecmp(currstyle, info->style)) { + /* + * We've either finished a style/charset, or started a + * new one, or both. + */ + started = TRUE; + if (currstyle) { + gtk_list_store_append(fs->style_model, &iter); + gtk_list_store_set(fs->style_model, &iter, + 0, currstyle, 1, minpos, 2, maxpos+1, + 3, TRUE, -1); + listindex++; + } + if (info) { + minpos = i; + if (info->charset && strnullcasecmp(currcs, info->charset)) { + gtk_list_store_append(fs->style_model, &iter); + gtk_list_store_set(fs->style_model, &iter, + 0, info->charset, 1, -1, 2, -1, + 3, FALSE, -1); + listindex++; + } + currcs = info->charset; + currstyle = info->style; + } + } + if (!info) + break; /* now we're done */ + info->styleindex = listindex; + maxpos = i; + } +} + +static const int unifontsel_default_sizes[] = { 10, 12, 14, 16, 20, 24, 32 }; + +static void unifontsel_setup_sizelist(unifontsel_internal *fs, + int start, int end) +{ + GtkTreeIter iter; + int i, listindex; + char sizetext[40]; + fontinfo *info; + + gtk_list_store_clear(fs->size_model); + listindex = 0; + + /* + * Search through the font tree for anything matching our + * current filter criteria. When we find one, add its font + * name to the list box. + */ + for (i = start; i < end; i++) { + info = (fontinfo *)index234(fs->fonts_by_selorder, i); + if (info->flags &~ fs->filter_flags) { + info->sizeindex = -1; + continue; /* we're filtering out this font */ + } + if (info->size) { + sprintf(sizetext, "%d", info->size); + info->sizeindex = listindex; + gtk_list_store_append(fs->size_model, &iter); + gtk_list_store_set(fs->size_model, &iter, + 0, sizetext, 1, i, 2, info->size, -1); + listindex++; + } else { + int j; + + assert(i == start); + assert(i+1 == end); + + for (j = 0; j < lenof(unifontsel_default_sizes); j++) { + sprintf(sizetext, "%d", unifontsel_default_sizes[j]); + gtk_list_store_append(fs->size_model, &iter); + gtk_list_store_set(fs->size_model, &iter, 0, sizetext, 1, i, + 2, unifontsel_default_sizes[j], -1); + listindex++; + } + } + } +} + +static void unifontsel_set_filter_buttons(unifontsel_internal *fs) +{ + int i; + + for (i = 0; i < lenof(fs->filter_buttons); i++) { + int flagbit = GPOINTER_TO_INT(gtk_object_get_data + (GTK_OBJECT(fs->filter_buttons[i]), + "user-data")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fs->filter_buttons[i]), + !!(fs->filter_flags & flagbit)); + } +} + +static void unifontsel_select_font(unifontsel_internal *fs, + fontinfo *info, int size, int leftlist) +{ + int index; + int minval, maxval; + GtkTreePath *treepath; + GtkTreeIter iter; + + fs->inhibit_response = TRUE; + + fs->selected = info; + fs->selsize = size; + + /* + * Find the index of this fontinfo in the selorder list. + */ + index = -1; + findpos234(fs->fonts_by_selorder, info, NULL, &index); + assert(index >= 0); + + /* + * Adjust the font selector flags and redo the font family + * list box, if necessary. + */ + if (leftlist <= 0 && + (fs->filter_flags | info->flags) != fs->filter_flags) { + fs->filter_flags |= info->flags; + unifontsel_set_filter_buttons(fs); + unifontsel_setup_familylist(fs); + } + + /* + * Find the appropriate family name and select it in the list. + */ + assert(info->familyindex >= 0); + treepath = gtk_tree_path_new_from_indices(info->familyindex, -1); + gtk_tree_selection_select_path + (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->family_list)), + treepath); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->family_list), + treepath, NULL, FALSE, 0.0, 0.0); + gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), &iter, treepath); + gtk_tree_path_free(treepath); + + /* + * Now set up the font style list. + */ + gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter, + 1, &minval, 2, &maxval, -1); + if (leftlist <= 1) + unifontsel_setup_stylelist(fs, minval, maxval); + + /* + * Find the appropriate style name and select it in the list. + */ + if (info->style) { + assert(info->styleindex >= 0); + treepath = gtk_tree_path_new_from_indices(info->styleindex, -1); + gtk_tree_selection_select_path + (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->style_list)), + treepath); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->style_list), + treepath, NULL, FALSE, 0.0, 0.0); + gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->style_model), + &iter, treepath); + gtk_tree_path_free(treepath); + + /* + * And set up the size list. + */ + gtk_tree_model_get(GTK_TREE_MODEL(fs->style_model), &iter, + 1, &minval, 2, &maxval, -1); + if (leftlist <= 2) + unifontsel_setup_sizelist(fs, minval, maxval); + + /* + * Find the appropriate size, and select it in the list. + */ + if (info->size) { + assert(info->sizeindex >= 0); + treepath = gtk_tree_path_new_from_indices(info->sizeindex, -1); + gtk_tree_selection_select_path + (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->size_list)), + treepath); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list), + treepath, NULL, FALSE, 0.0, 0.0); + gtk_tree_path_free(treepath); + size = info->size; + } else { + int j; + for (j = 0; j < lenof(unifontsel_default_sizes); j++) + if (unifontsel_default_sizes[j] == size) { + treepath = gtk_tree_path_new_from_indices(j, -1); + gtk_tree_view_set_cursor(GTK_TREE_VIEW(fs->size_list), + treepath, NULL, FALSE); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list), + treepath, NULL, FALSE, 0.0, + 0.0); + gtk_tree_path_free(treepath); + } + } + + /* + * And set up the font size text entry box. + */ + { + char sizetext[40]; + sprintf(sizetext, "%d", size); + gtk_entry_set_text(GTK_ENTRY(fs->size_entry), sizetext); + } + } else { + if (leftlist <= 2) + unifontsel_setup_sizelist(fs, 0, 0); + gtk_entry_set_text(GTK_ENTRY(fs->size_entry), ""); + } + + /* + * Grey out the font size edit box if we're not using a + * scalable font. + */ + gtk_entry_set_editable(GTK_ENTRY(fs->size_entry), fs->selected->size == 0); + gtk_widget_set_sensitive(fs->size_entry, fs->selected->size == 0); + + fs->inhibit_response = FALSE; +} + +static void unifontsel_button_toggled(GtkToggleButton *tb, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + int newstate = gtk_toggle_button_get_active(tb); + int newflags; + int flagbit = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(tb), + "user-data")); + + if (newstate) + newflags = fs->filter_flags | flagbit; + else + newflags = fs->filter_flags & ~flagbit; + + if (fs->filter_flags != newflags) { + fs->filter_flags = newflags; + unifontsel_setup_familylist(fs); + } +} + +static void unifontsel_add_entry(void *ctx, const char *realfontname, + const char *family, const char *charset, + const char *style, int size, int flags, + const struct unifont_vtable *fontclass) +{ + unifontsel_internal *fs = (unifontsel_internal *)ctx; + fontinfo *info; + int totalsize; + char *p; + + totalsize = sizeof(fontinfo) + strlen(realfontname) + + (family ? strlen(family) : 0) + (charset ? strlen(charset) : 0) + + (style ? strlen(style) : 0) + 10; + info = (fontinfo *)smalloc(totalsize); + info->fontclass = fontclass; + p = (char *)info + sizeof(fontinfo); + info->realname = p; + strcpy(p, realfontname); + p += 1+strlen(p); + if (family) { + info->family = p; + strcpy(p, family); + p += 1+strlen(p); + } else + info->family = NULL; + if (charset) { + info->charset = p; + strcpy(p, charset); + p += 1+strlen(p); + } else + info->charset = NULL; + if (style) { + info->style = p; + strcpy(p, style); + p += 1+strlen(p); + } else + info->style = NULL; + assert(p - (char *)info <= totalsize); + info->size = size; + info->flags = flags; + info->index = count234(fs->fonts_by_selorder); + + /* + * It's just conceivable that a misbehaving font enumerator + * might tell us about the same font real name more than once, + * in which case we should silently drop the new one. + */ + if (add234(fs->fonts_by_realname, info) != info) { + sfree(info); + return; + } + /* + * However, we should never get a duplicate key in the + * selorder tree, because the index field carefully + * disambiguates otherwise identical records. + */ + add234(fs->fonts_by_selorder, info); +} + +static void family_changed(GtkTreeSelection *treeselection, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + GtkTreeModel *treemodel; + GtkTreeIter treeiter; + int minval; + fontinfo *info; + + if (fs->inhibit_response) /* we made this change ourselves */ + return; + + if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter)) + return; + + gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1); + info = (fontinfo *)index234(fs->fonts_by_selorder, minval); + unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize, 1); +} + +static void style_changed(GtkTreeSelection *treeselection, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + GtkTreeModel *treemodel; + GtkTreeIter treeiter; + int minval; + fontinfo *info; + + if (fs->inhibit_response) /* we made this change ourselves */ + return; + + if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter)) + return; + + gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1); + info = (fontinfo *)index234(fs->fonts_by_selorder, minval); + unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize, 2); +} + +static void size_changed(GtkTreeSelection *treeselection, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + GtkTreeModel *treemodel; + GtkTreeIter treeiter; + int minval, size; + fontinfo *info; + + if (fs->inhibit_response) /* we made this change ourselves */ + return; + + if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter)) + return; + + gtk_tree_model_get(treemodel, &treeiter, 1, &minval, 2, &size, -1); + info = (fontinfo *)index234(fs->fonts_by_selorder, minval); + unifontsel_select_font(fs, info, info->size ? info->size : size, 3); +} + +static void size_entry_changed(GtkEditable *ed, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + const char *text; + int size; + + if (fs->inhibit_response) /* we made this change ourselves */ + return; + + text = gtk_entry_get_text(GTK_ENTRY(ed)); + size = atoi(text); + + if (size > 0) { + assert(fs->selected->size == 0); + unifontsel_select_font(fs, fs->selected, size, 3); + } +} + +unifontsel *unifontsel_new(const char *wintitle) +{ + unifontsel_internal *fs = snew(unifontsel_internal); + GtkWidget *table, *label, *w, *scroll; + GtkListStore *model; + GtkTreeViewColumn *column; + int lists_height, font_width, style_width, size_width; + int i; + + fs->inhibit_response = FALSE; + + { + /* + * Invent some magic size constants. + */ + GtkRequisition req; + label = gtk_label_new("Quite Long Font Name (Foundry)"); + gtk_widget_size_request(label, &req); + font_width = req.width; + lists_height = 14 * req.height; + gtk_label_set_text(GTK_LABEL(label), "Italic Extra Condensed"); + gtk_widget_size_request(label, &req); + style_width = req.width; + gtk_label_set_text(GTK_LABEL(label), "48000"); + gtk_widget_size_request(label, &req); + size_width = req.width; + g_object_ref_sink(label); + g_object_unref(label); + } + + /* + * Create the dialog box and initialise the user-visible + * fields in the returned structure. + */ + fs->u.user_data = NULL; + fs->u.window = GTK_WINDOW(gtk_dialog_new()); + gtk_window_set_title(fs->u.window, wintitle); + fs->u.cancel_button = gtk_dialog_add_button + (GTK_DIALOG(fs->u.window), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); + fs->u.ok_button = gtk_dialog_add_button + (GTK_DIALOG(fs->u.window), GTK_STOCK_OK, GTK_RESPONSE_OK); + gtk_widget_grab_default(fs->u.ok_button); + + /* + * Now set up the internal fields, including in particular all + * the controls that actually allow the user to select fonts. + */ + table = gtk_table_new(3, 8, FALSE); + gtk_widget_show(table); + gtk_table_set_col_spacings(GTK_TABLE(table), 8); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fs->u.window)->vbox), + table, TRUE, TRUE, 0); + + label = gtk_label_new_with_mnemonic("_Font:"); + gtk_widget_show(label); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); + + /* + * The Font list box displays only a string, but additionally + * stores two integers which give the limits within the + * tree234 of the font entries covered by this list entry. + */ + model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT); + w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE); + gtk_label_set_mnemonic_widget(GTK_LABEL(label), w); + gtk_widget_show(w); + column = gtk_tree_view_column_new_with_attributes + ("Font", gtk_cell_renderer_text_new(), + "text", 0, (char *)NULL); + gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); + gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); + g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))), + "changed", G_CALLBACK(family_changed), fs); + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_container_add(GTK_CONTAINER(scroll), w); + gtk_widget_show(scroll); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_widget_set_size_request(scroll, font_width, lists_height); + gtk_table_attach(GTK_TABLE(table), scroll, 0, 1, 1, 3, GTK_FILL, 0, 0, 0); + fs->family_model = model; + fs->family_list = w; + + label = gtk_label_new_with_mnemonic("_Style:"); + gtk_widget_show(label); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); + gtk_table_attach(GTK_TABLE(table), label, 1, 2, 0, 1, GTK_FILL, 0, 0, 0); + + /* + * The Style list box can contain insensitive elements + * (character set headings for server-side fonts), so we add + * an extra column to the list store to hold that information. + */ + model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, + G_TYPE_BOOLEAN); + w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE); + gtk_label_set_mnemonic_widget(GTK_LABEL(label), w); + gtk_widget_show(w); + column = gtk_tree_view_column_new_with_attributes + ("Style", gtk_cell_renderer_text_new(), + "text", 0, "sensitive", 3, (char *)NULL); + gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); + gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); + g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))), + "changed", G_CALLBACK(style_changed), fs); + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_container_add(GTK_CONTAINER(scroll), w); + gtk_widget_show(scroll); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_widget_set_size_request(scroll, style_width, lists_height); + gtk_table_attach(GTK_TABLE(table), scroll, 1, 2, 1, 3, GTK_FILL, 0, 0, 0); + fs->style_model = model; + fs->style_list = w; + + label = gtk_label_new_with_mnemonic("Si_ze:"); + gtk_widget_show(label); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); + gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0); + + /* + * The Size label attaches primarily to a text input box so + * that the user can select a size of their choice. The list + * of available sizes is secondary. + */ + fs->size_entry = w = gtk_entry_new(); + gtk_label_set_mnemonic_widget(GTK_LABEL(label), w); + gtk_widget_set_size_request(w, size_width, -1); + gtk_widget_show(w); + gtk_table_attach(GTK_TABLE(table), w, 2, 3, 1, 2, GTK_FILL, 0, 0, 0); + g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(size_entry_changed), + fs); + + model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT); + w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE); + gtk_widget_show(w); + column = gtk_tree_view_column_new_with_attributes + ("Size", gtk_cell_renderer_text_new(), + "text", 0, (char *)NULL); + gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); + gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); + g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))), + "changed", G_CALLBACK(size_changed), fs); + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_container_add(GTK_CONTAINER(scroll), w); + gtk_widget_show(scroll); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_table_attach(GTK_TABLE(table), scroll, 2, 3, 2, 3, GTK_FILL, + GTK_EXPAND | GTK_FILL, 0, 0); + fs->size_model = model; + fs->size_list = w; + + /* + * FIXME: preview widget + */ + i = 0; + w = gtk_check_button_new_with_label("Show client-side fonts"); + gtk_object_set_data(GTK_OBJECT(w), "user-data", + GINT_TO_POINTER(FONTFLAG_CLIENTSIDE)); + gtk_signal_connect(GTK_OBJECT(w), "toggled", + GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs); + gtk_widget_show(w); + fs->filter_buttons[i++] = w; + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 4, 5, GTK_FILL, 0, 0, 0); + w = gtk_check_button_new_with_label("Show server-side fonts"); + gtk_object_set_data(GTK_OBJECT(w), "user-data", + GINT_TO_POINTER(FONTFLAG_SERVERSIDE)); + gtk_signal_connect(GTK_OBJECT(w), "toggled", + GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs); + gtk_widget_show(w); + fs->filter_buttons[i++] = w; + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 5, 6, GTK_FILL, 0, 0, 0); + w = gtk_check_button_new_with_label("Show server-side font aliases"); + gtk_object_set_data(GTK_OBJECT(w), "user-data", + GINT_TO_POINTER(FONTFLAG_SERVERALIAS)); + gtk_signal_connect(GTK_OBJECT(w), "toggled", + GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs); + gtk_widget_show(w); + fs->filter_buttons[i++] = w; + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 6, 7, GTK_FILL, 0, 0, 0); + w = gtk_check_button_new_with_label("Show non-monospaced fonts"); + gtk_object_set_data(GTK_OBJECT(w), "user-data", + GINT_TO_POINTER(FONTFLAG_NONMONOSPACED)); + gtk_signal_connect(GTK_OBJECT(w), "toggled", + GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs); + gtk_widget_show(w); + fs->filter_buttons[i++] = w; + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 7, 8, GTK_FILL, 0, 0, 0); + + assert(i == lenof(fs->filter_buttons)); + fs->filter_flags = FONTFLAG_CLIENTSIDE | FONTFLAG_SERVERSIDE; + unifontsel_set_filter_buttons(fs); + + /* + * Go and find all the font names, and set up our master font + * list. + */ + fs->fonts_by_realname = newtree234(fontinfo_realname_compare); + fs->fonts_by_selorder = newtree234(fontinfo_selorder_compare); + for (i = 0; i < lenof(unifont_types); i++) + unifont_types[i]->enum_fonts(GTK_WIDGET(fs->u.window), + unifontsel_add_entry, fs); + + /* + * And set up the initial font names list. + */ + unifontsel_setup_familylist(fs); + + fs->selected = NULL; + + return (unifontsel *)fs; +} + +void unifontsel_destroy(unifontsel *fontsel) +{ + unifontsel_internal *fs = (unifontsel_internal *)fontsel; + fontinfo *info; + + freetree234(fs->fonts_by_selorder); + while ((info = delpos234(fs->fonts_by_realname, 0)) != NULL) + sfree(info); + freetree234(fs->fonts_by_realname); + + gtk_widget_destroy(GTK_WIDGET(fs->u.window)); + sfree(fs); +} + +void unifontsel_set_name(unifontsel *fontsel, const char *fontname) +{ + unifontsel_internal *fs = (unifontsel_internal *)fontsel; + int i, start, end, size; + const char *fontname2; + fontinfo *info; + + /* + * Provide a default if given an empty or null font name. + */ + if (!fontname || !*fontname) + fontname = "fixed"; /* Pango zealots might prefer "Monospace 12" */ + + /* + * Call the canonify_fontname function. + */ + fontname = unifont_do_prefix(fontname, &start, &end); + for (i = start; i < end; i++) { + fontname2 = unifont_types[i]->canonify_fontname + (GTK_WIDGET(fs->u.window), fontname, &size); + if (fontname2) + break; + } + if (i == end) + return; /* font name not recognised */ + + /* + * Now look up the canonified font name in our index. + */ + info = find234(fs->fonts_by_realname, (char *)fontname2, + fontinfo_realname_find); + + /* + * If we've found the font, and its size field is either + * correct or zero (the latter indicating a scalable font), + * then we're done. Otherwise, try looking up the original + * font name instead. + */ + if (!info || (info->size != size && info->size != 0)) { + info = find234(fs->fonts_by_realname, (char *)fontname, + fontinfo_realname_find); + if (!info || info->size != size) + return; /* font name not in our index */ + } + + /* + * Now we've got a fontinfo structure and a font size, so we + * know everything we need to fill in all the fields in the + * dialog. + */ + unifontsel_select_font(fs, info, size, 0); +} + +char *unifontsel_get_name(unifontsel *fontsel) +{ + unifontsel_internal *fs = (unifontsel_internal *)fontsel; + char *name; + + assert(fs->selected); + + if (fs->selected->size == 0) { + name = fs->selected->fontclass->scale_fontname + (GTK_WIDGET(fs->u.window), fs->selected->realname, fs->selsize); + if (name) + return name; + } + + return dupstr(fs->selected->realname); +} diff --git a/unix/gtkfont.h b/unix/gtkfont.h index 5c36fee5..f414594e 100644 --- a/unix/gtkfont.h +++ b/unix/gtkfont.h @@ -36,11 +36,32 @@ typedef struct unifont { int width, height, ascent, descent; } unifont; -unifont *unifont_create(GtkWidget *widget, char *name, int wide, int bold, +unifont *unifont_create(GtkWidget *widget, const char *name, + int wide, int bold, int shadowoffset, int shadowalways); void unifont_destroy(unifont *font); void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, int x, int y, const char *string, int len, int wide, int bold, int cellwidth); +/* + * Unified font selector dialog. I can't be bothered to do a + * proper GTK subclassing today, so this will just be an ordinary + * data structure with some useful members. + * + * (Of course, these aren't the only members; this structure is + * contained within a bigger one which holds data visible only to + * the implementation.) + */ +typedef struct unifontsel { + void *user_data; /* settable by the user */ + GtkWindow *window; + GtkWidget *ok_button, *cancel_button; +} unifontsel; + +unifontsel *unifontsel_new(const char *wintitle); +void unifontsel_destroy(unifontsel *fontsel); +void unifontsel_set_name(unifontsel *fontsel, const char *fontname); +char *unifontsel_get_name(unifontsel *fontsel); + #endif /* PUTTY_GTKFONT_H */ From 397dcf5baefb2075eff97039047dfcfe09adc88e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 26 Mar 2008 18:16:52 +0000 Subject: [PATCH 17/53] Placate optimiser. [originally from svn r7939] --- unix/gtkfont.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unix/gtkfont.c b/unix/gtkfont.c index 19f9bafd..35ca48ab 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -1843,7 +1843,7 @@ void unifontsel_set_name(unifontsel *fontsel, const char *fontname) { unifontsel_internal *fs = (unifontsel_internal *)fontsel; int i, start, end, size; - const char *fontname2; + const char *fontname2 = NULL; fontinfo *info; /* From b4b9b8a00efb084604cc1c46467c6c5dad8d9290 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 26 Mar 2008 18:30:20 +0000 Subject: [PATCH 18/53] Add ifdefs for older versions of GTK2 and Pango. Unfortunately, the latter require manual input to the Makefile, since the Pango developers in their unbounded wisdom (that is, unbounded below) didn't bother to start providing the PANGO_VERSION macros until release 1.16 - ten releases _after_ everything I'm trying to check! [originally from svn r7940] --- unix/gtkdlg.c | 2 ++ unix/gtkfont.c | 54 ++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index 0c248779..73d734e6 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -2161,7 +2161,9 @@ void set_dialog_action_area(GtkDialog *dlg, GtkWidget *w) * of the time (one separating the tree view from the main * controls, and another for the main controls themselves). */ +#if GTK_CHECK_VERSION(2,4,0) gtk_alignment_set_padding(GTK_ALIGNMENT(align), 8, 8, 8, 8); +#endif gtk_widget_show(align); gtk_box_pack_end(GTK_BOX(dlg->vbox), align, FALSE, TRUE, 0); w = gtk_hseparator_new(); diff --git a/unix/gtkfont.c b/unix/gtkfont.c index 35ca48ab..8d491ab8 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -653,7 +653,9 @@ static unifont *pangofont_create(GtkWidget *widget, const char *name, { struct pangofont *pfont; PangoContext *ctx; +#ifndef PANGO_PRE_1POINT6 PangoFontMap *map; +#endif PangoFontDescription *desc; PangoFontset *fset; PangoFontMetrics *metrics; @@ -666,6 +668,7 @@ static unifont *pangofont_create(GtkWidget *widget, const char *name, pango_font_description_free(desc); return NULL; } +#ifndef PANGO_PRE_1POINT6 map = pango_context_get_font_map(ctx); if (!map) { pango_font_description_free(desc); @@ -673,6 +676,10 @@ static unifont *pangofont_create(GtkWidget *widget, const char *name, } fset = pango_font_map_load_fontset(map, ctx, desc, pango_context_get_language(ctx)); +#else + fset = pango_context_load_fontset(ctx, desc, + pango_context_get_language(ctx)); +#endif if (!fset) { pango_font_description_free(desc); return NULL; @@ -781,25 +788,28 @@ static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, void *callback_ctx) { PangoContext *ctx; +#ifndef PANGO_PRE_1POINT6 PangoFontMap *map; +#endif PangoFontFamily **families; int i, nfamilies; - /* - * Find the active font map. - */ ctx = gtk_widget_get_pango_context(widget); if (!ctx) return; + + /* + * Ask Pango for a list of font families, and iterate through + * them. + */ +#ifndef PANGO_PRE_1POINT6 map = pango_context_get_font_map(ctx); if (!map) return; - - /* - * Ask the font map for a list of font families, and iterate - * through them. - */ pango_font_map_list_families(map, &families, &nfamilies); +#else + pango_context_list_families(ctx, &families, &nfamilies); +#endif for (i = 0; i < nfamilies; i++) { PangoFontFamily *family = families[i]; const char *familyname; @@ -812,8 +822,14 @@ static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, * string. */ flags = FONTFLAG_CLIENTSIDE; +#ifndef PANGO_PRE_1POINT4 + /* + * In very early versions of Pango, we can't tell + * monospaced fonts from non-monospaced. + */ if (!pango_font_family_is_monospace(family)) flags |= FONTFLAG_NONMONOSPACED; +#endif familyname = pango_font_family_get_name(family); /* @@ -843,7 +859,16 @@ static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, /* * See if this font has a list of specific sizes. */ +#ifndef PANGO_PRE_1POINT4 pango_font_face_list_sizes(face, &sizes, &nsizes); +#else + /* + * In early versions of Pango, that call wasn't + * supported; we just have to assume everything is + * scalable. + */ + sizes = NULL; +#endif if (!sizes) { /* * Write a single entry with a dummy size. @@ -893,7 +918,9 @@ static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name, * extract its original size (in pixels) into the `size' field. */ PangoContext *ctx; +#ifndef PANGO_PRE_1POINT6 PangoFontMap *map; +#endif PangoFontDescription *desc; PangoFontset *fset; PangoFontMetrics *metrics; @@ -907,6 +934,7 @@ static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name, pango_font_description_free(desc); return NULL; } +#ifndef PANGO_PRE_1POINT6 map = pango_context_get_font_map(ctx); if (!map) { pango_font_description_free(desc); @@ -914,6 +942,10 @@ static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name, } fset = pango_font_map_load_fontset(map, ctx, desc, pango_context_get_language(ctx)); +#else + fset = pango_context_load_fontset(ctx, desc, + pango_context_get_language(ctx)); +#endif if (!fset) { pango_font_description_free(desc); return NULL; @@ -1563,6 +1595,8 @@ static void style_changed(GtkTreeSelection *treeselection, gpointer data) return; gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1); + if (minval < 0) + return; /* somehow a charset heading got clicked */ info = (fontinfo *)index234(fs->fonts_by_selorder, minval); unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize, 2); } @@ -1630,8 +1664,12 @@ unifontsel *unifontsel_new(const char *wintitle) gtk_label_set_text(GTK_LABEL(label), "48000"); gtk_widget_size_request(label, &req); size_width = req.width; +#if GTK_CHECK_VERSION(2,10,0) g_object_ref_sink(label); g_object_unref(label); +#else + gtk_object_sink(GTK_OBJECT(label)); +#endif } /* From 95a5116dbf46d8b21f39e0f1bc3028c70eee5dcf Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 26 Mar 2008 20:20:25 +0000 Subject: [PATCH 19/53] Sort the styles of Pango font families into a sensible order, instead of alphabetical order. This is more than cosmetic: it's important because the first one in the list is selected by default. [originally from svn r7941] --- unix/gtkfont.c | 54 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/unix/gtkfont.c b/unix/gtkfont.c index 8d491ab8..00348fb8 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -46,8 +46,7 @@ * - work out why the list boxes don't go all the way to the RHS * of the dialog box * - * - develop a sensible sorting order for the font styles - - * Regular / Roman / non-bold-or-italic should come at the top! + * - construct a stylekey for X11 fonts * * - big testing and shakedown! */ @@ -90,7 +89,8 @@ typedef void (*fontsel_add_entry)(void *ctx, const char *realfontname, const char *family, const char *charset, - const char *style, int size, int flags, + const char *style, const char *stylekey, + int size, int flags, const struct unifont_vtable *fontclass); struct unifont_vtable { @@ -536,7 +536,7 @@ static void x11font_enum_fonts(GtkWidget *widget, */ if (fontsize) callback(callback_ctx, fontnames[i], font, charset, - style, fontsize, flags, &x11font_vtable); + style, NULL, fontsize, flags, &x11font_vtable); } else { /* * This isn't an XLFD, so it must be an alias. @@ -547,7 +547,7 @@ static void x11font_enum_fonts(GtkWidget *widget, * anything but computationally hideous. Ah well. */ callback(callback_ctx, fontnames[i], fontnames[i], NULL, - NULL, 0, FONTFLAG_SERVERALIAS, &x11font_vtable); + NULL, NULL, 0, FONTFLAG_SERVERALIAS, &x11font_vtable); } } XFreeFontNames(fontnames); @@ -883,17 +883,46 @@ static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, */ for (k = 0; k < nsizes; k++) { char *fullname; + char stylekey[128]; pango_font_description_set_size(desc, sizes[k]); fullname = pango_font_description_to_string(desc); + /* + * Construct the sorting key for font styles. + */ + { + char *p = stylekey; + int n; + + n = pango_font_description_get_weight(desc); + /* Weight: normal, then lighter, then bolder */ + if (n <= PANGO_WEIGHT_NORMAL) + n = PANGO_WEIGHT_NORMAL - n; + p += sprintf(p, "%4d", n); + + n = pango_font_description_get_style(desc); + p += sprintf(p, " %2d", n); + + n = pango_font_description_get_stretch(desc); + /* Stretch: closer to normal sorts earlier */ + n = 2 * abs(PANGO_STRETCH_NORMAL - n) + + (n < PANGO_STRETCH_NORMAL); + p += sprintf(p, " %2d", n); + + n = pango_font_description_get_variant(desc); + p += sprintf(p, " %2d", n); + + } + /* * Got everything. Hand off to the callback. * (The charset string is NULL, because only * server-side X fonts use it.) */ callback(callback_ctx, fullname, familyname, NULL, facename, + stylekey, (sizes == &dummysize ? 0 : PANGO_PIXELS(sizes[k])), flags, &pangofont_vtable); @@ -1106,7 +1135,7 @@ typedef struct unifontsel_internal { */ struct fontinfo { char *realname; - char *family, *charset, *style; + char *family, *charset, *style, *stylekey; int size, flags; /* * Fallback sorting key, to permit multiple identical entries @@ -1171,6 +1200,8 @@ static int fontinfo_selorder_compare(void *av, void *bv) return i; if ((i = strnullcasecmp(a->charset, b->charset)) != 0) return i; + if ((i = strnullcasecmp(a->stylekey, b->stylekey)) != 0) + return i; if ((i = strnullcasecmp(a->style, b->style)) != 0) return i; if (a->size != b->size) @@ -1504,7 +1535,8 @@ static void unifontsel_button_toggled(GtkToggleButton *tb, gpointer data) static void unifontsel_add_entry(void *ctx, const char *realfontname, const char *family, const char *charset, - const char *style, int size, int flags, + const char *style, const char *stylekey, + int size, int flags, const struct unifont_vtable *fontclass) { unifontsel_internal *fs = (unifontsel_internal *)ctx; @@ -1514,7 +1546,7 @@ static void unifontsel_add_entry(void *ctx, const char *realfontname, totalsize = sizeof(fontinfo) + strlen(realfontname) + (family ? strlen(family) : 0) + (charset ? strlen(charset) : 0) + - (style ? strlen(style) : 0) + 10; + (style ? strlen(style) : 0) + (stylekey ? strlen(stylekey) : 0) + 10; info = (fontinfo *)smalloc(totalsize); info->fontclass = fontclass; p = (char *)info + sizeof(fontinfo); @@ -1539,6 +1571,12 @@ static void unifontsel_add_entry(void *ctx, const char *realfontname, p += 1+strlen(p); } else info->style = NULL; + if (stylekey) { + info->stylekey = p; + strcpy(p, stylekey); + p += 1+strlen(p); + } else + info->stylekey = NULL; assert(p - (char *)info <= totalsize); info->size = size; info->flags = flags; From f0b78b8869566752d55696cc181cf7e733b1b749 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 26 Mar 2008 21:33:10 +0000 Subject: [PATCH 20/53] Tune the sorting of the style list box for X fonts. [originally from svn r7942] --- unix/gtkfont.c | 59 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/unix/gtkfont.c b/unix/gtkfont.c index 00348fb8..654dd141 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -46,8 +46,6 @@ * - work out why the list boxes don't go all the way to the RHS * of the dialog box * - * - construct a stylekey for X11 fonts - * * - big testing and shakedown! */ @@ -452,10 +450,11 @@ static void x11font_enum_fonts(GtkWidget *widget, * we'll be using in the font selector. */ char *components[14]; - char *p, *font, *style, *charset; - int j, thistmpsize, fontsize, flags; + char *p, *font, *style, *stylekey, *charset; + int j, weightkey, slantkey, setwidthkey; + int thistmpsize, fontsize, flags; - thistmpsize = 3 * strlen(fontnames[i]) + 256; + thistmpsize = 4 * strlen(fontnames[i]) + 256; if (tmpsize < thistmpsize) { tmpsize = thistmpsize; tmp = sresize(tmp, tmpsize, char); @@ -488,10 +487,8 @@ static void x11font_enum_fonts(GtkWidget *widget, p += 1 + sprintf(p, "%s-%s", components[12], components[13]); /* - * Style is a mixture of the weight, slant, set_width - * and spacing fields (respectively 2, 3, 4 and 10) - * with some strange formatting. (Again, cribbed - * entirely from the GTK 1.2 font selector.) + * Style is a mixture of quite a lot of the fields, + * with some strange formatting. */ style = p; p += sprintf(p, "%s", components[2][0] ? components[2] : @@ -512,6 +509,48 @@ static void x11font_enum_fonts(GtkWidget *widget, p += sprintf(p, " [M]"); if (!g_strcasecmp(components[10], "c")) p += sprintf(p, " [C]"); + if (components[5][0]) + p += sprintf(p, " %s", components[5]); + + /* + * Style key is the same stuff as above, but with a + * couple of transformations done on it to make it + * sort more sensibly. + */ + p++; + stylekey = p; + if (!g_strcasecmp(components[2], "medium") || + !g_strcasecmp(components[2], "regular") || + !g_strcasecmp(components[2], "normal") || + !g_strcasecmp(components[2], "book")) + weightkey = 0; + else if (!g_strncasecmp(components[2], "demi", 4) || + !g_strncasecmp(components[2], "semi", 4)) + weightkey = 1; + else + weightkey = 2; + if (!g_strcasecmp(components[3], "r")) + slantkey = 0; + else if (!g_strncasecmp(components[3], "r", 1)) + slantkey = 2; + else + slantkey = 1; + if (!g_strcasecmp(components[4], "normal")) + setwidthkey = 0; + else + setwidthkey = 1; + + p += sprintf(p, "%04d%04d%s%04d%04d%s%04d%04d%s%04d%s%04d%s", + weightkey, + strlen(components[2]), components[2], + slantkey, + strlen(components[3]), components[3], + setwidthkey, + strlen(components[4]), components[4], + strlen(components[10]), components[10], + strlen(components[5]), components[5]); + + assert(p - tmp < thistmpsize); /* * Size is in pixels, for our application, so we @@ -536,7 +575,7 @@ static void x11font_enum_fonts(GtkWidget *widget, */ if (fontsize) callback(callback_ctx, fontnames[i], font, charset, - style, NULL, fontsize, flags, &x11font_vtable); + style, stylekey, fontsize, flags, &x11font_vtable); } else { /* * This isn't an XLFD, so it must be an alias. From bb80007da4edcebd0d7d90d9b303429b4619043d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 27 Mar 2008 19:41:08 +0000 Subject: [PATCH 21/53] More consistent handling of X11 font aliases: we now don't resolve them automatically. If the user selects an alias in the font selector, they get that alias copied literally into the output font name string; when they return to the font selector, the alias is still selected. We still _can_ resolve aliases, but we only do it on demand: double-clicking one in the list box will do the job. [originally from svn r7944] --- unix/gtkfont.c | 209 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 184 insertions(+), 25 deletions(-) diff --git a/unix/gtkfont.c b/unix/gtkfont.c index 654dd141..1718cbee 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -27,25 +27,30 @@ * TODO on fontsel * --------------- * - * - implement the preview pane - * - * - extend the font style language for X11 fonts so that we - * never get unexplained double size elements? Or, at least, so - * that _my_ font collection never produces them; that'd be a - * decent start. - * - * - decide what _should_ happen about font aliases. Should we - * resolve them as soon as they're clicked? Or be able to - * resolve them on demand, er, somehow? Or resolve them on exit - * from the function? Or what? If we resolve on demand, should - * we stop canonifying them on input, on the basis that we'd - * prefer to let the user _tell_ us when to canonify them? + * - preview pane fixes: + * + better centring of text? (Garuda is a particularly oddly + * behaved font in this respect; are its metrics going weird + * for some reason?) + * + pick a better few lines of preview text. Should take care + * to use wide letters (MWmw) and narrow ones (ij). + * + resize to fit? * * - think about points versus pixels, harder than I already have * * - work out why the list boxes don't go all the way to the RHS * of the dialog box * + * - consistency and updating issues: + * + the preview pane is empty when the dialog comes up + * + we should attempt to preserve font size when switching + * font family and style (it's currently OK as long as we're + * moving between scalable fonts but falls down badly + * otherwise) + * + * - generalised style and padding polish + * + gtk_scrolled_window_set_shadow_type(foo, GTK_SHADOW_IN); + * might be worth considering + * * - big testing and shakedown! */ @@ -103,7 +108,8 @@ struct unifont_vtable { int bold, int cellwidth); void (*enum_fonts)(GtkWidget *widget, fontsel_add_entry callback, void *callback_ctx); - char *(*canonify_fontname)(GtkWidget *widget, const char *name, int *size); + char *(*canonify_fontname)(GtkWidget *widget, const char *name, int *size, + int resolve_aliases); char *(*scale_fontname)(GtkWidget *widget, const char *name, int size); /* @@ -126,7 +132,7 @@ static void x11font_destroy(unifont *font); static void x11font_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, void *callback_ctx); static char *x11font_canonify_fontname(GtkWidget *widget, const char *name, - int *size); + int *size, int resolve_aliases); static char *x11font_scale_fontname(GtkWidget *widget, const char *name, int size); @@ -593,14 +599,18 @@ static void x11font_enum_fonts(GtkWidget *widget, } static char *x11font_canonify_fontname(GtkWidget *widget, const char *name, - int *size) + int *size, int resolve_aliases) { /* * When given an X11 font name to try to make sense of for a * font selector, we must attempt to load it (to see if it * exists), and then canonify it by extracting its FONT * property, which should give its full XLFD even if what we - * originally had was an alias. + * originally had was a wildcard. + * + * However, we must carefully avoid canonifying font + * _aliases_, unless specifically asked to, because the font + * selector treats them as worthwhile in their own right. */ GdkFont *font = gdk_font_load(name); XFontStruct *xfs; @@ -618,15 +628,16 @@ static char *x11font_canonify_fontname(GtkWidget *widget, const char *name, fontprop = XInternAtom(disp, "FONT", False); if (XGetFontProperty(xfs, fontprop, &ret)) { - char *name = XGetAtomName(disp, (Atom)ret); - if (name) { + char *newname = XGetAtomName(disp, (Atom)ret); + if (newname) { unsigned long fsize = 12; fontprop2 = XInternAtom(disp, "PIXEL_SIZE", False); - if (XGetFontProperty(xfs, fontprop2, &fsize)) { + if (XGetFontProperty(xfs, fontprop2, &fsize) && fsize > 0) { *size = fsize; gdk_font_unref(font); - return dupstr(name); + return dupstr(name[0] == '-' || resolve_aliases ? + newname : name); } } } @@ -655,7 +666,7 @@ static void pangofont_destroy(unifont *font); static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, void *callback_ctx); static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name, - int *size); + int *size, int resolve_aliases); static char *pangofont_scale_fontname(GtkWidget *widget, const char *name, int size); @@ -978,7 +989,7 @@ static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, } static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name, - int *size) + int *size, int resolve_aliases) { /* * When given a Pango font name to try to make sense of for a @@ -1160,6 +1171,10 @@ typedef struct unifontsel_internal { GtkListStore *family_model, *style_model, *size_model; GtkWidget *family_list, *style_list, *size_entry, *size_list; GtkWidget *filter_buttons[4]; + GtkWidget *preview_area; + GdkPixmap *preview_pixmap; + int preview_width, preview_height; + GdkColor preview_fg, preview_bg; int filter_flags; tree234 *fonts_by_realname, *fonts_by_selorder; fontinfo *selected; @@ -1550,6 +1565,38 @@ static void unifontsel_select_font(unifontsel_internal *fs, gtk_entry_set_editable(GTK_ENTRY(fs->size_entry), fs->selected->size == 0); gtk_widget_set_sensitive(fs->size_entry, fs->selected->size == 0); + /* + * Instantiate the font and draw some preview text. + */ + { + unifont *font; + char *sizename; + + sizename = info->fontclass->scale_fontname + (GTK_WIDGET(fs->u.window), info->realname, fs->selsize); + + font = info->fontclass->create(GTK_WIDGET(fs->u.window), + sizename ? sizename : info->realname, + FALSE, FALSE, 0, 0); + if (font && fs->preview_pixmap) { + GdkGC *gc = gdk_gc_new(fs->preview_pixmap); + gdk_gc_set_foreground(gc, &fs->preview_bg); + gdk_draw_rectangle(fs->preview_pixmap, gc, 1, 0, 0, + fs->preview_width, fs->preview_height); + gdk_gc_set_foreground(gc, &fs->preview_fg); + info->fontclass->draw_text(fs->preview_pixmap, gc, font, + font->width, font->height, + "hello, world", 12, + FALSE, FALSE, font->width); + gdk_gc_unref(gc); + gdk_window_invalidate_rect(fs->preview_area->window, NULL, FALSE); + } + + info->fontclass->destroy(font); + + sfree(sizename); + } + fs->inhibit_response = FALSE; } @@ -1715,13 +1762,99 @@ static void size_entry_changed(GtkEditable *ed, gpointer data) } } +static void alias_resolve(GtkTreeView *treeview, GtkTreePath *path, + GtkTreeViewColumn *column, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + GtkTreeIter iter; + int minval, newsize; + fontinfo *info, *newinfo; + char *newname; + + if (fs->inhibit_response) /* we made this change ourselves */ + return; + + gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), &iter, path); + gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter, 1,&minval, -1); + info = (fontinfo *)index234(fs->fonts_by_selorder, minval); + if (info) { + newname = info->fontclass->canonify_fontname + (GTK_WIDGET(fs->u.window), info->realname, &newsize, TRUE); + newinfo = find234(fs->fonts_by_realname, (char *)newname, + fontinfo_realname_find); + sfree(newname); + if (!newinfo) + return; /* font name not in our index */ + if (newinfo == info) + return; /* didn't change under canonification => not an alias */ + unifontsel_select_font(fs, newinfo, + newinfo->size ? newinfo->size : newsize, 1); + } +} + +static gint unifontsel_expose_area(GtkWidget *widget, GdkEventExpose *event, + gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + + if (fs->preview_pixmap) { + gdk_draw_pixmap(widget->window, + widget->style->fg_gc[GTK_WIDGET_STATE(widget)], + fs->preview_pixmap, + event->area.x, event->area.y, + event->area.x, event->area.y, + event->area.width, event->area.height); + } + return TRUE; +} + +static gint unifontsel_configure_area(GtkWidget *widget, + GdkEventConfigure *event, gpointer data) +{ + unifontsel_internal *fs = (unifontsel_internal *)data; + GdkGC *gc; + int ox, oy, nx, ny, x, y; + + /* + * Enlarge the pixmap, but never shrink it. + */ + ox = fs->preview_width; + oy = fs->preview_height; + x = event->width; + y = event->height; + if (x > ox || y > oy) { + GdkPixmap *newpm; + + nx = (x > ox ? x : ox); + ny = (y > oy ? y : oy); + newpm = gdk_pixmap_new(widget->window, nx, ny, -1); + + gc = gdk_gc_new(newpm); + gdk_gc_set_foreground(gc, &fs->preview_bg); + gdk_draw_rectangle(newpm, gc, 1, 0, 0, nx, ny); + if (fs->preview_pixmap) { + gdk_draw_pixmap(newpm, gc, fs->preview_pixmap, 0, 0, 0, 0, ox, oy); + gdk_pixmap_unref(fs->preview_pixmap); + } + gdk_gc_unref(gc); + + fs->preview_pixmap = newpm; + fs->preview_width = nx; + fs->preview_height = ny; + } + + gdk_window_invalidate_rect(widget->window, NULL, FALSE); + + return TRUE; +} + unifontsel *unifontsel_new(const char *wintitle) { unifontsel_internal *fs = snew(unifontsel_internal); GtkWidget *table, *label, *w, *scroll; GtkListStore *model; GtkTreeViewColumn *column; - int lists_height, font_width, style_width, size_width; + int lists_height, preview_height, font_width, style_width, size_width; int i; fs->inhibit_response = FALSE; @@ -1735,6 +1868,7 @@ unifontsel *unifontsel_new(const char *wintitle) gtk_widget_size_request(label, &req); font_width = req.width; lists_height = 14 * req.height; + preview_height = 5 * req.height; gtk_label_set_text(GTK_LABEL(label), "Italic Extra Condensed"); gtk_widget_size_request(label, &req); style_width = req.width; @@ -1794,6 +1928,8 @@ unifontsel *unifontsel_new(const char *wintitle) gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))), "changed", G_CALLBACK(family_changed), fs); + g_signal_connect(G_OBJECT(w), "row-activated", + G_CALLBACK(alias_resolve), fs); scroll = gtk_scrolled_window_new(NULL, NULL); gtk_container_add(GTK_CONTAINER(scroll), w); @@ -1879,6 +2015,26 @@ unifontsel *unifontsel_new(const char *wintitle) fs->size_model = model; fs->size_list = w; + fs->preview_area = gtk_drawing_area_new(); + fs->preview_pixmap = NULL; + fs->preview_width = 0; + fs->preview_height = 0; + fs->preview_fg.pixel = fs->preview_bg.pixel = 0; + fs->preview_fg.red = fs->preview_fg.green = fs->preview_fg.blue = 0x0000; + fs->preview_bg.red = fs->preview_bg.green = fs->preview_bg.blue = 0xFFFF; + gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_fg, + FALSE, FALSE); + gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_bg, + FALSE, FALSE); + gtk_signal_connect(GTK_OBJECT(fs->preview_area), "expose_event", + GTK_SIGNAL_FUNC(unifontsel_expose_area), fs); + gtk_signal_connect(GTK_OBJECT(fs->preview_area), "configure_event", + GTK_SIGNAL_FUNC(unifontsel_configure_area), fs); + gtk_widget_set_size_request(fs->preview_area, 1, preview_height); + gtk_widget_show(fs->preview_area); + gtk_table_attach(GTK_TABLE(table), fs->preview_area, 0, 3, 3, 4, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + /* * FIXME: preview widget */ @@ -1945,6 +2101,9 @@ void unifontsel_destroy(unifontsel *fontsel) unifontsel_internal *fs = (unifontsel_internal *)fontsel; fontinfo *info; + if (fs->preview_pixmap) + gdk_pixmap_unref(fs->preview_pixmap); + freetree234(fs->fonts_by_selorder); while ((info = delpos234(fs->fonts_by_realname, 0)) != NULL) sfree(info); @@ -1973,7 +2132,7 @@ void unifontsel_set_name(unifontsel *fontsel, const char *fontname) fontname = unifont_do_prefix(fontname, &start, &end); for (i = start; i < end; i++) { fontname2 = unifont_types[i]->canonify_fontname - (GTK_WIDGET(fs->u.window), fontname, &size); + (GTK_WIDGET(fs->u.window), fontname, &size, FALSE); if (fontname2) break; } From 638e350e1f3473766ba482288702f19ab056e2dc Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 27 Mar 2008 19:53:28 +0000 Subject: [PATCH 22/53] Move the font-preview updating code out into a separate function so we can call it both when the drawing area changes size and when the selected font changes. As a result, the preview pane doesn't start off blank any more. [originally from svn r7945] --- unix/gtkfont.c | 85 ++++++++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 47 deletions(-) diff --git a/unix/gtkfont.c b/unix/gtkfont.c index 1718cbee..4d4a2aab 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -41,7 +41,6 @@ * of the dialog box * * - consistency and updating issues: - * + the preview pane is empty when the dialog comes up * + we should attempt to preserve font size when switching * font family and style (it's currently OK as long as we're * moving between scalable fonts but falls down badly @@ -1442,6 +1441,37 @@ static void unifontsel_set_filter_buttons(unifontsel_internal *fs) } } +static void unifontsel_draw_preview_text(unifontsel_internal *fs) +{ + unifont *font; + char *sizename; + fontinfo *info = fs->selected; + + sizename = info->fontclass->scale_fontname + (GTK_WIDGET(fs->u.window), info->realname, fs->selsize); + + font = info->fontclass->create(GTK_WIDGET(fs->u.window), + sizename ? sizename : info->realname, + FALSE, FALSE, 0, 0); + if (font && fs->preview_pixmap) { + GdkGC *gc = gdk_gc_new(fs->preview_pixmap); + gdk_gc_set_foreground(gc, &fs->preview_bg); + gdk_draw_rectangle(fs->preview_pixmap, gc, 1, 0, 0, + fs->preview_width, fs->preview_height); + gdk_gc_set_foreground(gc, &fs->preview_fg); + info->fontclass->draw_text(fs->preview_pixmap, gc, font, + font->width, font->height, + "hello, world", 12, + FALSE, FALSE, font->width); + gdk_gc_unref(gc); + gdk_window_invalidate_rect(fs->preview_area->window, NULL, FALSE); + } + + info->fontclass->destroy(font); + + sfree(sizename); +} + static void unifontsel_select_font(unifontsel_internal *fs, fontinfo *info, int size, int leftlist) { @@ -1565,37 +1595,7 @@ static void unifontsel_select_font(unifontsel_internal *fs, gtk_entry_set_editable(GTK_ENTRY(fs->size_entry), fs->selected->size == 0); gtk_widget_set_sensitive(fs->size_entry, fs->selected->size == 0); - /* - * Instantiate the font and draw some preview text. - */ - { - unifont *font; - char *sizename; - - sizename = info->fontclass->scale_fontname - (GTK_WIDGET(fs->u.window), info->realname, fs->selsize); - - font = info->fontclass->create(GTK_WIDGET(fs->u.window), - sizename ? sizename : info->realname, - FALSE, FALSE, 0, 0); - if (font && fs->preview_pixmap) { - GdkGC *gc = gdk_gc_new(fs->preview_pixmap); - gdk_gc_set_foreground(gc, &fs->preview_bg); - gdk_draw_rectangle(fs->preview_pixmap, gc, 1, 0, 0, - fs->preview_width, fs->preview_height); - gdk_gc_set_foreground(gc, &fs->preview_fg); - info->fontclass->draw_text(fs->preview_pixmap, gc, font, - font->width, font->height, - "hello, world", 12, - FALSE, FALSE, font->width); - gdk_gc_unref(gc); - gdk_window_invalidate_rect(fs->preview_area->window, NULL, FALSE); - } - - info->fontclass->destroy(font); - - sfree(sizename); - } + unifontsel_draw_preview_text(fs); fs->inhibit_response = FALSE; } @@ -1812,7 +1812,6 @@ static gint unifontsel_configure_area(GtkWidget *widget, GdkEventConfigure *event, gpointer data) { unifontsel_internal *fs = (unifontsel_internal *)data; - GdkGC *gc; int ox, oy, nx, ny, x, y; /* @@ -1823,24 +1822,16 @@ static gint unifontsel_configure_area(GtkWidget *widget, x = event->width; y = event->height; if (x > ox || y > oy) { - GdkPixmap *newpm; - + if (fs->preview_pixmap) + gdk_pixmap_unref(fs->preview_pixmap); + nx = (x > ox ? x : ox); ny = (y > oy ? y : oy); - newpm = gdk_pixmap_new(widget->window, nx, ny, -1); - - gc = gdk_gc_new(newpm); - gdk_gc_set_foreground(gc, &fs->preview_bg); - gdk_draw_rectangle(newpm, gc, 1, 0, 0, nx, ny); - if (fs->preview_pixmap) { - gdk_draw_pixmap(newpm, gc, fs->preview_pixmap, 0, 0, 0, 0, ox, oy); - gdk_pixmap_unref(fs->preview_pixmap); - } - gdk_gc_unref(gc); - - fs->preview_pixmap = newpm; + fs->preview_pixmap = gdk_pixmap_new(widget->window, nx, ny, -1); fs->preview_width = nx; fs->preview_height = ny; + + unifontsel_draw_preview_text(fs); } gdk_window_invalidate_rect(widget->window, NULL, FALSE); From d173eb52ea6b570b320482548f2a04997c6fb901 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 28 Mar 2008 20:08:12 +0000 Subject: [PATCH 23/53] Improve the preview pane text. [originally from svn r7946] --- unix/gtkfont.c | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/unix/gtkfont.c b/unix/gtkfont.c index 4d4a2aab..17222d86 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -27,14 +27,6 @@ * TODO on fontsel * --------------- * - * - preview pane fixes: - * + better centring of text? (Garuda is a particularly oddly - * behaved font in this respect; are its metrics going weird - * for some reason?) - * + pick a better few lines of preview text. Should take care - * to use wide letters (MWmw) and narrow ones (ij). - * + resize to fit? - * * - think about points versus pixels, harder than I already have * * - work out why the list boxes don't go all the way to the RHS @@ -1459,10 +1451,34 @@ static void unifontsel_draw_preview_text(unifontsel_internal *fs) gdk_draw_rectangle(fs->preview_pixmap, gc, 1, 0, 0, fs->preview_width, fs->preview_height); gdk_gc_set_foreground(gc, &fs->preview_fg); + /* + * The pangram used here is rather carefully constructed: + * it contains a sequence of very narrow letters (`jil') + * and a pair of adjacent very wide letters (`wm'). + * + * If the user selects a proportional font, it will be + * coerced into fixed-width character cells when used in + * the actual terminal window. We therefore display it the + * same way in the preview pane, so as to show it the way + * it will actually be displayed - and we deliberately + * pick a pangram which will show the resulting miskerning + * at its worst. + * + * We aren't trying to sell people these fonts; we're + * trying to let them make an informed choice. Better that + * they find out the problems with using proportional + * fonts in terminal windows here than that they go to the + * effort of selecting their font and _then_ realise it + * was a mistake. + */ info->fontclass->draw_text(fs->preview_pixmap, gc, font, - font->width, font->height, - "hello, world", 12, - FALSE, FALSE, font->width); + 0, font->ascent, + "bankrupt jilted showmen quiz convex fogey", + 41, FALSE, FALSE, font->width); + info->fontclass->draw_text(fs->preview_pixmap, gc, font, + 0, font->ascent + font->height, + "BANKRUPT JILTED SHOWMEN QUIZ CONVEX FOGEY", + 41, FALSE, FALSE, font->width); gdk_gc_unref(gc); gdk_window_invalidate_rect(fs->preview_area->window, NULL, FALSE); } From 6e6eae25064d92d11638986e4e630ea78cb6c5e8 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 29 Mar 2008 10:16:48 +0000 Subject: [PATCH 24/53] When the user switches between fonts using the font family or style selectors, preserve their most recent size selection as faithfully as possible. We do this by having a secondary size variable indicating what they _intend_, so we can come back to their intended size even after going through a font which doesn't include it. [originally from svn r7947] --- unix/gtkfont.c | 146 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 118 insertions(+), 28 deletions(-) diff --git a/unix/gtkfont.c b/unix/gtkfont.c index 17222d86..c4e4c30f 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -27,20 +27,19 @@ * TODO on fontsel * --------------- * + * - add a third line of sample text containing digits and punct + * + * - start drawing X fonts one character at a time, at least if + * they're not fixed-width? Now we have that helpful pangram, + * we should make it work for everyone. + * * - think about points versus pixels, harder than I already have * - * - work out why the list boxes don't go all the way to the RHS - * of the dialog box - * - * - consistency and updating issues: - * + we should attempt to preserve font size when switching - * font family and style (it's currently OK as long as we're - * moving between scalable fonts but falls down badly - * otherwise) - * * - generalised style and padding polish * + gtk_scrolled_window_set_shadow_type(foo, GTK_SHADOW_IN); * might be worth considering + * + work out why the list boxes don't go all the way to the + * RHS of the dialog box * * - big testing and shakedown! */ @@ -1169,7 +1168,7 @@ typedef struct unifontsel_internal { int filter_flags; tree234 *fonts_by_realname, *fonts_by_selorder; fontinfo *selected; - int selsize; + int selsize, intendedsize; int inhibit_response; /* inhibit callbacks when we change GUI controls */ } unifontsel_internal; @@ -1445,7 +1444,7 @@ static void unifontsel_draw_preview_text(unifontsel_internal *fs) font = info->fontclass->create(GTK_WIDGET(fs->u.window), sizename ? sizename : info->realname, FALSE, FALSE, 0, 0); - if (font && fs->preview_pixmap) { + if (fs->preview_pixmap) { GdkGC *gc = gdk_gc_new(fs->preview_pixmap); gdk_gc_set_foreground(gc, &fs->preview_bg); gdk_draw_rectangle(fs->preview_pixmap, gc, 1, 0, 0, @@ -1471,25 +1470,28 @@ static void unifontsel_draw_preview_text(unifontsel_internal *fs) * effort of selecting their font and _then_ realise it * was a mistake. */ - info->fontclass->draw_text(fs->preview_pixmap, gc, font, - 0, font->ascent, - "bankrupt jilted showmen quiz convex fogey", - 41, FALSE, FALSE, font->width); - info->fontclass->draw_text(fs->preview_pixmap, gc, font, - 0, font->ascent + font->height, - "BANKRUPT JILTED SHOWMEN QUIZ CONVEX FOGEY", - 41, FALSE, FALSE, font->width); + if (font) { + info->fontclass->draw_text(fs->preview_pixmap, gc, font, + 0, font->ascent, + "bankrupt jilted showmen quiz convex fogey", + 41, FALSE, FALSE, font->width); + info->fontclass->draw_text(fs->preview_pixmap, gc, font, + 0, font->ascent + font->height, + "BANKRUPT JILTED SHOWMEN QUIZ CONVEX FOGEY", + 41, FALSE, FALSE, font->width); + } gdk_gc_unref(gc); gdk_window_invalidate_rect(fs->preview_area->window, NULL, FALSE); } - - info->fontclass->destroy(font); + if (font) + info->fontclass->destroy(font); sfree(sizename); } static void unifontsel_select_font(unifontsel_internal *fs, - fontinfo *info, int size, int leftlist) + fontinfo *info, int size, int leftlist, + int size_is_explicit) { int index; int minval, maxval; @@ -1500,6 +1502,8 @@ static void unifontsel_select_font(unifontsel_internal *fs, fs->selected = info; fs->selsize = size; + if (size_is_explicit) + fs->intendedsize = size; /* * Find the index of this fontinfo in the selorder list. @@ -1701,6 +1705,79 @@ static void unifontsel_add_entry(void *ctx, const char *realfontname, add234(fs->fonts_by_selorder, info); } +static fontinfo *update_for_intended_size(unifontsel_internal *fs, + fontinfo *info) +{ + fontinfo info2, *below, *above; + int pos; + + /* + * Copy the info structure. This doesn't copy its dynamic + * string fields, but that's unimportant because all we're + * going to do is to adjust the size field and use it in one + * tree search. + */ + info2 = *info; + info2.size = fs->intendedsize; + + /* + * Search in the tree to find the fontinfo structure which + * best approximates the size the user last requested. + */ + below = findrelpos234(fs->fonts_by_selorder, &info2, NULL, + REL234_LE, &pos); + above = index234(fs->fonts_by_selorder, pos+1); + + /* + * See if we've found it exactly, which is an easy special + * case. If we have, it'll be in `below' and not `above', + * because we did a REL234_LE rather than REL234_LT search. + */ + if (!fontinfo_selorder_compare(&info2, below)) + return below; + + /* + * Now we've either found two suitable fonts, one smaller and + * one larger, or we're at one or other extreme end of the + * scale. Find out which, by NULLing out either of below and + * above if it differs from this one in any respect but size + * (and the disambiguating index field). Bear in mind, also, + * that either one might _already_ be NULL if we're at the + * extreme ends of the font list. + */ + if (below) { + info2.size = below->size; + info2.index = below->index; + if (fontinfo_selorder_compare(&info2, below)) + below = NULL; + } + if (above) { + info2.size = above->size; + info2.index = above->index; + if (fontinfo_selorder_compare(&info2, above)) + above = NULL; + } + + /* + * Now return whichever of above and below is non-NULL, if + * that's unambiguous. + */ + if (!above) + return below; + if (!below) + return above; + + /* + * And now we really do have to make a choice about whether to + * round up or down. We'll do it by rounding to nearest, + * breaking ties by rounding up. + */ + if (above->size - fs->intendedsize <= fs->intendedsize - below->size) + return above; + else + return below; +} + static void family_changed(GtkTreeSelection *treeselection, gpointer data) { unifontsel_internal *fs = (unifontsel_internal *)data; @@ -1717,7 +1794,13 @@ static void family_changed(GtkTreeSelection *treeselection, gpointer data) gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1); info = (fontinfo *)index234(fs->fonts_by_selorder, minval); - unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize, 1); + info = update_for_intended_size(fs, info); + if (!info) + return; /* _shouldn't_ happen unless font list is completely funted */ + if (!info->size) + fs->selsize = fs->intendedsize; /* font is scalable */ + unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize, + 1, FALSE); } static void style_changed(GtkTreeSelection *treeselection, gpointer data) @@ -1738,7 +1821,13 @@ static void style_changed(GtkTreeSelection *treeselection, gpointer data) if (minval < 0) return; /* somehow a charset heading got clicked */ info = (fontinfo *)index234(fs->fonts_by_selorder, minval); - unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize, 2); + info = update_for_intended_size(fs, info); + if (!info) + return; /* _shouldn't_ happen unless font list is completely funted */ + if (!info->size) + fs->selsize = fs->intendedsize; /* font is scalable */ + unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize, + 2, FALSE); } static void size_changed(GtkTreeSelection *treeselection, gpointer data) @@ -1757,7 +1846,7 @@ static void size_changed(GtkTreeSelection *treeselection, gpointer data) gtk_tree_model_get(treemodel, &treeiter, 1, &minval, 2, &size, -1); info = (fontinfo *)index234(fs->fonts_by_selorder, minval); - unifontsel_select_font(fs, info, info->size ? info->size : size, 3); + unifontsel_select_font(fs, info, info->size ? info->size : size, 3, TRUE); } static void size_entry_changed(GtkEditable *ed, gpointer data) @@ -1774,7 +1863,7 @@ static void size_entry_changed(GtkEditable *ed, gpointer data) if (size > 0) { assert(fs->selected->size == 0); - unifontsel_select_font(fs, fs->selected, size, 3); + unifontsel_select_font(fs, fs->selected, size, 3, TRUE); } } @@ -1804,7 +1893,8 @@ static void alias_resolve(GtkTreeView *treeview, GtkTreePath *path, if (newinfo == info) return; /* didn't change under canonification => not an alias */ unifontsel_select_font(fs, newinfo, - newinfo->size ? newinfo->size : newsize, 1); + newinfo->size ? newinfo->size : newsize, + 1, TRUE); } } @@ -2170,7 +2260,7 @@ void unifontsel_set_name(unifontsel *fontsel, const char *fontname) * know everything we need to fill in all the fields in the * dialog. */ - unifontsel_select_font(fs, info, size, 0); + unifontsel_select_font(fs, info, size, 0, TRUE); } char *unifontsel_get_name(unifontsel *fontsel) From 115898b2a5ed9d2bc1c5331998d9a700bbbf193e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 29 Mar 2008 10:25:45 +0000 Subject: [PATCH 25/53] Add the rest of ASCII to the font preview window. [originally from svn r7948] --- unix/gtkfont.c | 60 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/unix/gtkfont.c b/unix/gtkfont.c index c4e4c30f..d236c6f4 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -27,8 +27,6 @@ * TODO on fontsel * --------------- * - * - add a third line of sample text containing digits and punct - * * - start drawing X fonts one character at a time, at least if * they're not fixed-width? Now we have that helpful pangram, * we should make it work for everyone. @@ -40,6 +38,7 @@ * might be worth considering * + work out why the list boxes don't go all the way to the * RHS of the dialog box + * + sort out the behaviour when resizing the dialog box * * - big testing and shakedown! */ @@ -1450,27 +1449,28 @@ static void unifontsel_draw_preview_text(unifontsel_internal *fs) gdk_draw_rectangle(fs->preview_pixmap, gc, 1, 0, 0, fs->preview_width, fs->preview_height); gdk_gc_set_foreground(gc, &fs->preview_fg); - /* - * The pangram used here is rather carefully constructed: - * it contains a sequence of very narrow letters (`jil') - * and a pair of adjacent very wide letters (`wm'). - * - * If the user selects a proportional font, it will be - * coerced into fixed-width character cells when used in - * the actual terminal window. We therefore display it the - * same way in the preview pane, so as to show it the way - * it will actually be displayed - and we deliberately - * pick a pangram which will show the resulting miskerning - * at its worst. - * - * We aren't trying to sell people these fonts; we're - * trying to let them make an informed choice. Better that - * they find out the problems with using proportional - * fonts in terminal windows here than that they go to the - * effort of selecting their font and _then_ realise it - * was a mistake. - */ if (font) { + /* + * The pangram used here is rather carefully + * constructed: it contains a sequence of very narrow + * letters (`jil') and a pair of adjacent very wide + * letters (`wm'). + * + * If the user selects a proportional font, it will be + * coerced into fixed-width character cells when used + * in the actual terminal window. We therefore display + * it the same way in the preview pane, so as to show + * it the way it will actually be displayed - and we + * deliberately pick a pangram which will show the + * resulting miskerning at its worst. + * + * We aren't trying to sell people these fonts; we're + * trying to let them make an informed choice. Better + * that they find out the problems with using + * proportional fonts in terminal windows here than + * that they go to the effort of selecting their font + * and _then_ realise it was a mistake. + */ info->fontclass->draw_text(fs->preview_pixmap, gc, font, 0, font->ascent, "bankrupt jilted showmen quiz convex fogey", @@ -1479,6 +1479,22 @@ static void unifontsel_draw_preview_text(unifontsel_internal *fs) 0, font->ascent + font->height, "BANKRUPT JILTED SHOWMEN QUIZ CONVEX FOGEY", 41, FALSE, FALSE, font->width); + /* + * The ordering of punctuation here is also selected + * with some specific aims in mind. I put ` and ' + * together because some software (and people) still + * use them as matched quotes no matter what Unicode + * might say on the matter, so people can quickly + * check whether they look silly in a candidate font. + * The sequence #_@ is there to let people judge the + * suitability of the underscore as an effectively + * alphabetic character (since that's how it's often + * used in practice, at least by programmers). + */ + info->fontclass->draw_text(fs->preview_pixmap, gc, font, + 0, font->ascent + font->height * 2, + "0123456789!?,.:;<>()[]{}\\/`'\"+*-=~#_@|%&^$", + 42, FALSE, FALSE, font->width); } gdk_gc_unref(gc); gdk_window_invalidate_rect(fs->preview_area->window, NULL, FALSE); From cce6a5e2ec53542c3f4dca4035cdee4ec243fca5 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 29 Mar 2008 10:48:16 +0000 Subject: [PATCH 26/53] Detect non-monospaced X fonts, and respond by drawing text one character at a time centred in its character cell, as we do for Pango. Gives much better results for those non-monospaced fonts which are usable as terminal fonts, and shows up the problems with the others more readily. (In particular, this means the preview pane in the font selector now warns you there will be trouble if you select such a font.) [originally from svn r7949] --- unix/gtkfont.c | 81 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 18 deletions(-) diff --git a/unix/gtkfont.c b/unix/gtkfont.c index d236c6f4..f5b7ec8c 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -27,10 +27,6 @@ * TODO on fontsel * --------------- * - * - start drawing X fonts one character at a time, at least if - * they're not fixed-width? Now we have that helpful pangram, - * we should make it work for everyone. - * * - think about points versus pixels, harder than I already have * * - generalised style and padding polish @@ -145,6 +141,12 @@ struct x11font { * whether we use gdk_draw_text_wc() or gdk_draw_text(). */ int sixteen_bit; + /* + * `variable' is true iff the font is non-fixed-pitch. This + * enables some code which takes greater care over character + * positioning during text drawing. + */ + int variable; /* * Data passed in to unifont_create(). */ @@ -218,10 +220,10 @@ static int x11_font_width(GdkFont *font, int sixteen_bit) if (sixteen_bit) { XChar2b space; space.byte1 = 0; - space.byte2 = ' '; + space.byte2 = '0'; return gdk_text_width(font, (const gchar *)&space, 2); } else { - return gdk_char_width(font, ' '); + return gdk_char_width(font, '0'); } } @@ -233,9 +235,9 @@ static unifont *x11font_create(GtkWidget *widget, const char *name, GdkFont *font; XFontStruct *xfs; Display *disp; - Atom charset_registry, charset_encoding; - unsigned long registry_ret, encoding_ret; - int pubcs, realcs, sixteen_bit; + Atom charset_registry, charset_encoding, spacing; + unsigned long registry_ret, encoding_ret, spacing_ret; + int pubcs, realcs, sixteen_bit, variable; int i; font = gdk_font_load(name); @@ -250,6 +252,7 @@ static unifont *x11font_create(GtkWidget *widget, const char *name, pubcs = realcs = CS_NONE; sixteen_bit = FALSE; + variable = TRUE; if (XGetFontProperty(xfs, charset_registry, ®istry_ret) && XGetFontProperty(xfs, charset_encoding, &encoding_ret)) { @@ -298,6 +301,15 @@ static unifont *x11font_create(GtkWidget *widget, const char *name, } } + spacing = XInternAtom(disp, "SPACING", False); + if (XGetFontProperty(xfs, spacing, &spacing_ret)) { + char *spc; + spc = XGetAtomName(disp, (Atom)spacing_ret); + + if (spc && strchr("CcMm", spc[0])) + variable = FALSE; + } + xfont = snew(struct x11font); xfont->u.vt = &x11font_vtable; xfont->u.width = x11_font_width(font, sixteen_bit); @@ -309,6 +321,7 @@ static unifont *x11font_create(GtkWidget *widget, const char *name, xfont->fonts[0] = font; xfont->allocated[0] = TRUE; xfont->sixteen_bit = sixteen_bit; + xfont->variable = variable; xfont->wide = wide; xfont->bold = bold; xfont->shadowoffset = shadowoffset; @@ -342,6 +355,38 @@ static void x11_alloc_subfont(struct x11font *xfont, int sfid) sfree(derived_name); } +static void x11font_really_draw_text(GdkDrawable *target, GdkFont *font, + GdkGC *gc, int x, int y, + const gchar *string, int clen, int nchars, + int shadowbold, int shadowoffset, + int fontvariable, int cellwidth) +{ + int step = clen * nchars, nsteps = 1, centre = FALSE; + + if (fontvariable) { + /* + * In a variable-pitch font, we draw one character at a + * time, and centre it in the character cell. + */ + step = clen; + nsteps = nchars; + centre = TRUE; + } + + while (nsteps-- > 0) { + int X = x; + if (centre) + X += (cellwidth - gdk_text_width(font, string, step)) / 2; + + gdk_draw_text(target, font, gc, X, y, string, step); + if (shadowbold) + gdk_draw_text(target, font, gc, X + shadowoffset, y, string, step); + + x += cellwidth; + string += step; + } +} + static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, int x, int y, const char *string, int len, int wide, int bold, int cellwidth) @@ -349,6 +394,7 @@ static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, struct x11font *xfont = (struct x11font *)font; int sfid; int shadowbold = FALSE; + int mult = (wide ? 2 : 1); wide -= xfont->wide; bold -= xfont->bold; @@ -404,18 +450,17 @@ static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, xcs[i].byte2 = wcs[i]; } - gdk_draw_text(target, xfont->fonts[sfid], gc, - x, y, (gchar *)xcs, nchars*2); - if (shadowbold) - gdk_draw_text(target, xfont->fonts[sfid], gc, - x + xfont->shadowoffset, y, (gchar *)xcs, nchars*2); + x11font_really_draw_text(target, xfont->fonts[sfid], gc, x, y, + (gchar *)xcs, 2, nchars, + shadowbold, xfont->shadowoffset, + xfont->variable, cellwidth * mult); sfree(xcs); sfree(wcs); } else { - gdk_draw_text(target, xfont->fonts[sfid], gc, x, y, string, len); - if (shadowbold) - gdk_draw_text(target, xfont->fonts[sfid], gc, - x + xfont->shadowoffset, y, string, len); + x11font_really_draw_text(target, xfont->fonts[sfid], gc, x, y, + string, 1, len, + shadowbold, xfont->shadowoffset, + xfont->variable, cellwidth * mult); } } From 05766d7214241555f78ad930199bb5b981356cf4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 29 Mar 2008 13:55:40 +0000 Subject: [PATCH 27/53] Cosmetic polishing. [originally from svn r7950] --- unix/gtkfont.c | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/unix/gtkfont.c b/unix/gtkfont.c index f5b7ec8c..295be462 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -27,14 +27,9 @@ * TODO on fontsel * --------------- * - * - think about points versus pixels, harder than I already have - * * - generalised style and padding polish - * + gtk_scrolled_window_set_shadow_type(foo, GTK_SHADOW_IN); - * might be worth considering * + work out why the list boxes don't go all the way to the * RHS of the dialog box - * + sort out the behaviour when resizing the dialog box * * - big testing and shakedown! */ @@ -2009,7 +2004,7 @@ static gint unifontsel_configure_area(GtkWidget *widget, unifontsel *unifontsel_new(const char *wintitle) { unifontsel_internal *fs = snew(unifontsel_internal); - GtkWidget *table, *label, *w, *scroll; + GtkWidget *table, *label, *w, *ww, *scroll; GtkListStore *model; GtkTreeViewColumn *column; int lists_height, preview_height, font_width, style_width, size_width; @@ -2090,12 +2085,15 @@ unifontsel *unifontsel_new(const char *wintitle) G_CALLBACK(alias_resolve), fs); scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), + GTK_SHADOW_IN); gtk_container_add(GTK_CONTAINER(scroll), w); gtk_widget_show(scroll); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); gtk_widget_set_size_request(scroll, font_width, lists_height); - gtk_table_attach(GTK_TABLE(table), scroll, 0, 1, 1, 3, GTK_FILL, 0, 0, 0); + gtk_table_attach(GTK_TABLE(table), scroll, 0, 1, 1, 3, GTK_FILL, + GTK_EXPAND | GTK_FILL, 0, 0); fs->family_model = model; fs->family_list = w; @@ -2124,12 +2122,15 @@ unifontsel *unifontsel_new(const char *wintitle) "changed", G_CALLBACK(style_changed), fs); scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), + GTK_SHADOW_IN); gtk_container_add(GTK_CONTAINER(scroll), w); gtk_widget_show(scroll); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); gtk_widget_set_size_request(scroll, style_width, lists_height); - gtk_table_attach(GTK_TABLE(table), scroll, 1, 2, 1, 3, GTK_FILL, 0, 0, 0); + gtk_table_attach(GTK_TABLE(table), scroll, 1, 2, 1, 3, GTK_FILL, + GTK_EXPAND | GTK_FILL, 0, 0); fs->style_model = model; fs->style_list = w; @@ -2164,6 +2165,8 @@ unifontsel *unifontsel_new(const char *wintitle) "changed", G_CALLBACK(size_changed), fs); scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), + GTK_SHADOW_IN); gtk_container_add(GTK_CONTAINER(scroll), w); gtk_widget_show(scroll); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), @@ -2190,8 +2193,22 @@ unifontsel *unifontsel_new(const char *wintitle) GTK_SIGNAL_FUNC(unifontsel_configure_area), fs); gtk_widget_set_size_request(fs->preview_area, 1, preview_height); gtk_widget_show(fs->preview_area); - gtk_table_attach(GTK_TABLE(table), fs->preview_area, 0, 3, 3, 4, - GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + ww = fs->preview_area; + w = gtk_frame_new(NULL); + gtk_container_add(GTK_CONTAINER(w), ww); + gtk_widget_show(w); + ww = w; + /* GtkAlignment seems to be the simplest way to put padding round things */ + w = gtk_alignment_new(0, 0, 1, 1); + gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8); + gtk_container_add(GTK_CONTAINER(w), ww); + gtk_widget_show(w); + ww = w; + w = gtk_frame_new("Preview of font"); + gtk_container_add(GTK_CONTAINER(w), ww); + gtk_widget_show(w); + gtk_table_attach(GTK_TABLE(table), w, 0, 3, 3, 4, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 8); /* * FIXME: preview widget From 0527d54f03063aff5f6b0cf3f1fde4abeaaacb66 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 29 Mar 2008 13:55:51 +0000 Subject: [PATCH 28/53] Richard B points out another thing for the overall GTK2 to-do list. [originally from svn r7951] --- unix/GTK2.TODO | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/unix/GTK2.TODO b/unix/GTK2.TODO index f3caf82f..e0e6a6ce 100644 --- a/unix/GTK2.TODO +++ b/unix/GTK2.TODO @@ -41,6 +41,13 @@ Things to do before deciding a merge is feasible: - Investigate the shortcut mechanism in GTK2's GtkLabel, and see if it's worth switching to it from the current ad-hockery. + - Update the autoconf build. Richard B says he had to replace + AM_PATH_GTK([1.2.0], + with + AM_PATH_GTK_2_0([2.0.0], + + also I'll need to detect early Pangoi and enable my magic + switches in gtkfont.c. + Things to do once GTK2 development is complete: - Make sure we haven't broken GTK1. From f07f7825476302b96b06feb1712d765b18a9fb79 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 29 Mar 2008 13:59:31 +0000 Subject: [PATCH 29/53] Prevent NULL-dereferencing segfaults when the font selector is invoked with no valid font in the input text. [originally from svn r7952] --- unix/gtkfont.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/unix/gtkfont.c b/unix/gtkfont.c index 295be462..b5c31cfa 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -1474,15 +1474,18 @@ static void unifontsel_set_filter_buttons(unifontsel_internal *fs) static void unifontsel_draw_preview_text(unifontsel_internal *fs) { unifont *font; - char *sizename; + char *sizename = NULL; fontinfo *info = fs->selected; - sizename = info->fontclass->scale_fontname - (GTK_WIDGET(fs->u.window), info->realname, fs->selsize); + if (info) { + sizename = info->fontclass->scale_fontname + (GTK_WIDGET(fs->u.window), info->realname, fs->selsize); + font = info->fontclass->create(GTK_WIDGET(fs->u.window), + sizename ? sizename : info->realname, + FALSE, FALSE, 0, 0); + } else + font = NULL; - font = info->fontclass->create(GTK_WIDGET(fs->u.window), - sizename ? sizename : info->realname, - FALSE, FALSE, 0, 0); if (fs->preview_pixmap) { GdkGC *gc = gdk_gc_new(fs->preview_pixmap); gdk_gc_set_foreground(gc, &fs->preview_bg); From ae802c16d8feb31f3c3600b3e2176b5d20d64da9 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 29 Mar 2008 14:21:25 +0000 Subject: [PATCH 30/53] Deal with the possibility of no valid font being selected at all during an entire run of unifontsel (because unifontsel_set_name was either not called at all, or called with a name that didn't correspond to any known font). In this situation we grey out the OK button until a valid font is selected, and we have unifontsel_get_name return NULL rather than failing an assertion if it should be called in that state. The current client code in gtkdlg.c should never encounter a NULL return, since it only calls it after the OK button is clicked, but I've stuck an assertion in there too on general principles. [originally from svn r7953] --- unix/gtkdlg.c | 1 + unix/gtkfont.c | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index 73d734e6..7b50ac19 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -1226,6 +1226,7 @@ static void fontsel_ok(GtkButton *button, gpointer data) (GTK_OBJECT(button), "user-data"); struct uctrl *uc = (struct uctrl *)fontsel->user_data; char *name = unifontsel_get_name(fontsel); + assert(name); /* should always be ok after OK pressed */ gtk_entry_set_text(GTK_ENTRY(uc->entry), name); sfree(name); } diff --git a/unix/gtkfont.c b/unix/gtkfont.c index b5c31cfa..cc655622 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -1564,6 +1564,8 @@ static void unifontsel_select_font(unifontsel_internal *fs, if (size_is_explicit) fs->intendedsize = size; + gtk_widget_set_sensitive(fs->u.ok_button, TRUE); + /* * Find the index of this fontinfo in the selorder list. */ @@ -2270,6 +2272,8 @@ unifontsel *unifontsel_new(const char *wintitle) unifontsel_setup_familylist(fs); fs->selected = NULL; + fs->selsize = fs->intendedsize = 13; /* random default */ + gtk_widget_set_sensitive(fs->u.ok_button, FALSE); return (unifontsel *)fs; } @@ -2349,7 +2353,8 @@ char *unifontsel_get_name(unifontsel *fontsel) unifontsel_internal *fs = (unifontsel_internal *)fontsel; char *name; - assert(fs->selected); + if (!fs->selected) + return NULL; if (fs->selected->size == 0) { name = fs->selected->fontclass->scale_fontname From c3dbd71f9d79d546b806303c07faf466fed8cdf7 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 29 Mar 2008 14:54:55 +0000 Subject: [PATCH 31/53] Aha, _that's_ why there was some unexplained space on the RHS of the font selector: I had got the row and column counts in gtk_table_new() back to front, so the space on the right was the padding around five empty table columns! (And apparently a GtkTable silently expands if you try to use rows that don't exist, which is why I hadn't already noticed.) Fixed that, and added some padding around the entire table. I think my font selector is now finished, except for any bug fixes that come up in testing. [originally from svn r7954] --- unix/gtkfont.c | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/unix/gtkfont.c b/unix/gtkfont.c index cc655622..9ff0ad1c 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -23,17 +23,6 @@ #include "gtkfont.h" #include "tree234.h" -/* - * TODO on fontsel - * --------------- - * - * - generalised style and padding polish - * + work out why the list boxes don't go all the way to the - * RHS of the dialog box - * - * - big testing and shakedown! - */ - /* * Future work: * @@ -2058,11 +2047,16 @@ unifontsel *unifontsel_new(const char *wintitle) * Now set up the internal fields, including in particular all * the controls that actually allow the user to select fonts. */ - table = gtk_table_new(3, 8, FALSE); + table = gtk_table_new(8, 3, FALSE); gtk_widget_show(table); gtk_table_set_col_spacings(GTK_TABLE(table), 8); + /* GtkAlignment seems to be the simplest way to put padding round things */ + w = gtk_alignment_new(0, 0, 1, 1); + gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8); + gtk_container_add(GTK_CONTAINER(w), table); + gtk_widget_show(w); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fs->u.window)->vbox), - table, TRUE, TRUE, 0); + w, TRUE, TRUE, 0); label = gtk_label_new_with_mnemonic("_Font:"); gtk_widget_show(label); @@ -2181,6 +2175,9 @@ unifontsel *unifontsel_new(const char *wintitle) fs->size_model = model; fs->size_list = w; + /* + * Preview widget. + */ fs->preview_area = gtk_drawing_area_new(); fs->preview_pixmap = NULL; fs->preview_width = 0; @@ -2215,9 +2212,6 @@ unifontsel *unifontsel_new(const char *wintitle) gtk_table_attach(GTK_TABLE(table), w, 0, 3, 3, 4, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 8); - /* - * FIXME: preview widget - */ i = 0; w = gtk_check_button_new_with_label("Show client-side fonts"); gtk_object_set_data(GTK_OBJECT(w), "user-data", From 9adbc97e91889e852c5a2cebf7b721308d9545c1 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 29 Mar 2008 14:57:56 +0000 Subject: [PATCH 32/53] TODO updates. [originally from svn r7955] --- unix/GTK2.TODO | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/unix/GTK2.TODO b/unix/GTK2.TODO index e0e6a6ce..77de23d5 100644 --- a/unix/GTK2.TODO +++ b/unix/GTK2.TODO @@ -3,31 +3,20 @@ TODO for PuTTY GTK2 port before merging back into main trunk code Things to do before deciding a merge is feasible: - - Font handling is the biggie. Current problems with it: - * The GTK2 font selection dialog only mentions client-side - fonts, but the actual text display code can't cope with them. - + Clearly one or the other needs to be fixed: the font - selection dialog certainly needs to agree with the fonts - actually available in the program. - + I want to keep being able to use my server-side fonts. - + People used to GTK2 applications will probably want to use - their client-side fonts. - * Also, the GTK2 font selection dialog doesn't allow filtering - to monospaced fonts only (and gnome-terminal, for example, - just has to cope if the user selects a proportional font). - + We can live with this problem if we really have to, but - it'd be nice not to have to. - * Colin's idea is that we should simply cook up a font selection - dialog entirely of our own, which handles both client- _and_ - server-side fonts, and correspondingly soup up the text - display code to deal with whichever it's given (if necessary - by switching between two totally separate pieces of code). - This sounds like a sensible plan to me, or at least the most - sensible response to a generally insane situation. + - Although I'm still stubbornly _supporting_ X11 fonts alongside + Pango ones in defiance of standard GTK2 policy, it might be a + good idea to at least switch the default font to Pango's + Monospace 12, not least so that font aliases don't come up + selected by default. Then again, perhaps keeping fixed as the + default is more traditional. Hmm. - The call to _gtk_container_dequeue_resize_handler wants revisiting, and preferably removing in favour of a cleaner way to do the job. + + trouble with this is that I have no idea what it's actually + doing. Perhaps the thing to do is to debug through the GTK1 + version and see when it gets called and what happens if I take + it out? - gtkcols.c is currently a minimal-work GTK2 port of my original GTK1 implementation. Someone should go through it and compare it @@ -47,6 +36,9 @@ Things to do before deciding a merge is feasible: AM_PATH_GTK_2_0([2.0.0], + also I'll need to detect early Pangoi and enable my magic switches in gtkfont.c. + + and I'll probably also want to detect GTK2 vs GTK1 + automatically - _and_ provide a command line switch on + configure to select one manually. Things to do once GTK2 development is complete: From bef50d3fd297631471e247c76f789d03a0d1e80f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 29 Mar 2008 15:44:32 +0000 Subject: [PATCH 33/53] Be more picky than Pango when validating a Pango font description string. Without this, Richard B reports that Pango 1.18 will treat _anything_ as valid, which means PuTTY can never fall back to X fonts. [originally from svn r7956] --- unix/gtkfont.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/unix/gtkfont.c b/unix/gtkfont.c index 9ff0ad1c..88859d10 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -715,6 +715,55 @@ static const struct unifont_vtable pangofont_vtable = { "client" }; +/* + * This function is used to rigorously validate a + * PangoFontDescription. Later versions of Pango have a nasty + * habit of accepting _any_ old string as input to + * pango_font_description_from_string and returning a font + * description which can actually be used to display text, even if + * they have to do it by falling back to their most default font. + * This is doubtless helpful in some situations, but not here, + * because we need to know if a Pango font string actually _makes + * sense_ in order to fall back to treating it as an X font name + * if it doesn't. So we check that the font family is actually one + * supported by Pango. + */ +static int pangofont_check_desc_makes_sense(PangoContext *ctx, + PangoFontDescription *desc) +{ +#ifndef PANGO_PRE_1POINT6 + PangoFontMap *map; +#endif + PangoFontFamily **families; + int i, nfamilies, matched; + + /* + * Ask Pango for a list of font families, and iterate through + * them to see if one of them matches the family in the + * PangoFontDescription. + */ +#ifndef PANGO_PRE_1POINT6 + map = pango_context_get_font_map(ctx); + if (!map) + return FALSE; + pango_font_map_list_families(map, &families, &nfamilies); +#else + pango_context_list_families(ctx, &families, &nfamilies); +#endif + + matched = FALSE; + for (i = 0; i < nfamilies; i++) { + if (!g_strcasecmp(pango_font_family_get_name(families[i]), + pango_font_description_get_family(desc))) { + matched = TRUE; + break; + } + } + g_free(families); + + return matched; +} + static unifont *pangofont_create(GtkWidget *widget, const char *name, int wide, int bold, int shadowoffset, int shadowalways) @@ -736,6 +785,10 @@ static unifont *pangofont_create(GtkWidget *widget, const char *name, pango_font_description_free(desc); return NULL; } + if (!pangofont_check_desc_makes_sense(ctx, desc)) { + pango_font_description_free(desc); + return NULL; + } #ifndef PANGO_PRE_1POINT6 map = pango_context_get_font_map(ctx); if (!map) { @@ -1031,6 +1084,10 @@ static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name, pango_font_description_free(desc); return NULL; } + if (!pangofont_check_desc_makes_sense(ctx, desc)) { + pango_font_description_free(desc); + return NULL; + } #ifndef PANGO_PRE_1POINT6 map = pango_context_get_font_map(ctx); if (!map) { From 6ef1fa7b1fd5ed3a823dc87bad7649eb5098c251 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 29 Mar 2008 20:02:12 +0000 Subject: [PATCH 34/53] I give up. I can't work out what the purpose of the call to gtk_container_dequeue_resize_handler in request_resize() was; everything seems to work fine without it. So I'm removing the nonportable GTK 2 instance of it, and if anything ever goes wrong as a result then I'll at least find out what the problem was. [originally from svn r7957] --- unix/GTK2.TODO | 8 -------- unix/gtkwin.c | 18 +++++++++++++++++- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/unix/GTK2.TODO b/unix/GTK2.TODO index 77de23d5..efbd496a 100644 --- a/unix/GTK2.TODO +++ b/unix/GTK2.TODO @@ -10,14 +10,6 @@ Things to do before deciding a merge is feasible: selected by default. Then again, perhaps keeping fixed as the default is more traditional. Hmm. - - The call to _gtk_container_dequeue_resize_handler wants - revisiting, and preferably removing in favour of a cleaner way to - do the job. - + trouble with this is that I have no idea what it's actually - doing. Perhaps the thing to do is to debug through the GTK1 - version and see when it gets called and what happens if I take - it out? - - gtkcols.c is currently a minimal-work GTK2 port of my original GTK1 implementation. Someone should go through it and compare it to a real GTK2 container class, to make sure there aren't any diff --git a/unix/gtkwin.c b/unix/gtkwin.c index 5e4c6946..51a867e1 100644 --- a/unix/gtkwin.c +++ b/unix/gtkwin.c @@ -1350,12 +1350,28 @@ void request_resize(void *frontend, int w, int h) */ #if GTK_CHECK_VERSION(2,0,0) gtk_widget_set_size_request(inst->area, area_x, area_y); - _gtk_container_dequeue_resize_handler(GTK_CONTAINER(inst->window)); gtk_window_resize(GTK_WINDOW(inst->window), area_x + offset_x, area_y + offset_y); #else gtk_widget_set_usize(inst->area, area_x, area_y); gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), area_x, area_y); + /* + * I can no longer remember what this call to + * gtk_container_dequeue_resize_handler is for. It was + * introduced in r3092 with no comment, and the commit log + * message was uninformative. I'm _guessing_ its purpose is to + * prevent gratuitous resize processing on the window given + * that we're about to resize it anyway, but I have no idea + * why that's so incredibly vital. + * + * I've tried removing the call, and nothing seems to go + * wrong. I've backtracked to r3092 and tried removing the + * call there, and still nothing goes wrong. So I'm going to + * adopt the working hypothesis that it's superfluous; I won't + * actually remove it from the GTK 1.2 code, but I won't + * attempt to replicate its functionality in the GTK 2 code + * above. + */ gtk_container_dequeue_resize_handler(GTK_CONTAINER(inst->window)); gdk_window_resize(inst->window->window, area_x + offset_x, area_y + offset_y); From d2b4b4a9efe9a52a10faa97faca7dd248ebbc08f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 31 Mar 2008 10:47:48 +0000 Subject: [PATCH 35/53] Explicit casts to placate OS X gcc's pedantic type-check warnings. [originally from svn r7958] --- unix/gtkfont.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/unix/gtkfont.c b/unix/gtkfont.c index 88859d10..a2d8f40c 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -566,13 +566,13 @@ static void x11font_enum_fonts(GtkWidget *widget, p += sprintf(p, "%04d%04d%s%04d%04d%s%04d%04d%s%04d%s%04d%s", weightkey, - strlen(components[2]), components[2], + (int)strlen(components[2]), components[2], slantkey, - strlen(components[3]), components[3], + (int)strlen(components[3]), components[3], setwidthkey, - strlen(components[4]), components[4], - strlen(components[10]), components[10], - strlen(components[5]), components[5]); + (int)strlen(components[4]), components[4], + (int)strlen(components[10]), components[10], + (int)strlen(components[5]), components[5]); assert(p - tmp < thistmpsize); From 6a743399b03e3d609b8529f2695c2cb2ff03c5ee Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 2 Apr 2008 14:48:06 +0000 Subject: [PATCH 36/53] Update all the list box code in gtkdlg.c to use the new-style GTK2 GtkTreeView, GtkComboBox and GtkComboBoxEntry instead of the various old deprecated stuff. Immediate benefit: GTK2 natively supports real drag lists, hooray! [originally from svn r7959] --- dialog.h | 9 + unix/GTK2.TODO | 6 +- unix/gtkdlg.c | 1015 ++++++++++++++++++++---------------------------- 3 files changed, 441 insertions(+), 589 deletions(-) diff --git a/dialog.h b/dialog.h index a695c852..0cabb3d3 100644 --- a/dialog.h +++ b/dialog.h @@ -209,6 +209,10 @@ union control { * has a drop-down list built in. (Note that a _non_- * editable drop-down list is done as a special case of a * list box.) + * + * Don't try setting has_list and password on the same + * control; front ends are not required to support that + * combination. */ int has_list; /* @@ -333,6 +337,11 @@ union control { * the respective widths of `ncols' columns, which together * will exactly fit the width of the list box. Otherwise * `percentages' must be NULL. + * + * There should never be more than one column in a + * drop-down list (one with height==0), because front ends + * may have to implement it as a special case of an + * editable combo box. */ int ncols; /* number of columns */ int *percentages; /* % width of each column */ diff --git a/unix/GTK2.TODO b/unix/GTK2.TODO index efbd496a..e5d4b38d 100644 --- a/unix/GTK2.TODO +++ b/unix/GTK2.TODO @@ -16,9 +16,6 @@ Things to do before deciding a merge is feasible: large chunks we should have reimplemented and haven't, or indeed that we shouldn't have reimplemented and have. - - Uses of GtkList should be replaced with the non-deprecated - GtkTreeView. - - Investigate the shortcut mechanism in GTK2's GtkLabel, and see if it's worth switching to it from the current ad-hockery. @@ -35,6 +32,9 @@ Things to do before deciding a merge is feasible: Things to do once GTK2 development is complete: - Make sure we haven't broken GTK1. + + In particular, I know I _have_ broken GTK1 by taking out all + the GTK1-style list box code. Put it all back in under ifdefs, + which will be unpleasant but necessary. Things to do at point of actual merge: diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index 7b50ac19..b2cb551c 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -40,11 +40,11 @@ struct uctrl { void *privdata; int privdata_needs_free; GtkWidget **buttons; int nbuttons; /* for radio buttons */ - GtkWidget *entry; /* for editbox, combobox, filesel, fontsel */ + GtkWidget *entry; /* for editbox, filesel, fontsel */ + GtkWidget *combo; /* for combo box (either editable or not) */ + GtkWidget *list; /* for list box (list, droplist, combo box) */ + GtkListStore *listmodel; /* for all types of list box */ GtkWidget *button; /* for filesel, fontsel */ - GtkWidget *list; /* for combobox, listbox */ - GtkWidget *menu; /* for optionmenu (==droplist) */ - GtkWidget *optmenu; /* also for optionmenu */ GtkWidget *text; /* for text */ GtkWidget *label; /* for dlg_label_change */ GtkAdjustment *adj; /* for the scrollbar in a list box */ @@ -70,6 +70,7 @@ struct dlgparam { int retval; }; #define FLAG_UPDATING_COMBO_LIST 1 +#define FLAG_UPDATING_LISTBOX 2 enum { /* values for Shortcut.action */ SHORTCUT_EMPTY, /* no shortcut on this key */ @@ -96,18 +97,10 @@ static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event, static void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw, int chr, int action, void *ptr); static void shortcut_highlight(GtkWidget *label, int chr); -static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event, - gpointer data); -static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event, - gpointer data); -static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event, - gpointer data); -static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event, - gpointer data); -static void menuitem_activate(GtkMenuItem *item, gpointer data); static void coloursel_ok(GtkButton *button, gpointer data); static void coloursel_cancel(GtkButton *button, gpointer data); static void window_destroy(GtkWidget *widget, gpointer data); +int get_listitemheight(GtkWidget *widget); static int uctrl_cmp_byctrl(void *av, void *bv) { @@ -294,14 +287,24 @@ void dlg_editbox_set(union control *ctrl, void *dlg, char const *text) { struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + GtkWidget *entry; + char *tmpstring; assert(uc->ctrl->generic.type == CTRL_EDITBOX); - assert(uc->entry != NULL); + + if (!uc->ctrl->editbox.has_list) { + assert(uc->entry != NULL); + entry = uc->entry; + } else { + assert(uc->combo != NULL); + entry = gtk_bin_get_child(GTK_BIN(uc->combo)); + } + /* * GTK 2 implements gtk_entry_set_text by means of two separate * operations: first delete the previous text leaving the empty * string, then insert the new text. This causes two calls to * the "changed" signal. - * + * * The first call to "changed", if allowed to proceed normally, * will cause an EVENT_VALCHANGE event on the edit box, causing * a call to dlg_editbox_get() which will read the empty string @@ -310,13 +313,13 @@ void dlg_editbox_set(union control *ctrl, void *dlg, char const *text) * pointer is probably pointing, so the second editing * operation will insert that instead of the string we * originally asked for. - * - * Hence, we must block our "changed" signal handler for the - * duration of this call to gtk_entry_set_text. + * + * Hence, we must take our own copy of the text before we do + * this. */ - g_signal_handler_block(uc->entry, uc->entrysig); - gtk_entry_set_text(GTK_ENTRY(uc->entry), text); - g_signal_handler_unblock(uc->entry, uc->entrysig); + tmpstring = dupstr(text); + gtk_entry_set_text(GTK_ENTRY(entry), tmpstring); + sfree(tmpstring); } void dlg_editbox_get(union control *ctrl, void *dlg, char *buffer, int length) @@ -324,17 +327,19 @@ void dlg_editbox_get(union control *ctrl, void *dlg, char *buffer, int length) struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_EDITBOX); - assert(uc->entry != NULL); - strncpy(buffer, gtk_entry_get_text(GTK_ENTRY(uc->entry)), - length); - buffer[length-1] = '\0'; -} -static void container_remove_and_destroy(GtkWidget *w, gpointer data) -{ - GtkContainer *cont = GTK_CONTAINER(data); - /* gtk_container_remove will unref the widget for us; we need not. */ - gtk_container_remove(cont, w); + if (!uc->ctrl->editbox.has_list) { + assert(uc->entry != NULL); + strncpy(buffer, gtk_entry_get_text(GTK_ENTRY(uc->entry)), + length); + buffer[length-1] = '\0'; + } else { + assert(uc->combo != NULL); + strncpy(buffer, + gtk_combo_box_get_active_text(GTK_COMBO_BOX(uc->combo)), + length); + buffer[length-1] = '\0'; + } } /* The `listbox' functions can also apply to combo boxes. */ @@ -345,33 +350,26 @@ void dlg_listbox_clear(union control *ctrl, void *dlg) assert(uc->ctrl->generic.type == CTRL_EDITBOX || uc->ctrl->generic.type == CTRL_LISTBOX); - assert(uc->menu != NULL || uc->list != NULL); + assert(uc->listmodel != NULL); - if (uc->menu) { - gtk_container_foreach(GTK_CONTAINER(uc->menu), - container_remove_and_destroy, - GTK_CONTAINER(uc->menu)); - } else { - gtk_list_clear_items(GTK_LIST(uc->list), 0, -1); - } + gtk_list_store_clear(uc->listmodel); } void dlg_listbox_del(union control *ctrl, void *dlg, int index) { struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + GtkTreePath *path; + GtkTreeIter iter; assert(uc->ctrl->generic.type == CTRL_EDITBOX || uc->ctrl->generic.type == CTRL_LISTBOX); - assert(uc->menu != NULL || uc->list != NULL); + assert(uc->listmodel != NULL); - if (uc->menu) { - gtk_container_remove - (GTK_CONTAINER(uc->menu), - g_list_nth_data(GTK_MENU_SHELL(uc->menu)->children, index)); - } else { - gtk_list_clear_items(GTK_LIST(uc->list), index, index+1); - } + path = gtk_tree_path_new_from_indices(index, -1); + gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path); + gtk_list_store_remove(uc->listmodel, &iter); + gtk_tree_path_free(path); } void dlg_listbox_add(union control *ctrl, void *dlg, char const *text) @@ -391,126 +389,54 @@ void dlg_listbox_addwithid(union control *ctrl, void *dlg, { struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); + GtkTreeIter iter; + int i, cols; assert(uc->ctrl->generic.type == CTRL_EDITBOX || uc->ctrl->generic.type == CTRL_LISTBOX); - assert(uc->menu != NULL || uc->list != NULL); + assert(uc->listmodel); - dp->flags |= FLAG_UPDATING_COMBO_LIST; + dp->flags |= FLAG_UPDATING_LISTBOX;/* inhibit drag-list update function */ + gtk_list_store_append(uc->listmodel, &iter); + dp->flags &= ~FLAG_UPDATING_LISTBOX; + gtk_list_store_set(uc->listmodel, &iter, 0, id, -1); - if (uc->menu) { - /* - * List item in a drop-down (but non-combo) list. Tabs are - * ignored; we just provide a standard menu item with the - * text. - */ - GtkWidget *menuitem = gtk_menu_item_new_with_label(text); - - gtk_container_add(GTK_CONTAINER(uc->menu), menuitem); - gtk_widget_show(menuitem); - - gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", - GINT_TO_POINTER(id)); - gtk_signal_connect(GTK_OBJECT(menuitem), "activate", - GTK_SIGNAL_FUNC(menuitem_activate), dp); - } else if (!uc->entry) { - /* - * List item in a non-combo-box list box. We make all of - * these Columns containing GtkLabels. This allows us to do - * the nasty force_left hack irrespective of whether there - * are tabs in the thing. - */ - GtkWidget *listitem = gtk_list_item_new(); - GtkWidget *cols = columns_new(10); - gint *percents; - int i, ncols; - - /* Count the tabs in the text, and hence determine # of columns. */ - ncols = 1; - for (i = 0; text[i]; i++) - if (text[i] == '\t') - ncols++; - - assert(ncols <= - (uc->ctrl->listbox.ncols ? uc->ctrl->listbox.ncols : 1)); - percents = snewn(ncols, gint); - percents[ncols-1] = 100; - for (i = 0; i < ncols-1; i++) { - percents[i] = uc->ctrl->listbox.percentages[i]; - percents[ncols-1] -= percents[i]; - } - columns_set_cols(COLUMNS(cols), ncols, percents); - sfree(percents); - - for (i = 0; i < ncols; i++) { - int len = strcspn(text, "\t"); - char *dup = dupprintf("%.*s", len, text); - GtkWidget *label; - - text += len; - if (*text) text++; - label = gtk_label_new(dup); - sfree(dup); - - columns_add(COLUMNS(cols), label, i, 1); - columns_force_left_align(COLUMNS(cols), label); - gtk_widget_show(label); - } - gtk_container_add(GTK_CONTAINER(listitem), cols); - gtk_widget_show(cols); - gtk_container_add(GTK_CONTAINER(uc->list), listitem); - gtk_widget_show(listitem); - - if (ctrl->listbox.multisel) { - gtk_signal_connect(GTK_OBJECT(listitem), "key_press_event", - GTK_SIGNAL_FUNC(listitem_multi_key), uc->adj); - } else { - gtk_signal_connect(GTK_OBJECT(listitem), "key_press_event", - GTK_SIGNAL_FUNC(listitem_single_key), uc->adj); - } - gtk_signal_connect(GTK_OBJECT(listitem), "focus_in_event", - GTK_SIGNAL_FUNC(widget_focus), dp); - gtk_signal_connect(GTK_OBJECT(listitem), "button_press_event", - GTK_SIGNAL_FUNC(listitem_button_press), dp); - gtk_signal_connect(GTK_OBJECT(listitem), "button_release_event", - GTK_SIGNAL_FUNC(listitem_button_release), dp); - gtk_object_set_data(GTK_OBJECT(listitem), "user-data", - GINT_TO_POINTER(id)); - } else { - /* - * List item in a combo-box list, which means the sensible - * thing to do is make it a perfectly normal label. Hence - * tabs are disregarded. - */ - GtkWidget *listitem = gtk_list_item_new_with_label(text); - - gtk_container_add(GTK_CONTAINER(uc->list), listitem); - gtk_widget_show(listitem); - - gtk_object_set_data(GTK_OBJECT(listitem), "user-data", - GINT_TO_POINTER(id)); + /* + * Now go through text and divide it into columns at the tabs, + * as necessary. + */ + cols = (uc->ctrl->generic.type == CTRL_LISTBOX ? ctrl->listbox.ncols : 1); + cols = cols ? cols : 1; + for (i = 0; i < cols; i++) { + int collen = strcspn(text, "\t"); + char *tmpstr = snewn(collen+1, char); + memcpy(tmpstr, text, collen); + tmpstr[collen] = '\0'; + gtk_list_store_set(uc->listmodel, &iter, i+1, tmpstr, -1); + sfree(tmpstr); + text += collen; + if (*text) text++; } - - dp->flags &= ~FLAG_UPDATING_COMBO_LIST; } int dlg_listbox_getid(union control *ctrl, void *dlg, int index) { struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - GList *children; - GtkObject *item; + GtkTreePath *path; + GtkTreeIter iter; + int ret; assert(uc->ctrl->generic.type == CTRL_EDITBOX || uc->ctrl->generic.type == CTRL_LISTBOX); - assert(uc->menu != NULL || uc->list != NULL); + assert(uc->listmodel != NULL); - children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu : - uc->list)); - item = GTK_OBJECT(g_list_nth_data(children, index)); - g_list_free(children); + path = gtk_tree_path_new_from_indices(index, -1); + gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path); + gtk_tree_model_get(GTK_TREE_MODEL(uc->listmodel), &iter, 0, &ret, -1); + gtk_tree_path_free(path); - return GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(item), "user-data")); + return ret; } /* dlg_listbox_index returns <0 if no single element is selected. */ @@ -518,57 +444,87 @@ int dlg_listbox_index(union control *ctrl, void *dlg) { struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - GList *children; - GtkWidget *item, *activeitem; - int i; - int selected = -1; assert(uc->ctrl->generic.type == CTRL_EDITBOX || uc->ctrl->generic.type == CTRL_LISTBOX); - assert(uc->menu != NULL || uc->list != NULL); - if (uc->menu) - activeitem = gtk_menu_get_active(GTK_MENU(uc->menu)); - else - activeitem = NULL; /* unnecessarily placate gcc */ + /* + * We have to do this completely differently for a combo box + * (editable or otherwise) and a full-size list box. + */ + if (uc->combo) { + /* + * This API function already does the right thing in the + * case of no current selection. + */ + return gtk_combo_box_get_active(GTK_COMBO_BOX(uc->combo)); + } else { + GtkTreeSelection *treesel; + GtkTreePath *path; + GList *sellist; + gint *indices; + int ret; - children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu : - uc->list)); - for (i = 0; children!=NULL && (item = GTK_WIDGET(children->data))!=NULL; - i++, children = children->next) { - if (uc->menu ? activeitem == item : - GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED) { - if (selected == -1) - selected = i; - else - selected = -2; + assert(uc->list != NULL); + treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->list)); + + if (gtk_tree_selection_count_selected_rows(treesel) != 1) + return -1; + + sellist = gtk_tree_selection_get_selected_rows(treesel, NULL); + + assert(sellist && sellist->data); + path = sellist->data; + + if (gtk_tree_path_get_depth(path) != 1) { + ret = -1; + } else { + indices = gtk_tree_path_get_indices(path); + if (!indices) { + ret = -1; + } else { + ret = indices[0]; + } } + + g_list_foreach(sellist, (GFunc)gtk_tree_path_free, NULL); + g_list_free(sellist); + + return ret; } - g_list_free(children); - return selected < 0 ? -1 : selected; } int dlg_listbox_issel(union control *ctrl, void *dlg, int index) { struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - GList *children; - GtkWidget *item, *activeitem; assert(uc->ctrl->generic.type == CTRL_EDITBOX || uc->ctrl->generic.type == CTRL_LISTBOX); - assert(uc->menu != NULL || uc->list != NULL); - children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu : - uc->list)); - item = GTK_WIDGET(g_list_nth_data(children, index)); - g_list_free(children); - - if (uc->menu) { - activeitem = gtk_menu_get_active(GTK_MENU(uc->menu)); - return item == activeitem; + /* + * We have to do this completely differently for a combo box + * (editable or otherwise) and a full-size list box. + */ + if (uc->combo) { + /* + * This API function already does the right thing in the + * case of no current selection. + */ + return gtk_combo_box_get_active(GTK_COMBO_BOX(uc->combo)) == index; } else { - return GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED; + GtkTreeSelection *treesel; + GtkTreePath *path; + int ret; + + assert(uc->list != NULL); + treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->list)); + + path = gtk_tree_path_new_from_indices(index, -1); + ret = gtk_tree_selection_path_is_selected(treesel, path); + gtk_tree_path_free(path); + + return ret; } } @@ -579,40 +535,25 @@ void dlg_listbox_select(union control *ctrl, void *dlg, int index) assert(uc->ctrl->generic.type == CTRL_EDITBOX || uc->ctrl->generic.type == CTRL_LISTBOX); - assert(uc->optmenu != NULL || uc->list != NULL); - if (uc->optmenu) { - gtk_option_menu_set_history(GTK_OPTION_MENU(uc->optmenu), index); + /* + * We have to do this completely differently for a combo box + * (editable or otherwise) and a full-size list box. + */ + if (uc->combo) { + gtk_combo_box_set_active(GTK_COMBO_BOX(uc->combo), index); } else { - int nitems; - GList *items; - gdouble newtop, newbot; + GtkTreeSelection *treesel; + GtkTreePath *path; - gtk_list_select_item(GTK_LIST(uc->list), index); + assert(uc->list != NULL); + treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->list)); - /* - * Scroll the list box if necessary to ensure the newly - * selected item is visible. - */ - items = gtk_container_children(GTK_CONTAINER(uc->list)); - nitems = g_list_length(items); - if (nitems > 0) { - int modified = FALSE; - g_list_free(items); - newtop = uc->adj->lower + - (uc->adj->upper - uc->adj->lower) * index / nitems; - newbot = uc->adj->lower + - (uc->adj->upper - uc->adj->lower) * (index+1) / nitems; - if (uc->adj->value > newtop) { - modified = TRUE; - uc->adj->value = newtop; - } else if (uc->adj->value < newbot - uc->adj->page_size) { - modified = TRUE; - uc->adj->value = newbot - uc->adj->page_size; - } - if (modified) - gtk_adjustment_value_changed(uc->adj); - } + path = gtk_tree_path_new_from_indices(index, -1); + gtk_tree_selection_select_path(treesel, path); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(uc->list), + path, NULL, FALSE, 0.0, 0.0); + gtk_tree_path_free(path); } } @@ -742,8 +683,13 @@ void dlg_set_focus(union control *ctrl, void *dlg) case CTRL_FILESELECT: case CTRL_FONTSELECT: case CTRL_EDITBOX: - /* Anything containing an edit box gets that focused. */ - gtk_widget_grab_focus(uc->entry); + if (uc->entry) { + /* Anything containing an edit box gets that focused. */ + gtk_widget_grab_focus(uc->entry); + } else if (uc->combo) { + /* Failing that, there'll be a combo box. */ + gtk_widget_grab_focus(uc->combo); + } break; case CTRL_RADIO: /* @@ -760,21 +706,15 @@ void dlg_set_focus(union control *ctrl, void *dlg) } break; case CTRL_LISTBOX: - /* - * If the list is really an option menu, we focus it. - * Otherwise we tell it to focus one of its children, which - * appears to do the Right Thing. - */ - if (uc->optmenu) { - gtk_widget_grab_focus(uc->optmenu); - } else { - assert(uc->list != NULL); -#if GTK_CHECK_VERSION(2,0,0) + /* + * There might be a combo box (drop-down list) here, or a + * proper list box. + */ + if (uc->list) { gtk_widget_grab_focus(uc->list); -#else - gtk_container_focus(GTK_CONTAINER(uc->list), GTK_DIR_TAB_FORWARD); -#endif - } + } else if (uc->combo) { + gtk_widget_grab_focus(uc->combo); + } break; } } @@ -1021,192 +961,86 @@ static gboolean editbox_lostfocus(GtkWidget *ed, GdkEventFocus *event, return FALSE; } -static gboolean listitem_key(GtkWidget *item, GdkEventKey *event, - gpointer data, int multiple) -{ - GtkAdjustment *adj = GTK_ADJUSTMENT(data); - - if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up || - event->keyval == GDK_Down || event->keyval == GDK_KP_Down || - event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up || - event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down) { - /* - * Up, Down, PgUp or PgDn have been pressed on a ListItem - * in a list box. So, if the list box is single-selection: - * - * - if the list item in question isn't already selected, - * we simply select it. - * - otherwise, we find the next one (or next - * however-far-away) in whichever direction we're going, - * and select that. - * + in this case, we must also fiddle with the - * scrollbar to ensure the newly selected item is - * actually visible. - * - * If it's multiple-selection, we do all of the above - * except actually selecting anything, so we move the focus - * and fiddle the scrollbar to follow it. - */ - GtkWidget *list = item->parent; - - gtk_signal_emit_stop_by_name(GTK_OBJECT(item), "key_press_event"); - - if (!multiple && - GTK_WIDGET_STATE(item) != GTK_STATE_SELECTED) { - gtk_list_select_child(GTK_LIST(list), item); - } else { - int direction = - (event->keyval==GDK_Up || event->keyval==GDK_KP_Up || - event->keyval==GDK_Page_Up || event->keyval==GDK_KP_Page_Up) - ? -1 : +1; - int step = - (event->keyval==GDK_Page_Down || - event->keyval==GDK_KP_Page_Down || - event->keyval==GDK_Page_Up || event->keyval==GDK_KP_Page_Up) - ? 2 : 1; - int i, n; - GList *children, *chead; - - chead = children = gtk_container_children(GTK_CONTAINER(list)); - - n = g_list_length(children); - - if (step == 2) { - /* - * Figure out how many list items to a screenful, - * and adjust the step appropriately. - */ - step = 0.5 + adj->page_size * n / (adj->upper - adj->lower); - step--; /* go by one less than that */ - } - - i = 0; - while (children != NULL) { - if (item == children->data) - break; - children = children->next; - i++; - } - - while (step > 0) { - if (direction < 0 && i > 0) - children = children->prev, i--; - else if (direction > 0 && i < n-1) - children = children->next, i++; - step--; - } - - if (children && children->data) { - if (!multiple) - gtk_list_select_child(GTK_LIST(list), - GTK_WIDGET(children->data)); - gtk_widget_grab_focus(GTK_WIDGET(children->data)); - gtk_adjustment_clamp_page - (adj, - adj->lower + (adj->upper-adj->lower) * i / n, - adj->lower + (adj->upper-adj->lower) * (i+1) / n); - } - - g_list_free(chead); - } - return TRUE; - } - - return FALSE; -} - -static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event, - gpointer data) -{ - return listitem_key(item, event, data, FALSE); -} - -static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event, - gpointer data) -{ - return listitem_key(item, event, data, TRUE); -} - -static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event, - gpointer data) +static void listbox_doubleclick(GtkTreeView *treeview, GtkTreePath *path, + GtkTreeViewColumn *column, gpointer data) { struct dlgparam *dp = (struct dlgparam *)data; - struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item)); - switch (event->type) { - default: - case GDK_BUTTON_PRESS: uc->nclicks = 1; break; - case GDK_2BUTTON_PRESS: uc->nclicks = 2; break; - case GDK_3BUTTON_PRESS: uc->nclicks = 3; break; - } - return FALSE; -} - -static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event, - gpointer data) -{ - struct dlgparam *dp = (struct dlgparam *)data; - struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item)); - if (uc->nclicks>1) { + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(treeview)); + if (uc) uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION); - return TRUE; - } +} + +static void droplist_selchange(GtkComboBox *combo, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(combo)); + if (uc) + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); +} + +static void listbox_selchange(GtkTreeSelection *treeselection, + gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + GtkTreeView *tree = gtk_tree_selection_get_tree_view(treeselection); + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(tree)); + if (uc) + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); +} + +struct draglist_valchange_ctx { + struct uctrl *uc; + struct dlgparam *dp; +}; + +static gboolean draglist_valchange(gpointer data) +{ + struct draglist_valchange_ctx *ctx = + (struct draglist_valchange_ctx *)data; + + ctx->uc->ctrl->generic.handler(ctx->uc->ctrl, ctx->dp, + ctx->dp->data, EVENT_VALCHANGE); + + sfree(ctx); + return FALSE; } -static void list_selchange(GtkList *list, gpointer data) +static void listbox_reorder(GtkTreeModel *treemodel, GtkTreePath *path, + GtkTreeIter *iter, gpointer data) { struct dlgparam *dp = (struct dlgparam *)data; - struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(list)); - if (!uc) return; - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); -} + gpointer tree; + struct uctrl *uc; -static void menuitem_activate(GtkMenuItem *item, gpointer data) -{ - struct dlgparam *dp = (struct dlgparam *)data; - GtkWidget *menushell = GTK_WIDGET(item)->parent; - gpointer optmenu = gtk_object_get_data(GTK_OBJECT(menushell), "user-data"); - struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(optmenu)); - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); -} + if (dp->flags & FLAG_UPDATING_LISTBOX) + return; /* not a user drag operation */ -static void draglist_move(struct dlgparam *dp, struct uctrl *uc, int direction) -{ - int index = dlg_listbox_index(uc->ctrl, dp); - GList *children = gtk_container_children(GTK_CONTAINER(uc->list)); - GtkWidget *child; - - if ((index < 0) || - (index == 0 && direction < 0) || - (index == g_list_length(children)-1 && direction > 0)) { - gdk_beep(); - return; + tree = g_object_get_data(G_OBJECT(treemodel), "user-data"); + uc = dlg_find_bywidget(dp, GTK_WIDGET(tree)); + if (uc) { + /* + * We should cause EVENT_VALCHANGE on the list box, now + * that its rows have been reordered. However, the GTK 2 + * docs say that at the point this signal is received the + * new row might not have actually been filled in yet. + * + * (So what smegging use is it then, eh? Don't suppose it + * occurred to you at any point that letting the + * application know _after_ the reordering was compelete + * might be helpful to someone?) + * + * To get round this, I schedule an idle function, which I + * hope won't be called until the main event loop is + * re-entered after the drag-and-drop handler has finished + * furtling with the list store. + */ + struct draglist_valchange_ctx *ctx = + snew(struct draglist_valchange_ctx); + ctx->uc = uc; + ctx->dp = dp; + g_idle_add(draglist_valchange, ctx); } - - child = g_list_nth_data(children, index); - gtk_widget_ref(child); - gtk_list_clear_items(GTK_LIST(uc->list), index, index+1); - g_list_free(children); - - children = NULL; - children = g_list_append(children, child); - gtk_list_insert_items(GTK_LIST(uc->list), children, index + direction); - gtk_list_select_item(GTK_LIST(uc->list), index + direction); - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE); -} - -static void draglist_up(GtkButton *button, gpointer data) -{ - struct dlgparam *dp = (struct dlgparam *)data; - struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button)); - draglist_move(dp, uc, -1); -} - -static void draglist_down(GtkButton *button, gpointer data) -{ - struct dlgparam *dp = (struct dlgparam *)data; - struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button)); - draglist_move(dp, uc, +1); } static void filesel_ok(GtkButton *button, gpointer data) @@ -1324,16 +1158,12 @@ static void label_sizealloc(GtkWidget *widget, GtkAllocation *alloc, * definitely a GtkWidget and should probably be added to a * GtkVbox.) * - * `listitemheight' is used to calculate a usize for list boxes: it - * should be the height from the size request of a GtkListItem. - * * `win' is required for setting the default button. If it is * non-NULL, all buttons created will be default-capable (so they * have extra space round them for the default highlight). */ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, - struct controlset *s, int listitemheight, - GtkWindow *win) + struct controlset *s, GtkWindow *win) { Columns *cols; GtkWidget *ret; @@ -1395,8 +1225,9 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, uc->privdata = NULL; uc->privdata_needs_free = FALSE; uc->buttons = NULL; - uc->entry = uc->list = uc->menu = NULL; - uc->button = uc->optmenu = uc->text = NULL; + uc->entry = uc->combo = uc->list = NULL; + uc->listmodel = NULL; + uc->button = uc->text = NULL; uc->label = NULL; uc->nclicks = 0; @@ -1491,23 +1322,37 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, GtkRequisition req; if (ctrl->editbox.has_list) { - w = gtk_combo_new(); - gtk_combo_set_value_in_list(GTK_COMBO(w), FALSE, TRUE); - uc->entry = GTK_COMBO(w)->entry; - uc->list = GTK_COMBO(w)->list; + uc->listmodel = gtk_list_store_new(2, G_TYPE_INT, + G_TYPE_STRING); + w = gtk_combo_box_entry_new_with_model + (GTK_TREE_MODEL(uc->listmodel), 1); + /* We cannot support password combo boxes. */ + assert(!ctrl->editbox.password); + uc->combo = w; + uc->entrysig = + gtk_signal_connect(GTK_OBJECT(uc->combo), "changed", + GTK_SIGNAL_FUNC(editbox_changed), dp); + gtk_signal_connect(GTK_OBJECT(uc->combo), "key_press_event", + GTK_SIGNAL_FUNC(editbox_key), dp); + gtk_signal_connect(GTK_OBJECT(uc->combo), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + gtk_signal_connect(GTK_OBJECT(uc->combo), "focus_out_event", + GTK_SIGNAL_FUNC(editbox_lostfocus), dp); } else { w = gtk_entry_new(); if (ctrl->editbox.password) gtk_entry_set_visibility(GTK_ENTRY(w), FALSE); uc->entry = w; + uc->entrysig = + gtk_signal_connect(GTK_OBJECT(uc->entry), "changed", + GTK_SIGNAL_FUNC(editbox_changed), dp); + gtk_signal_connect(GTK_OBJECT(uc->entry), "key_press_event", + GTK_SIGNAL_FUNC(editbox_key), dp); + gtk_signal_connect(GTK_OBJECT(uc->entry), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + gtk_signal_connect(GTK_OBJECT(uc->entry), "focus_out_event", + GTK_SIGNAL_FUNC(editbox_lostfocus), dp); } - uc->entrysig = - gtk_signal_connect(GTK_OBJECT(uc->entry), "changed", - GTK_SIGNAL_FUNC(editbox_changed), dp); - gtk_signal_connect(GTK_OBJECT(uc->entry), "key_press_event", - GTK_SIGNAL_FUNC(editbox_key), dp); - gtk_signal_connect(GTK_OBJECT(uc->entry), "focus_in_event", - GTK_SIGNAL_FUNC(widget_focus), dp); /* * Edit boxes, for some strange reason, have a minimum * width of 150 in GTK 1.2. We don't want this - we'd @@ -1552,8 +1397,6 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, w = container; uc->label = label; } - gtk_signal_connect(GTK_OBJECT(uc->entry), "focus_out_event", - GTK_SIGNAL_FUNC(editbox_lostfocus), dp); } break; case CTRL_FILESELECT: @@ -1606,126 +1449,162 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, } break; case CTRL_LISTBOX: - if (ctrl->listbox.height == 0) { - uc->optmenu = w = gtk_option_menu_new(); - uc->menu = gtk_menu_new(); - gtk_option_menu_set_menu(GTK_OPTION_MENU(w), uc->menu); - gtk_object_set_data(GTK_OBJECT(uc->menu), "user-data", - (gpointer)uc->optmenu); - gtk_signal_connect(GTK_OBJECT(uc->optmenu), "focus_in_event", - GTK_SIGNAL_FUNC(widget_focus), dp); - } else { - uc->list = gtk_list_new(); - if (ctrl->listbox.multisel == 2) { - gtk_list_set_selection_mode(GTK_LIST(uc->list), - GTK_SELECTION_EXTENDED); - } else if (ctrl->listbox.multisel == 1) { - gtk_list_set_selection_mode(GTK_LIST(uc->list), - GTK_SELECTION_MULTIPLE); - } else { - gtk_list_set_selection_mode(GTK_LIST(uc->list), - GTK_SELECTION_SINGLE); - } - w = gtk_scrolled_window_new(NULL, NULL); - gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(w), - uc->list); - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w), - GTK_POLICY_NEVER, - GTK_POLICY_AUTOMATIC); - uc->adj = gtk_scrolled_window_get_vadjustment - (GTK_SCROLLED_WINDOW(w)); + /* + * First construct the list data store, with the right + * number of columns. + */ + { + GType *types; + int i; + int cols; - gtk_widget_show(uc->list); - gtk_signal_connect(GTK_OBJECT(uc->list), "selection-changed", - GTK_SIGNAL_FUNC(list_selchange), dp); - gtk_signal_connect(GTK_OBJECT(uc->list), "focus_in_event", - GTK_SIGNAL_FUNC(widget_focus), dp); + cols = ctrl->listbox.ncols; + cols = cols ? cols : 1; + types = snewn(1 + cols, GType); - /* - * Adjust the height of the scrolled window to the - * minimum given by the height parameter. - * - * This piece of guesswork is a horrid hack based - * on looking inside the GTK 1.2 sources - * (specifically gtkviewport.c, which appears to be - * the widget which provides the border around the - * scrolling area). Anyone lets me know how I can - * do this in a way which isn't at risk from GTK - * upgrades, I'd be grateful. - */ - { - int edge; -#if GTK_CHECK_VERSION(2,0,0) - edge = GTK_WIDGET(uc->list)->style->ythickness; -#else - edge = GTK_WIDGET(uc->list)->style->klass->ythickness; -#endif - gtk_widget_set_usize(w, 10, - 2*edge + (ctrl->listbox.height * - listitemheight)); + types[0] = G_TYPE_INT; + for (i = 0; i < cols; i++) + types[i+1] = G_TYPE_STRING; + + uc->listmodel = gtk_list_store_newv(1 + cols, types); + + sfree(types); + } + + /* + * Drop-down lists are done completely differently. + */ + if (ctrl->listbox.height == 0) { + GtkCellRenderer *cr; + + /* + * Create a non-editable GtkComboBox (that is, not + * its subclass GtkComboBoxEntry). + */ + w = gtk_combo_box_new_with_model + (GTK_TREE_MODEL(uc->listmodel)); + uc->combo = w; + + /* + * Tell it how to render a list item (i.e. which + * column to look at in the list model). + */ + cr = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), cr, TRUE); + gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(w), cr, + "text", 1, NULL); + + /* + * And tell it to notify us when the selection + * changes. + */ + g_signal_connect(G_OBJECT(w), "changed", + G_CALLBACK(droplist_selchange), dp); + } else { + GtkTreeSelection *sel; + + /* + * Create the list box itself, its columns, and + * its containing scrolled window. + */ + w = gtk_tree_view_new_with_model + (GTK_TREE_MODEL(uc->listmodel)); + g_object_set_data(G_OBJECT(uc->listmodel), "user-data", + (gpointer)w); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE); + sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(w)); + gtk_tree_selection_set_mode + (sel, ctrl->listbox.multisel ? GTK_SELECTION_MULTIPLE : + GTK_SELECTION_SINGLE); + uc->list = w; + gtk_signal_connect(GTK_OBJECT(w), "row-activated", + GTK_SIGNAL_FUNC(listbox_doubleclick), dp); + g_signal_connect(G_OBJECT(sel), "changed", + G_CALLBACK(listbox_selchange), dp); + + if (ctrl->listbox.draglist) { + gtk_tree_view_set_reorderable(GTK_TREE_VIEW(w), TRUE); + g_signal_connect(G_OBJECT(uc->listmodel), "row-inserted", + G_CALLBACK(listbox_reorder), dp); } - if (ctrl->listbox.draglist) { - /* - * GTK doesn't appear to make it easy to - * implement a proper draggable list; so - * instead I'm just going to have to put an Up - * and a Down button to the right of the actual - * list box. Ah well. - */ - GtkWidget *cols, *button; - static const gint percentages[2] = { 80, 20 }; + { + int i; + int cols; - cols = columns_new(4); - columns_set_cols(COLUMNS(cols), 2, percentages); - columns_add(COLUMNS(cols), w, 0, 1); - gtk_widget_show(w); - button = gtk_button_new_with_label("Up"); - columns_add(COLUMNS(cols), button, 1, 1); - gtk_widget_show(button); - gtk_signal_connect(GTK_OBJECT(button), "clicked", - GTK_SIGNAL_FUNC(draglist_up), dp); - gtk_signal_connect(GTK_OBJECT(button), "focus_in_event", - GTK_SIGNAL_FUNC(widget_focus), dp); - button = gtk_button_new_with_label("Down"); - columns_add(COLUMNS(cols), button, 1, 1); - gtk_widget_show(button); - gtk_signal_connect(GTK_OBJECT(button), "clicked", - GTK_SIGNAL_FUNC(draglist_down), dp); - gtk_signal_connect(GTK_OBJECT(button), "focus_in_event", - GTK_SIGNAL_FUNC(widget_focus), dp); + cols = ctrl->listbox.ncols; + cols = cols ? cols : 1; + for (i = 0; i < cols; i++) { + GtkTreeViewColumn *column; + /* + * It appears that GTK 2 doesn't leave us any + * particularly sensible way to honour the + * "percentages" specification in the ctrl + * structure. + */ + column = gtk_tree_view_column_new_with_attributes + ("heading", gtk_cell_renderer_text_new(), + "text", i+1, (char *)NULL); + gtk_tree_view_column_set_sizing + (column, GTK_TREE_VIEW_COLUMN_GROW_ONLY); + gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); + } + } - w = cols; - } + { + GtkWidget *scroll; - } - if (ctrl->generic.label) { - GtkWidget *label, *container; + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type + (GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_IN); + gtk_widget_show(w); + gtk_container_add(GTK_CONTAINER(scroll), w); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_ALWAYS); + gtk_widget_set_size_request + (scroll, -1, + ctrl->listbox.height * get_listitemheight(w)); - label = gtk_label_new(ctrl->generic.label); + w = scroll; + } + } + + if (ctrl->generic.label) { + GtkWidget *label, *container; + GtkRequisition req; + + label = gtk_label_new(ctrl->generic.label); + + shortcut_add(scs, label, ctrl->listbox.shortcut, + SHORTCUT_FOCUS, w); container = columns_new(4); - if (ctrl->listbox.percentwidth == 100) { - columns_add(COLUMNS(container), label, 0, 1); + if (ctrl->listbox.percentwidth == 100) { + columns_add(COLUMNS(container), label, 0, 1); columns_force_left_align(COLUMNS(container), label); - columns_add(COLUMNS(container), w, 0, 1); - } else { - gint percentages[2]; - percentages[1] = ctrl->listbox.percentwidth; - percentages[0] = 100 - ctrl->listbox.percentwidth; - columns_set_cols(COLUMNS(container), 2, percentages); - columns_add(COLUMNS(container), label, 0, 1); + columns_add(COLUMNS(container), w, 0, 1); + } else { + gint percentages[2]; + percentages[1] = ctrl->listbox.percentwidth; + percentages[0] = 100 - ctrl->listbox.percentwidth; + columns_set_cols(COLUMNS(container), 2, percentages); + columns_add(COLUMNS(container), label, 0, 1); columns_force_left_align(COLUMNS(container), label); - columns_add(COLUMNS(container), w, 1, 1); - } - gtk_widget_show(label); - gtk_widget_show(w); - shortcut_add(scs, label, ctrl->listbox.shortcut, - SHORTCUT_UCTRL, uc); - w = container; + columns_add(COLUMNS(container), w, 1, 1); + /* Centre the label vertically. */ + gtk_widget_size_request(w, &req); + gtk_widget_set_usize(label, -1, req.height); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); + } + gtk_widget_show(label); + gtk_widget_show(w); + + w = container; uc->label = label; - } - break; + } + + break; case CTRL_TEXT: /* * Wrapping text widgets don't sit well with the GTK @@ -1944,36 +1823,6 @@ int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) } } break; - case CTRL_LISTBOX: - /* - * If the list is really an option menu, we focus - * and click it. Otherwise we tell it to focus one - * of its children, which appears to do the Right - * Thing. - */ - if (sc->uc->optmenu) { - GdkEventButton bev; - gint returnval; - - gtk_widget_grab_focus(sc->uc->optmenu); - /* Option menus don't work using the "clicked" signal. - * We need to manufacture a button press event :-/ */ - bev.type = GDK_BUTTON_PRESS; - bev.button = 1; - gtk_signal_emit_by_name(GTK_OBJECT(sc->uc->optmenu), - "button_press_event", - &bev, &returnval); - } else { - assert(sc->uc->list != NULL); - -#if GTK_CHECK_VERSION(2,0,0) - gtk_widget_grab_focus(sc->uc->list); -#else - gtk_container_focus(GTK_CONTAINER(sc->uc->list), - GTK_DIR_TAB_FORWARD); -#endif - } - break; } break; } @@ -2098,13 +1947,15 @@ void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw, shortcut_highlight(labelw, chr); } -int get_listitemheight(void) +int get_listitemheight(GtkWidget *w) { - GtkWidget *listitem = gtk_list_item_new_with_label("foo"); - GtkRequisition req; - gtk_widget_size_request(listitem, &req); - gtk_object_sink(GTK_OBJECT(listitem)); - return req.height; + int height; + GtkCellRenderer *cr = gtk_cell_renderer_text_new(); + gtk_cell_renderer_get_size(cr, w, NULL, NULL, NULL, NULL, &height); + g_object_ref(G_OBJECT(cr)); + gtk_object_sink(GTK_OBJECT(cr)); + g_object_unref(G_OBJECT(cr)); + return height; } void set_dialog_action_area(GtkDialog *dlg, GtkWidget *w) @@ -2180,7 +2031,7 @@ int do_config_box(const char *title, Config *cfg, int midsession, { GtkWidget *window, *hbox, *vbox, *cols, *label, *tree, *treescroll, *panels, *panelvbox; - int index, level, listitemheight; + int index, level; struct controlbox *ctrlbox; char *path; #if GTK_CHECK_VERSION(2,0,0) @@ -2201,8 +2052,6 @@ int do_config_box(const char *title, Config *cfg, int midsession, dlg_init(&dp); - listitemheight = get_listitemheight(); - for (index = 0; index < lenof(scs.sc); index++) { scs.sc[index].action = SHORTCUT_EMPTY; } @@ -2268,7 +2117,7 @@ int do_config_box(const char *title, Config *cfg, int midsession, GtkWidget *w; if (!*s->pathname) { - w = layout_ctrls(&dp, &scs, s, listitemheight, GTK_WINDOW(window)); + w = layout_ctrls(&dp, &scs, s, GTK_WINDOW(window)); set_dialog_action_area(GTK_DIALOG(window), w); } else { @@ -2391,9 +2240,7 @@ int do_config_box(const char *title, Config *cfg, int midsession, nselparams++; } - w = layout_ctrls(&dp, - &selparams[nselparams-1].shortcuts, - s, listitemheight, NULL); + w = layout_ctrls(&dp, &selparams[nselparams-1].shortcuts, s, NULL); gtk_box_pack_start(GTK_BOX(panelvbox), w, FALSE, FALSE, 0); gtk_widget_show(w); } @@ -2549,10 +2396,10 @@ int messagebox(GtkWidget *parentwin, char *title, char *msg, int minwid, ...) window = gtk_dialog_new(); gtk_window_set_title(GTK_WINDOW(window), title); - w0 = layout_ctrls(&dp, &scs, s0, 0, GTK_WINDOW(window)); + w0 = layout_ctrls(&dp, &scs, s0, GTK_WINDOW(window)); set_dialog_action_area(GTK_DIALOG(window), w0); gtk_widget_show(w0); - w1 = layout_ctrls(&dp, &scs, s1, 0, GTK_WINDOW(window)); + w1 = layout_ctrls(&dp, &scs, s1, GTK_WINDOW(window)); gtk_container_set_border_width(GTK_CONTAINER(w1), 10); gtk_widget_set_usize(w1, minwid+20, -1); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), @@ -2936,9 +2783,9 @@ gint eventlog_selection_clear(GtkWidget *widget, GdkEventSelection *seldata, * Deselect everything in the list box. */ uc = dlg_find_byctrl(&es->dp, es->listctrl); - es->ignore_selchange = 1; - gtk_list_unselect_all(GTK_LIST(uc->list)); - es->ignore_selchange = 0; + assert(uc->list); + gtk_tree_selection_unselect_all + (gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->list))); sfree(es->seldata); es->sellen = 0; @@ -2953,7 +2800,7 @@ void showeventlog(void *estuff, void *parentwin) GtkWidget *parent = GTK_WIDGET(parentwin); struct controlset *s0, *s1; union control *c; - int listitemheight, index; + int index; char *title; if (es->window) { @@ -2987,18 +2834,14 @@ void showeventlog(void *estuff, void *parentwin) c->listbox.percentages[1] = 10; c->listbox.percentages[2] = 65; - listitemheight = get_listitemheight(); - es->window = window = gtk_dialog_new(); title = dupcat(appname, " Event Log", NULL); gtk_window_set_title(GTK_WINDOW(window), title); sfree(title); - w0 = layout_ctrls(&es->dp, &es->scs, s0, - listitemheight, GTK_WINDOW(window)); + w0 = layout_ctrls(&es->dp, &es->scs, s0, GTK_WINDOW(window)); set_dialog_action_area(GTK_DIALOG(window), w0); gtk_widget_show(w0); - w1 = layout_ctrls(&es->dp, &es->scs, s1, - listitemheight, GTK_WINDOW(window)); + w1 = layout_ctrls(&es->dp, &es->scs, s1, GTK_WINDOW(window)); gtk_container_set_border_width(GTK_CONTAINER(w1), 10); gtk_widget_set_usize(w1, 20 + string_width("LINE OF TEXT GIVING WIDTH OF EVENT LOG" From 29d875f7da9b8b840af9dc3e7d4af99b0837cfea Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 2 Apr 2008 14:50:47 +0000 Subject: [PATCH 37/53] Enable the display of server-side font aliases by default in my font selector. I had previously been worried that the default of not showing aliases interacted badly with the default actual font _being_ specified as an alias. One of those defaults had to change, and I've decided which: `fixed' is staying as Unix PuTTY's default font in defiance of GTK2's vigorous encouragement of Pango. [originally from svn r7960] --- unix/GTK2.TODO | 7 ------- unix/gtkfont.c | 3 ++- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/unix/GTK2.TODO b/unix/GTK2.TODO index e5d4b38d..7b695a50 100644 --- a/unix/GTK2.TODO +++ b/unix/GTK2.TODO @@ -3,13 +3,6 @@ TODO for PuTTY GTK2 port before merging back into main trunk code Things to do before deciding a merge is feasible: - - Although I'm still stubbornly _supporting_ X11 fonts alongside - Pango ones in defiance of standard GTK2 policy, it might be a - good idea to at least switch the default font to Pango's - Monospace 12, not least so that font aliases don't come up - selected by default. Then again, perhaps keeping fixed as the - default is more traditional. Hmm. - - gtkcols.c is currently a minimal-work GTK2 port of my original GTK1 implementation. Someone should go through it and compare it to a real GTK2 container class, to make sure there aren't any diff --git a/unix/gtkfont.c b/unix/gtkfont.c index a2d8f40c..9b95cb3b 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -2304,7 +2304,8 @@ unifontsel *unifontsel_new(const char *wintitle) gtk_table_attach(GTK_TABLE(table), w, 0, 3, 7, 8, GTK_FILL, 0, 0, 0); assert(i == lenof(fs->filter_buttons)); - fs->filter_flags = FONTFLAG_CLIENTSIDE | FONTFLAG_SERVERSIDE; + fs->filter_flags = FONTFLAG_CLIENTSIDE | FONTFLAG_SERVERSIDE | + FONTFLAG_SERVERALIAS; unifontsel_set_filter_buttons(fs); /* From 50d4d056791e443f3cfcf2f705cc505b7df69dbe Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 2 Apr 2008 16:26:01 +0000 Subject: [PATCH 38/53] TODO update: I don't think it's worth switching to GTK2's native shortcut mechanism. The existing code doesn't use any deprecated calls, and translating shortcut text _into_ Pango markup just sounds too unpleasant to do if I don't actually have to. Not to mention that the documentation for the Pango markup language doesn't tell me how to distinguish a mnemonic underscore prefix from a literal underscore in label text, but I know my current code can get that right (the current config box talks about TCP_NODELAY and SO_KEEPALIVE in widget labels that also have functioning shortcuts). [originally from svn r7961] --- unix/GTK2.TODO | 3 --- 1 file changed, 3 deletions(-) diff --git a/unix/GTK2.TODO b/unix/GTK2.TODO index 7b695a50..524458d7 100644 --- a/unix/GTK2.TODO +++ b/unix/GTK2.TODO @@ -9,9 +9,6 @@ Things to do before deciding a merge is feasible: large chunks we should have reimplemented and haven't, or indeed that we shouldn't have reimplemented and have. - - Investigate the shortcut mechanism in GTK2's GtkLabel, and see if - it's worth switching to it from the current ad-hockery. - - Update the autoconf build. Richard B says he had to replace AM_PATH_GTK([1.2.0], with From ed085ca8241ea9077436940cf7ccabb5c89b6086 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 2 Apr 2008 17:04:21 +0000 Subject: [PATCH 39/53] Another tedious chore off the to-do list. I've just checked over my custom Columns layout class to see what fiddly details of GTK2isation were yet to be done. It turns out that all the basic object management got moved out of GTK into a separate library, so that all the gtk_object_* calls are deprecated and g_object_* should be used instead; having done that, though, it all looks perfectly fine. [originally from svn r7962] --- unix/GTK2.TODO | 6 ------ unix/gtkcols.c | 56 +++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/unix/GTK2.TODO b/unix/GTK2.TODO index 524458d7..0453c475 100644 --- a/unix/GTK2.TODO +++ b/unix/GTK2.TODO @@ -3,12 +3,6 @@ TODO for PuTTY GTK2 port before merging back into main trunk code Things to do before deciding a merge is feasible: - - gtkcols.c is currently a minimal-work GTK2 port of my original - GTK1 implementation. Someone should go through it and compare it - to a real GTK2 container class, to make sure there aren't any - large chunks we should have reimplemented and haven't, or indeed - that we shouldn't have reimplemented and have. - - Update the autoconf build. Richard B says he had to replace AM_PATH_GTK([1.2.0], with diff --git a/unix/gtkcols.c b/unix/gtkcols.c index e70400de..cb654b03 100644 --- a/unix/gtkcols.c +++ b/unix/gtkcols.c @@ -26,6 +26,7 @@ static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc); static GtkContainerClass *parent_class = NULL; +#if !GTK_CHECK_VERSION(2,0,0) GtkType columns_get_type(void) { static GtkType columns_type = 0; @@ -47,6 +48,31 @@ GtkType columns_get_type(void) return columns_type; } +#else +GType columns_get_type(void) +{ + static GType columns_type = 0; + + if (!columns_type) { + static const GTypeInfo columns_info = { + sizeof(ColumnsClass), + NULL, + NULL, + (GClassInitFunc) columns_class_init, + NULL, + NULL, + sizeof(Columns), + 0, + (GInstanceInitFunc)columns_init, + }; + + columns_type = g_type_register_static(GTK_TYPE_CONTAINER, "Columns", + &columns_info, 0); + } + + return columns_type; +} +#endif #if !GTK_CHECK_VERSION(2,0,0) static gint (*columns_inherited_focus)(GtkContainer *container, @@ -55,20 +81,21 @@ static gint (*columns_inherited_focus)(GtkContainer *container, static void columns_class_init(ColumnsClass *klass) { - GtkObjectClass *object_class; - GtkWidgetClass *widget_class; - GtkContainerClass *container_class; - - object_class = (GtkObjectClass *)klass; - widget_class = (GtkWidgetClass *)klass; - container_class = (GtkContainerClass *)klass; +#if !GTK_CHECK_VERSION(2,0,0) + GtkObjectClass *object_class = (GtkObjectClass *)klass; + GtkWidgetClass *widget_class = (GtkWidgetClass *)klass; + GtkContainerClass *container_class = (GtkContainerClass *)klass; +#else + /* GObjectClass *object_class = G_OBJECT_CLASS(klass); */ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass); +#endif +#if !GTK_CHECK_VERSION(2,0,0) + parent_class = g_type_class_peek_parent(klass); +#else parent_class = gtk_type_class(GTK_TYPE_CONTAINER); - - /* - * FIXME: do we have to do all this faffing with set_arg, - * get_arg and child_arg_type? Ick. - */ +#endif widget_class->map = columns_map; widget_class->unmap = columns_unmap; @@ -300,7 +327,12 @@ GtkWidget *columns_new(gint spacing) { Columns *cols; +#if !GTK_CHECK_VERSION(2,0,0) cols = gtk_type_new(columns_get_type()); +#else + cols = g_object_new(TYPE_COLUMNS, NULL); +#endif + cols->spacing = spacing; return GTK_WIDGET(cols); From 9fe425c2810cf6026542624f2043ddff875451cd Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 2 Apr 2008 17:09:53 +0000 Subject: [PATCH 40/53] It's nice to be able to run mkauto.sh from inside the unix subdir as well as from outside. [originally from svn r7963] --- mkauto.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mkauto.sh b/mkauto.sh index cdee6221..5e394f8d 100755 --- a/mkauto.sh +++ b/mkauto.sh @@ -3,6 +3,10 @@ # It's separate from mkfiles.pl because it won't work (and isn't needed) # on a non-Unix system. +# It's nice to be able to run this from inside the unix subdir as +# well as from outside. +test -f unix.h && cd .. + # Track down automake's copy of install-sh cp `aclocal --print-ac-dir | sed 's/aclocal$/automake/'`/install-sh unix/. (cd unix && autoreconf && rm -rf aclocal.m4 autom4te.cache) From a128ee85889373f1ccb0cf22cf274d0a99f33175 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 2 Apr 2008 17:32:17 +0000 Subject: [PATCH 41/53] Update autoconf for GTK 2. We now check for both GTK2 and GTK1, and in the presence of GTK 2 we also check to see whether we have a prehistoric Pango (since Pango itself helpfully doesn't provide that functionality, bah). [originally from svn r7964] --- unix/GTK2.TODO | 20 ++++---------------- unix/configure.ac | 24 +++++++++++++++++++++++- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/unix/GTK2.TODO b/unix/GTK2.TODO index 0453c475..a0183c96 100644 --- a/unix/GTK2.TODO +++ b/unix/GTK2.TODO @@ -1,25 +1,13 @@ TODO for PuTTY GTK2 port before merging back into main trunk code ----------------------------------------------------------------- -Things to do before deciding a merge is feasible: - - - Update the autoconf build. Richard B says he had to replace - AM_PATH_GTK([1.2.0], - with - AM_PATH_GTK_2_0([2.0.0], - + also I'll need to detect early Pangoi and enable my magic - switches in gtkfont.c. - + and I'll probably also want to detect GTK2 vs GTK1 - automatically - _and_ provide a command line switch on - configure to select one manually. - -Things to do once GTK2 development is complete: +Things left to do: - Make sure we haven't broken GTK1. + In particular, I know I _have_ broken GTK1 by taking out all the GTK1-style list box code. Put it all back in under ifdefs, which will be unpleasant but necessary. -Things to do at point of actual merge: - - - Mention Colin in the website's licence page. + - Merge to trunk. Colin is already mentioned in the licence in all + the branched copies, but mention him in the _website's_ licence + page too. diff --git a/unix/configure.ac b/unix/configure.ac index 5b0c0c9e..846a35cd 100644 --- a/unix/configure.ac +++ b/unix/configure.ac @@ -18,7 +18,23 @@ AC_CHECK_HEADERS([utmpx.h sys/select.h],,,[ #include #include ]) -AM_PATH_GTK([1.2.0], [all_targets="all-cli all-gtk"], [all_targets="all-cli"]) +# Look for both GTK 1 and GTK 2. +AM_PATH_GTK([1.2.0], [gtk=1], [gtk=none]) +AM_PATH_GTK_2_0([2.0.0], [gtk=2], []) +if test "$gtk" = "none"; then + all_targets="all-cli" +else + all_targets="all-cli all-gtk" +fi +if test "$gtk" = "2"; then + ac_save_CFLAGS="$CFLAGS" + ac_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $GTK_CFLAGS" + LIBS="$GTK_LIBS $LIBS" + AC_CHECK_FUNCS([pango_font_family_is_monospace pango_font_map_list_families]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" +fi AC_SUBST([all_targets]) AC_SEARCH_LIBS([socket], [xnet]) @@ -48,4 +64,10 @@ AH_BOTTOM([ #ifndef HAVE_SYS_SELECT_H # define HAVE_NO_SYS_SELECT_H #endif +#ifndef HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE +# define PANGO_PRE_1POINT4 +#endif +#ifndef HAVE_PANGO_FONT_MAP_LIST_FAMILIES +# define PANGO_PRE_1POINT6 +#endif ]) From 54e26eb7efa4132cf300a0fede62b5f380e53b0a Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 4 Apr 2008 10:16:24 +0000 Subject: [PATCH 42/53] Rename a structure field to avoid clashing with one of the old GTK1 ones. (I'm going to merge the GTK1 list code back in under ifdefs, and I want none of the disputed structure fields to have the same names, so that I'll reliably be told by the compiler if I keep the wrong piece of code outside the ifdef.) [originally from svn r7965] --- unix/gtkdlg.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index b2cb551c..c3e76f41 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -41,10 +41,10 @@ struct uctrl { int privdata_needs_free; GtkWidget **buttons; int nbuttons; /* for radio buttons */ GtkWidget *entry; /* for editbox, filesel, fontsel */ - GtkWidget *combo; /* for combo box (either editable or not) */ - GtkWidget *list; /* for list box (list, droplist, combo box) */ - GtkListStore *listmodel; /* for all types of list box */ GtkWidget *button; /* for filesel, fontsel */ + GtkWidget *combo; /* for combo box (either editable or not) */ + GtkWidget *treeview; /* for list box (list, droplist, combo box) */ + GtkListStore *listmodel; /* for all types of list box */ GtkWidget *text; /* for text */ GtkWidget *label; /* for dlg_label_change */ GtkAdjustment *adj; /* for the scrollbar in a list box */ @@ -465,8 +465,8 @@ int dlg_listbox_index(union control *ctrl, void *dlg) gint *indices; int ret; - assert(uc->list != NULL); - treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->list)); + assert(uc->treeview != NULL); + treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview)); if (gtk_tree_selection_count_selected_rows(treesel) != 1) return -1; @@ -517,8 +517,8 @@ int dlg_listbox_issel(union control *ctrl, void *dlg, int index) GtkTreePath *path; int ret; - assert(uc->list != NULL); - treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->list)); + assert(uc->treeview != NULL); + treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview)); path = gtk_tree_path_new_from_indices(index, -1); ret = gtk_tree_selection_path_is_selected(treesel, path); @@ -546,12 +546,12 @@ void dlg_listbox_select(union control *ctrl, void *dlg, int index) GtkTreeSelection *treesel; GtkTreePath *path; - assert(uc->list != NULL); - treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->list)); + assert(uc->treeview != NULL); + treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview)); path = gtk_tree_path_new_from_indices(index, -1); gtk_tree_selection_select_path(treesel, path); - gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(uc->list), + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(uc->treeview), path, NULL, FALSE, 0.0, 0.0); gtk_tree_path_free(path); } @@ -710,8 +710,8 @@ void dlg_set_focus(union control *ctrl, void *dlg) * There might be a combo box (drop-down list) here, or a * proper list box. */ - if (uc->list) { - gtk_widget_grab_focus(uc->list); + if (uc->treeview) { + gtk_widget_grab_focus(uc->treeview); } else if (uc->combo) { gtk_widget_grab_focus(uc->combo); } @@ -1225,7 +1225,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, uc->privdata = NULL; uc->privdata_needs_free = FALSE; uc->buttons = NULL; - uc->entry = uc->combo = uc->list = NULL; + uc->entry = uc->combo = uc->treeview = NULL; uc->listmodel = NULL; uc->button = uc->text = NULL; uc->label = NULL; @@ -1516,7 +1516,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, gtk_tree_selection_set_mode (sel, ctrl->listbox.multisel ? GTK_SELECTION_MULTIPLE : GTK_SELECTION_SINGLE); - uc->list = w; + uc->treeview = w; gtk_signal_connect(GTK_OBJECT(w), "row-activated", GTK_SIGNAL_FUNC(listbox_doubleclick), dp); g_signal_connect(G_OBJECT(sel), "changed", @@ -2783,9 +2783,9 @@ gint eventlog_selection_clear(GtkWidget *widget, GdkEventSelection *seldata, * Deselect everything in the list box. */ uc = dlg_find_byctrl(&es->dp, es->listctrl); - assert(uc->list); + assert(uc->treeview); gtk_tree_selection_unselect_all - (gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->list))); + (gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview))); sfree(es->seldata); es->sellen = 0; From ee92c21e53700e490d6a1a8f6d65c32e6cb88fb2 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 4 Apr 2008 10:56:26 +0000 Subject: [PATCH 43/53] Reinstate all the GTK1-specific code under ifdefs, and verify that we can now build and run successfully using both GTK1 and GTK2 by giving appropriate options to make. (Specifically, to override the default of GTK2 in favour of GTK1, "make GTK_CONFIG=gtk-config".) [originally from svn r7966] --- unix/GTK2.TODO | 6 +- unix/gtkcols.c | 6 +- unix/gtkdlg.c | 923 +++++++++++++++++++++++++++++++++++++++++++++---- unix/gtkfont.c | 15 +- 4 files changed, 881 insertions(+), 69 deletions(-) diff --git a/unix/GTK2.TODO b/unix/GTK2.TODO index a0183c96..3ab394cf 100644 --- a/unix/GTK2.TODO +++ b/unix/GTK2.TODO @@ -3,10 +3,8 @@ TODO for PuTTY GTK2 port before merging back into main trunk code Things left to do: - - Make sure we haven't broken GTK1. - + In particular, I know I _have_ broken GTK1 by taking out all - the GTK1-style list box code. Put it all back in under ifdefs, - which will be unpleasant but necessary. + - I apparently missed out a piece of code when doing the new GTK2 + list box: shortcut activations for list boxes are missing. - Merge to trunk. Colin is already mentioned in the licence in all the branched copies, but mention him in the _website's_ licence diff --git a/unix/gtkcols.c b/unix/gtkcols.c index cb654b03..8cb5d14f 100644 --- a/unix/gtkcols.c +++ b/unix/gtkcols.c @@ -82,7 +82,7 @@ static gint (*columns_inherited_focus)(GtkContainer *container, static void columns_class_init(ColumnsClass *klass) { #if !GTK_CHECK_VERSION(2,0,0) - GtkObjectClass *object_class = (GtkObjectClass *)klass; + /* GtkObjectClass *object_class = (GtkObjectClass *)klass; */ GtkWidgetClass *widget_class = (GtkWidgetClass *)klass; GtkContainerClass *container_class = (GtkContainerClass *)klass; #else @@ -92,9 +92,9 @@ static void columns_class_init(ColumnsClass *klass) #endif #if !GTK_CHECK_VERSION(2,0,0) - parent_class = g_type_class_peek_parent(klass); -#else parent_class = gtk_type_class(GTK_TYPE_CONTAINER); +#else + parent_class = g_type_class_peek_parent(klass); #endif widget_class->map = columns_map; diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index c3e76f41..08bdf239 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -42,9 +42,15 @@ struct uctrl { GtkWidget **buttons; int nbuttons; /* for radio buttons */ GtkWidget *entry; /* for editbox, filesel, fontsel */ GtkWidget *button; /* for filesel, fontsel */ +#if !GTK_CHECK_VERSION(2,0,0) + GtkWidget *list; /* for combobox, listbox */ + GtkWidget *menu; /* for optionmenu (==droplist) */ + GtkWidget *optmenu; /* also for optionmenu */ +#else GtkWidget *combo; /* for combo box (either editable or not) */ GtkWidget *treeview; /* for list box (list, droplist, combo box) */ GtkListStore *listmodel; /* for all types of list box */ +#endif GtkWidget *text; /* for text */ GtkWidget *label; /* for dlg_label_change */ GtkAdjustment *adj; /* for the scrollbar in a list box */ @@ -97,6 +103,17 @@ static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event, static void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw, int chr, int action, void *ptr); static void shortcut_highlight(GtkWidget *label, int chr); +#if !GTK_CHECK_VERSION(2,0,0) +static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event, + gpointer data); +static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event, + gpointer data); +static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event, + gpointer data); +static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event, + gpointer data); +static void menuitem_activate(GtkMenuItem *item, gpointer data); +#endif static void coloursel_ok(GtkButton *button, gpointer data); static void coloursel_cancel(GtkButton *button, gpointer data); static void window_destroy(GtkWidget *widget, gpointer data); @@ -291,6 +308,17 @@ void dlg_editbox_set(union control *ctrl, void *dlg, char const *text) char *tmpstring; assert(uc->ctrl->generic.type == CTRL_EDITBOX); +#if !GTK_CHECK_VERSION(2,0,0) + /* + * In GTK 1, `entry' is valid for combo boxes and edit boxes + * alike. + */ + assert(uc->entry != NULL); + entry = uc->entry; +#else + /* + * In GTK 2, combo boxes use a completely different widget. + */ if (!uc->ctrl->editbox.has_list) { assert(uc->entry != NULL); entry = uc->entry; @@ -298,6 +326,7 @@ void dlg_editbox_set(union control *ctrl, void *dlg, char const *text) assert(uc->combo != NULL); entry = gtk_bin_get_child(GTK_BIN(uc->combo)); } +#endif /* * GTK 2 implements gtk_entry_set_text by means of two separate @@ -328,11 +357,14 @@ void dlg_editbox_get(union control *ctrl, void *dlg, char *buffer, int length) struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_EDITBOX); +#if GTK_CHECK_VERSION(2,0,0) if (!uc->ctrl->editbox.has_list) { +#endif assert(uc->entry != NULL); strncpy(buffer, gtk_entry_get_text(GTK_ENTRY(uc->entry)), length); buffer[length-1] = '\0'; +#if GTK_CHECK_VERSION(2,0,0) } else { assert(uc->combo != NULL); strncpy(buffer, @@ -340,8 +372,18 @@ void dlg_editbox_get(union control *ctrl, void *dlg, char *buffer, int length) length); buffer[length-1] = '\0'; } +#endif } +#if !GTK_CHECK_VERSION(2,0,0) +static void container_remove_and_destroy(GtkWidget *w, gpointer data) +{ + GtkContainer *cont = GTK_CONTAINER(data); + /* gtk_container_remove will unref the widget for us; we need not. */ + gtk_container_remove(cont, w); +} +#endif + /* The `listbox' functions can also apply to combo boxes. */ void dlg_listbox_clear(union control *ctrl, void *dlg) { @@ -350,26 +392,50 @@ void dlg_listbox_clear(union control *ctrl, void *dlg) assert(uc->ctrl->generic.type == CTRL_EDITBOX || uc->ctrl->generic.type == CTRL_LISTBOX); - assert(uc->listmodel != NULL); +#if !GTK_CHECK_VERSION(2,0,0) + assert(uc->menu != NULL || uc->list != NULL); + if (uc->menu) { + gtk_container_foreach(GTK_CONTAINER(uc->menu), + container_remove_and_destroy, + GTK_CONTAINER(uc->menu)); + } else { + gtk_list_clear_items(GTK_LIST(uc->list), 0, -1); + } +#else + assert(uc->listmodel != NULL); gtk_list_store_clear(uc->listmodel); +#endif } void dlg_listbox_del(union control *ctrl, void *dlg, int index) { struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - GtkTreePath *path; - GtkTreeIter iter; assert(uc->ctrl->generic.type == CTRL_EDITBOX || uc->ctrl->generic.type == CTRL_LISTBOX); - assert(uc->listmodel != NULL); - path = gtk_tree_path_new_from_indices(index, -1); - gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path); - gtk_list_store_remove(uc->listmodel, &iter); - gtk_tree_path_free(path); +#if !GTK_CHECK_VERSION(2,0,0) + assert(uc->menu != NULL || uc->list != NULL); + if (uc->menu) { + gtk_container_remove + (GTK_CONTAINER(uc->menu), + g_list_nth_data(GTK_MENU_SHELL(uc->menu)->children, index)); + } else { + gtk_list_clear_items(GTK_LIST(uc->list), index, index+1); + } +#else + { + GtkTreePath *path; + GtkTreeIter iter; + assert(uc->listmodel != NULL); + path = gtk_tree_path_new_from_indices(index, -1); + gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path); + gtk_list_store_remove(uc->listmodel, &iter); + gtk_tree_path_free(path); + } +#endif } void dlg_listbox_add(union control *ctrl, void *dlg, char const *text) @@ -389,54 +455,189 @@ void dlg_listbox_addwithid(union control *ctrl, void *dlg, { struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - GtkTreeIter iter; - int i, cols; assert(uc->ctrl->generic.type == CTRL_EDITBOX || uc->ctrl->generic.type == CTRL_LISTBOX); - assert(uc->listmodel); - - dp->flags |= FLAG_UPDATING_LISTBOX;/* inhibit drag-list update function */ - gtk_list_store_append(uc->listmodel, &iter); - dp->flags &= ~FLAG_UPDATING_LISTBOX; - gtk_list_store_set(uc->listmodel, &iter, 0, id, -1); /* - * Now go through text and divide it into columns at the tabs, - * as necessary. + * This routine is long and complicated in both GTK 1 and 2, + * and completely different. Sigh. */ - cols = (uc->ctrl->generic.type == CTRL_LISTBOX ? ctrl->listbox.ncols : 1); - cols = cols ? cols : 1; - for (i = 0; i < cols; i++) { - int collen = strcspn(text, "\t"); - char *tmpstr = snewn(collen+1, char); - memcpy(tmpstr, text, collen); - tmpstr[collen] = '\0'; - gtk_list_store_set(uc->listmodel, &iter, i+1, tmpstr, -1); - sfree(tmpstr); - text += collen; - if (*text) text++; +#if !GTK_CHECK_VERSION(2,0,0) + + assert(uc->menu != NULL || uc->list != NULL); + + dp->flags |= FLAG_UPDATING_COMBO_LIST; + + if (uc->menu) { + /* + * List item in a drop-down (but non-combo) list. Tabs are + * ignored; we just provide a standard menu item with the + * text. + */ + GtkWidget *menuitem = gtk_menu_item_new_with_label(text); + + gtk_container_add(GTK_CONTAINER(uc->menu), menuitem); + gtk_widget_show(menuitem); + + gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", + GINT_TO_POINTER(id)); + gtk_signal_connect(GTK_OBJECT(menuitem), "activate", + GTK_SIGNAL_FUNC(menuitem_activate), dp); + } else if (!uc->entry) { + /* + * List item in a non-combo-box list box. We make all of + * these Columns containing GtkLabels. This allows us to do + * the nasty force_left hack irrespective of whether there + * are tabs in the thing. + */ + GtkWidget *listitem = gtk_list_item_new(); + GtkWidget *cols = columns_new(10); + gint *percents; + int i, ncols; + + /* Count the tabs in the text, and hence determine # of columns. */ + ncols = 1; + for (i = 0; text[i]; i++) + if (text[i] == '\t') + ncols++; + + assert(ncols <= + (uc->ctrl->listbox.ncols ? uc->ctrl->listbox.ncols : 1)); + percents = snewn(ncols, gint); + percents[ncols-1] = 100; + for (i = 0; i < ncols-1; i++) { + percents[i] = uc->ctrl->listbox.percentages[i]; + percents[ncols-1] -= percents[i]; + } + columns_set_cols(COLUMNS(cols), ncols, percents); + sfree(percents); + + for (i = 0; i < ncols; i++) { + int len = strcspn(text, "\t"); + char *dup = dupprintf("%.*s", len, text); + GtkWidget *label; + + text += len; + if (*text) text++; + label = gtk_label_new(dup); + sfree(dup); + + columns_add(COLUMNS(cols), label, i, 1); + columns_force_left_align(COLUMNS(cols), label); + gtk_widget_show(label); + } + gtk_container_add(GTK_CONTAINER(listitem), cols); + gtk_widget_show(cols); + gtk_container_add(GTK_CONTAINER(uc->list), listitem); + gtk_widget_show(listitem); + + if (ctrl->listbox.multisel) { + gtk_signal_connect(GTK_OBJECT(listitem), "key_press_event", + GTK_SIGNAL_FUNC(listitem_multi_key), uc->adj); + } else { + gtk_signal_connect(GTK_OBJECT(listitem), "key_press_event", + GTK_SIGNAL_FUNC(listitem_single_key), uc->adj); + } + gtk_signal_connect(GTK_OBJECT(listitem), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + gtk_signal_connect(GTK_OBJECT(listitem), "button_press_event", + GTK_SIGNAL_FUNC(listitem_button_press), dp); + gtk_signal_connect(GTK_OBJECT(listitem), "button_release_event", + GTK_SIGNAL_FUNC(listitem_button_release), dp); + gtk_object_set_data(GTK_OBJECT(listitem), "user-data", + GINT_TO_POINTER(id)); + } else { + /* + * List item in a combo-box list, which means the sensible + * thing to do is make it a perfectly normal label. Hence + * tabs are disregarded. + */ + GtkWidget *listitem = gtk_list_item_new_with_label(text); + + gtk_container_add(GTK_CONTAINER(uc->list), listitem); + gtk_widget_show(listitem); + + gtk_object_set_data(GTK_OBJECT(listitem), "user-data", + GINT_TO_POINTER(id)); } + + dp->flags &= ~FLAG_UPDATING_COMBO_LIST; + +#else + + { + GtkTreeIter iter; + int i, cols; + + assert(uc->listmodel); + + dp->flags |= FLAG_UPDATING_LISTBOX;/* inhibit drag-list update */ + gtk_list_store_append(uc->listmodel, &iter); + dp->flags &= ~FLAG_UPDATING_LISTBOX; + gtk_list_store_set(uc->listmodel, &iter, 0, id, -1); + + /* + * Now go through text and divide it into columns at the tabs, + * as necessary. + */ + cols = (uc->ctrl->generic.type == CTRL_LISTBOX ? ctrl->listbox.ncols : 1); + cols = cols ? cols : 1; + for (i = 0; i < cols; i++) { + int collen = strcspn(text, "\t"); + char *tmpstr = snewn(collen+1, char); + memcpy(tmpstr, text, collen); + tmpstr[collen] = '\0'; + gtk_list_store_set(uc->listmodel, &iter, i+1, tmpstr, -1); + sfree(tmpstr); + text += collen; + if (*text) text++; + } + } + +#endif + } int dlg_listbox_getid(union control *ctrl, void *dlg, int index) { struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - GtkTreePath *path; - GtkTreeIter iter; - int ret; assert(uc->ctrl->generic.type == CTRL_EDITBOX || uc->ctrl->generic.type == CTRL_LISTBOX); - assert(uc->listmodel != NULL); - path = gtk_tree_path_new_from_indices(index, -1); - gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path); - gtk_tree_model_get(GTK_TREE_MODEL(uc->listmodel), &iter, 0, &ret, -1); - gtk_tree_path_free(path); +#if !GTK_CHECK_VERSION(2,0,0) + { + GList *children; + GtkObject *item; - return ret; + assert(uc->menu != NULL || uc->list != NULL); + + children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu : + uc->list)); + item = GTK_OBJECT(g_list_nth_data(children, index)); + g_list_free(children); + + return GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(item), + "user-data")); + } +#else + { + GtkTreePath *path; + GtkTreeIter iter; + int ret; + + assert(uc->listmodel != NULL); + + path = gtk_tree_path_new_from_indices(index, -1); + gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path); + gtk_tree_model_get(GTK_TREE_MODEL(uc->listmodel), &iter, 0, &ret, -1); + gtk_tree_path_free(path); + + return ret; + } +#endif } /* dlg_listbox_index returns <0 if no single element is selected. */ @@ -448,6 +649,39 @@ int dlg_listbox_index(union control *ctrl, void *dlg) assert(uc->ctrl->generic.type == CTRL_EDITBOX || uc->ctrl->generic.type == CTRL_LISTBOX); +#if !GTK_CHECK_VERSION(2,0,0) + + { + GList *children; + GtkWidget *item, *activeitem; + int i; + int selected = -1; + + assert(uc->menu != NULL || uc->list != NULL); + + if (uc->menu) + activeitem = gtk_menu_get_active(GTK_MENU(uc->menu)); + else + activeitem = NULL; /* unnecessarily placate gcc */ + + children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu : + uc->list)); + for (i = 0; children!=NULL && (item = GTK_WIDGET(children->data))!=NULL; + i++, children = children->next) { + if (uc->menu ? activeitem == item : + GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED) { + if (selected == -1) + selected = i; + else + selected = -2; + } + } + g_list_free(children); + return selected < 0 ? -1 : selected; + } + +#else + /* * We have to do this completely differently for a combo box * (editable or otherwise) and a full-size list box. @@ -492,6 +726,8 @@ int dlg_listbox_index(union control *ctrl, void *dlg) return ret; } + +#endif } int dlg_listbox_issel(union control *ctrl, void *dlg, int index) @@ -502,6 +738,31 @@ int dlg_listbox_issel(union control *ctrl, void *dlg, int index) assert(uc->ctrl->generic.type == CTRL_EDITBOX || uc->ctrl->generic.type == CTRL_LISTBOX); +#if !GTK_CHECK_VERSION(2,0,0) + + { + GList *children; + GtkWidget *item, *activeitem; + + assert(uc->ctrl->generic.type == CTRL_EDITBOX || + uc->ctrl->generic.type == CTRL_LISTBOX); + assert(uc->menu != NULL || uc->list != NULL); + + children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu : + uc->list)); + item = GTK_WIDGET(g_list_nth_data(children, index)); + g_list_free(children); + + if (uc->menu) { + activeitem = gtk_menu_get_active(GTK_MENU(uc->menu)); + return item == activeitem; + } else { + return GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED; + } + } + +#else + /* * We have to do this completely differently for a combo box * (editable or otherwise) and a full-size list box. @@ -526,6 +787,8 @@ int dlg_listbox_issel(union control *ctrl, void *dlg, int index) return ret; } + +#endif } void dlg_listbox_select(union control *ctrl, void *dlg, int index) @@ -536,6 +799,46 @@ void dlg_listbox_select(union control *ctrl, void *dlg, int index) assert(uc->ctrl->generic.type == CTRL_EDITBOX || uc->ctrl->generic.type == CTRL_LISTBOX); +#if !GTK_CHECK_VERSION(2,0,0) + + assert(uc->optmenu != NULL || uc->list != NULL); + + if (uc->optmenu) { + gtk_option_menu_set_history(GTK_OPTION_MENU(uc->optmenu), index); + } else { + int nitems; + GList *items; + gdouble newtop, newbot; + + gtk_list_select_item(GTK_LIST(uc->list), index); + + /* + * Scroll the list box if necessary to ensure the newly + * selected item is visible. + */ + items = gtk_container_children(GTK_CONTAINER(uc->list)); + nitems = g_list_length(items); + if (nitems > 0) { + int modified = FALSE; + g_list_free(items); + newtop = uc->adj->lower + + (uc->adj->upper - uc->adj->lower) * index / nitems; + newbot = uc->adj->lower + + (uc->adj->upper - uc->adj->lower) * (index+1) / nitems; + if (uc->adj->value > newtop) { + modified = TRUE; + uc->adj->value = newtop; + } else if (uc->adj->value < newbot - uc->adj->page_size) { + modified = TRUE; + uc->adj->value = newbot - uc->adj->page_size; + } + if (modified) + gtk_adjustment_value_changed(uc->adj); + } + } + +#else + /* * We have to do this completely differently for a combo box * (editable or otherwise) and a full-size list box. @@ -555,6 +858,9 @@ void dlg_listbox_select(union control *ctrl, void *dlg, int index) path, NULL, FALSE, 0.0, 0.0); gtk_tree_path_free(path); } + +#endif + } void dlg_text_set(union control *ctrl, void *dlg, char const *text) @@ -686,10 +992,13 @@ void dlg_set_focus(union control *ctrl, void *dlg) if (uc->entry) { /* Anything containing an edit box gets that focused. */ gtk_widget_grab_focus(uc->entry); - } else if (uc->combo) { + } +#if GTK_CHECK_VERSION(2,0,0) + else if (uc->combo) { /* Failing that, there'll be a combo box. */ gtk_widget_grab_focus(uc->combo); } +#endif break; case CTRL_RADIO: /* @@ -706,6 +1015,19 @@ void dlg_set_focus(union control *ctrl, void *dlg) } break; case CTRL_LISTBOX: +#if !GTK_CHECK_VERSION(2,0,0) + /* + * If the list is really an option menu, we focus it. + * Otherwise we tell it to focus one of its children, which + * appears to do the Right Thing. + */ + if (uc->optmenu) { + gtk_widget_grab_focus(uc->optmenu); + } else { + assert(uc->list != NULL); + gtk_container_focus(GTK_CONTAINER(uc->list), GTK_DIR_TAB_FORWARD); + } +#else /* * There might be a combo box (drop-down list) here, or a * proper list box. @@ -715,6 +1037,7 @@ void dlg_set_focus(union control *ctrl, void *dlg) } else if (uc->combo) { gtk_widget_grab_focus(uc->combo); } +#endif break; } } @@ -961,6 +1284,206 @@ static gboolean editbox_lostfocus(GtkWidget *ed, GdkEventFocus *event, return FALSE; } +#if !GTK_CHECK_VERSION(2,0,0) + +/* + * GTK 1 list box event handlers. + */ + +static gboolean listitem_key(GtkWidget *item, GdkEventKey *event, + gpointer data, int multiple) +{ + GtkAdjustment *adj = GTK_ADJUSTMENT(data); + + if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up || + event->keyval == GDK_Down || event->keyval == GDK_KP_Down || + event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up || + event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down) { + /* + * Up, Down, PgUp or PgDn have been pressed on a ListItem + * in a list box. So, if the list box is single-selection: + * + * - if the list item in question isn't already selected, + * we simply select it. + * - otherwise, we find the next one (or next + * however-far-away) in whichever direction we're going, + * and select that. + * + in this case, we must also fiddle with the + * scrollbar to ensure the newly selected item is + * actually visible. + * + * If it's multiple-selection, we do all of the above + * except actually selecting anything, so we move the focus + * and fiddle the scrollbar to follow it. + */ + GtkWidget *list = item->parent; + + gtk_signal_emit_stop_by_name(GTK_OBJECT(item), "key_press_event"); + + if (!multiple && + GTK_WIDGET_STATE(item) != GTK_STATE_SELECTED) { + gtk_list_select_child(GTK_LIST(list), item); + } else { + int direction = + (event->keyval==GDK_Up || event->keyval==GDK_KP_Up || + event->keyval==GDK_Page_Up || event->keyval==GDK_KP_Page_Up) + ? -1 : +1; + int step = + (event->keyval==GDK_Page_Down || + event->keyval==GDK_KP_Page_Down || + event->keyval==GDK_Page_Up || event->keyval==GDK_KP_Page_Up) + ? 2 : 1; + int i, n; + GList *children, *chead; + + chead = children = gtk_container_children(GTK_CONTAINER(list)); + + n = g_list_length(children); + + if (step == 2) { + /* + * Figure out how many list items to a screenful, + * and adjust the step appropriately. + */ + step = 0.5 + adj->page_size * n / (adj->upper - adj->lower); + step--; /* go by one less than that */ + } + + i = 0; + while (children != NULL) { + if (item == children->data) + break; + children = children->next; + i++; + } + + while (step > 0) { + if (direction < 0 && i > 0) + children = children->prev, i--; + else if (direction > 0 && i < n-1) + children = children->next, i++; + step--; + } + + if (children && children->data) { + if (!multiple) + gtk_list_select_child(GTK_LIST(list), + GTK_WIDGET(children->data)); + gtk_widget_grab_focus(GTK_WIDGET(children->data)); + gtk_adjustment_clamp_page + (adj, + adj->lower + (adj->upper-adj->lower) * i / n, + adj->lower + (adj->upper-adj->lower) * (i+1) / n); + } + + g_list_free(chead); + } + return TRUE; + } + + return FALSE; +} + +static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event, + gpointer data) +{ + return listitem_key(item, event, data, FALSE); +} + +static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event, + gpointer data) +{ + return listitem_key(item, event, data, TRUE); +} + +static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event, + gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item)); + switch (event->type) { + default: + case GDK_BUTTON_PRESS: uc->nclicks = 1; break; + case GDK_2BUTTON_PRESS: uc->nclicks = 2; break; + case GDK_3BUTTON_PRESS: uc->nclicks = 3; break; + } + return FALSE; +} + +static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event, + gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item)); + if (uc->nclicks>1) { + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION); + return TRUE; + } + return FALSE; +} + +static void list_selchange(GtkList *list, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(list)); + if (!uc) return; + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); +} + +static void menuitem_activate(GtkMenuItem *item, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + GtkWidget *menushell = GTK_WIDGET(item)->parent; + gpointer optmenu = gtk_object_get_data(GTK_OBJECT(menushell), "user-data"); + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(optmenu)); + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); +} + +static void draglist_move(struct dlgparam *dp, struct uctrl *uc, int direction) +{ + int index = dlg_listbox_index(uc->ctrl, dp); + GList *children = gtk_container_children(GTK_CONTAINER(uc->list)); + GtkWidget *child; + + if ((index < 0) || + (index == 0 && direction < 0) || + (index == g_list_length(children)-1 && direction > 0)) { + gdk_beep(); + return; + } + + child = g_list_nth_data(children, index); + gtk_widget_ref(child); + gtk_list_clear_items(GTK_LIST(uc->list), index, index+1); + g_list_free(children); + + children = NULL; + children = g_list_append(children, child); + gtk_list_insert_items(GTK_LIST(uc->list), children, index + direction); + gtk_list_select_item(GTK_LIST(uc->list), index + direction); + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE); +} + +static void draglist_up(GtkButton *button, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button)); + draglist_move(dp, uc, -1); +} + +static void draglist_down(GtkButton *button, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button)); + draglist_move(dp, uc, +1); +} + +#else /* !GTK_CHECK_VERSION(2,0,0) */ + +/* + * GTK 2 list box event handlers. + */ + static void listbox_doubleclick(GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *column, gpointer data) { @@ -1043,6 +1566,8 @@ static void listbox_reorder(GtkTreeModel *treemodel, GtkTreePath *path, } } +#endif /* !GTK_CHECK_VERSION(2,0,0) */ + static void filesel_ok(GtkButton *button, gpointer data) { /* struct dlgparam *dp = (struct dlgparam *)data; */ @@ -1056,6 +1581,17 @@ static void filesel_ok(GtkButton *button, gpointer data) static void fontsel_ok(GtkButton *button, gpointer data) { /* struct dlgparam *dp = (struct dlgparam *)data; */ + +#if !GTK_CHECK_VERSION(2,0,0) + + gpointer fontsel = gtk_object_get_data(GTK_OBJECT(button), "user-data"); + struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(fontsel), "user-data"); + const char *name = gtk_font_selection_dialog_get_font_name + (GTK_FONT_SELECTION_DIALOG(fontsel)); + gtk_entry_set_text(GTK_ENTRY(uc->entry), name); + +#else + unifontsel *fontsel = (unifontsel *)gtk_object_get_data (GTK_OBJECT(button), "user-data"); struct uctrl *uc = (struct uctrl *)fontsel->user_data; @@ -1063,6 +1599,8 @@ static void fontsel_ok(GtkButton *button, gpointer data) assert(name); /* should always be ok after OK pressed */ gtk_entry_set_text(GTK_ENTRY(uc->entry), name); sfree(name); + +#endif } static void coloursel_ok(GtkButton *button, gpointer data) @@ -1117,6 +1655,68 @@ static void filefont_clicked(GtkButton *button, gpointer data) if (uc->ctrl->generic.type == CTRL_FONTSELECT) { const gchar *fontname = gtk_entry_get_text(GTK_ENTRY(uc->entry)); + +#if !GTK_CHECK_VERSION(2,0,0) + + /* + * Use the GTK 1 standard font selector. + */ + + gchar *spacings[] = { "c", "m", NULL }; + GtkWidget *fontsel = + gtk_font_selection_dialog_new("Select a font"); + gtk_window_set_modal(GTK_WINDOW(fontsel), TRUE); + gtk_font_selection_dialog_set_filter + (GTK_FONT_SELECTION_DIALOG(fontsel), + GTK_FONT_FILTER_BASE, GTK_FONT_ALL, + NULL, NULL, NULL, NULL, spacings, NULL); + if (!gtk_font_selection_dialog_set_font_name + (GTK_FONT_SELECTION_DIALOG(fontsel), fontname)) { + /* + * If the font name wasn't found as it was, try opening + * it and extracting its FONT property. This should + * have the effect of mapping short aliases into true + * XLFDs. + */ + GdkFont *font = gdk_font_load(fontname); + if (font) { + XFontStruct *xfs = GDK_FONT_XFONT(font); + Display *disp = GDK_FONT_XDISPLAY(font); + Atom fontprop = XInternAtom(disp, "FONT", False); + unsigned long ret; + gdk_font_ref(font); + if (XGetFontProperty(xfs, fontprop, &ret)) { + char *name = XGetAtomName(disp, (Atom)ret); + if (name) + gtk_font_selection_dialog_set_font_name + (GTK_FONT_SELECTION_DIALOG(fontsel), name); + } + gdk_font_unref(font); + } + } + gtk_object_set_data + (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), + "user-data", (gpointer)fontsel); + gtk_object_set_data(GTK_OBJECT(fontsel), "user-data", (gpointer)uc); + gtk_signal_connect + (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), + "clicked", GTK_SIGNAL_FUNC(fontsel_ok), (gpointer)dp); + gtk_signal_connect_object + (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), + "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), + (gpointer)fontsel); + gtk_signal_connect_object + (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->cancel_button), + "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), + (gpointer)fontsel); + gtk_widget_show(fontsel); + +#else /* !GTK_CHECK_VERSION(2,0,0) */ + + /* + * Use the unifontsel code provided in gtkfont.c. + */ + unifontsel *fontsel = unifontsel_new("Select a font"); gtk_window_set_modal(fontsel->window, TRUE); @@ -1135,6 +1735,9 @@ static void filefont_clicked(GtkButton *button, gpointer data) (gpointer)fontsel); gtk_widget_show(GTK_WIDGET(fontsel->window)); + +#endif /* !GTK_CHECK_VERSION(2,0,0) */ + } } @@ -1225,8 +1828,13 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, uc->privdata = NULL; uc->privdata_needs_free = FALSE; uc->buttons = NULL; - uc->entry = uc->combo = uc->treeview = NULL; + uc->entry = NULL; +#if !GTK_CHECK_VERSION(2,0,0) + uc->list = uc->menu = uc->optmenu = NULL; +#else + uc->combo = uc->treeview = NULL; uc->listmodel = NULL; +#endif uc->button = uc->text = NULL; uc->label = NULL; uc->nclicks = 0; @@ -1320,8 +1928,22 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, case CTRL_EDITBOX: { GtkRequisition req; + GtkWidget *signalobject; if (ctrl->editbox.has_list) { +#if !GTK_CHECK_VERSION(2,0,0) + /* + * GTK 1 combo box. + */ + w = gtk_combo_new(); + gtk_combo_set_value_in_list(GTK_COMBO(w), FALSE, TRUE); + uc->entry = GTK_COMBO(w)->entry; + uc->list = GTK_COMBO(w)->list; + signalobject = uc->entry; +#else + /* + * GTK 2 combo box. + */ uc->listmodel = gtk_list_store_new(2, G_TYPE_INT, G_TYPE_STRING); w = gtk_combo_box_entry_new_with_model @@ -1329,30 +1951,26 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, /* We cannot support password combo boxes. */ assert(!ctrl->editbox.password); uc->combo = w; - uc->entrysig = - gtk_signal_connect(GTK_OBJECT(uc->combo), "changed", - GTK_SIGNAL_FUNC(editbox_changed), dp); - gtk_signal_connect(GTK_OBJECT(uc->combo), "key_press_event", - GTK_SIGNAL_FUNC(editbox_key), dp); - gtk_signal_connect(GTK_OBJECT(uc->combo), "focus_in_event", - GTK_SIGNAL_FUNC(widget_focus), dp); - gtk_signal_connect(GTK_OBJECT(uc->combo), "focus_out_event", - GTK_SIGNAL_FUNC(editbox_lostfocus), dp); + signalobject = uc->combo; +#endif } else { w = gtk_entry_new(); if (ctrl->editbox.password) gtk_entry_set_visibility(GTK_ENTRY(w), FALSE); uc->entry = w; - uc->entrysig = - gtk_signal_connect(GTK_OBJECT(uc->entry), "changed", - GTK_SIGNAL_FUNC(editbox_changed), dp); - gtk_signal_connect(GTK_OBJECT(uc->entry), "key_press_event", - GTK_SIGNAL_FUNC(editbox_key), dp); - gtk_signal_connect(GTK_OBJECT(uc->entry), "focus_in_event", - GTK_SIGNAL_FUNC(widget_focus), dp); - gtk_signal_connect(GTK_OBJECT(uc->entry), "focus_out_event", - GTK_SIGNAL_FUNC(editbox_lostfocus), dp); + signalobject = w; } + uc->entrysig = + gtk_signal_connect(GTK_OBJECT(signalobject), "changed", + GTK_SIGNAL_FUNC(editbox_changed), dp); + gtk_signal_connect(GTK_OBJECT(signalobject), "key_press_event", + GTK_SIGNAL_FUNC(editbox_key), dp); + gtk_signal_connect(GTK_OBJECT(signalobject), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + gtk_signal_connect(GTK_OBJECT(signalobject), "focus_out_event", + GTK_SIGNAL_FUNC(editbox_lostfocus), dp); + gtk_signal_connect(GTK_OBJECT(signalobject), "focus_out_event", + GTK_SIGNAL_FUNC(editbox_lostfocus), dp); /* * Edit boxes, for some strange reason, have a minimum * width of 150 in GTK 1.2. We don't want this - we'd @@ -1449,6 +2067,136 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, } break; case CTRL_LISTBOX: + +#if !GTK_CHECK_VERSION(2,0,0) + /* + * GTK 1 list box setup. + */ + + if (ctrl->listbox.height == 0) { + uc->optmenu = w = gtk_option_menu_new(); + uc->menu = gtk_menu_new(); + gtk_option_menu_set_menu(GTK_OPTION_MENU(w), uc->menu); + gtk_object_set_data(GTK_OBJECT(uc->menu), "user-data", + (gpointer)uc->optmenu); + gtk_signal_connect(GTK_OBJECT(uc->optmenu), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + } else { + uc->list = gtk_list_new(); + if (ctrl->listbox.multisel == 2) { + gtk_list_set_selection_mode(GTK_LIST(uc->list), + GTK_SELECTION_EXTENDED); + } else if (ctrl->listbox.multisel == 1) { + gtk_list_set_selection_mode(GTK_LIST(uc->list), + GTK_SELECTION_MULTIPLE); + } else { + gtk_list_set_selection_mode(GTK_LIST(uc->list), + GTK_SELECTION_SINGLE); + } + w = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(w), + uc->list); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + uc->adj = gtk_scrolled_window_get_vadjustment + (GTK_SCROLLED_WINDOW(w)); + + gtk_widget_show(uc->list); + gtk_signal_connect(GTK_OBJECT(uc->list), "selection-changed", + GTK_SIGNAL_FUNC(list_selchange), dp); + gtk_signal_connect(GTK_OBJECT(uc->list), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + + /* + * Adjust the height of the scrolled window to the + * minimum given by the height parameter. + * + * This piece of guesswork is a horrid hack based + * on looking inside the GTK 1.2 sources + * (specifically gtkviewport.c, which appears to be + * the widget which provides the border around the + * scrolling area). Anyone lets me know how I can + * do this in a way which isn't at risk from GTK + * upgrades, I'd be grateful. + */ + { + int edge; +#if GTK_CHECK_VERSION(2,0,0) + edge = GTK_WIDGET(uc->list)->style->ythickness; +#else + edge = GTK_WIDGET(uc->list)->style->klass->ythickness; +#endif + gtk_widget_set_usize(w, 10, + 2*edge + (ctrl->listbox.height * + get_listitemheight(w))); + } + + if (ctrl->listbox.draglist) { + /* + * GTK doesn't appear to make it easy to + * implement a proper draggable list; so + * instead I'm just going to have to put an Up + * and a Down button to the right of the actual + * list box. Ah well. + */ + GtkWidget *cols, *button; + static const gint percentages[2] = { 80, 20 }; + + cols = columns_new(4); + columns_set_cols(COLUMNS(cols), 2, percentages); + columns_add(COLUMNS(cols), w, 0, 1); + gtk_widget_show(w); + button = gtk_button_new_with_label("Up"); + columns_add(COLUMNS(cols), button, 1, 1); + gtk_widget_show(button); + gtk_signal_connect(GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC(draglist_up), dp); + gtk_signal_connect(GTK_OBJECT(button), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + button = gtk_button_new_with_label("Down"); + columns_add(COLUMNS(cols), button, 1, 1); + gtk_widget_show(button); + gtk_signal_connect(GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC(draglist_down), dp); + gtk_signal_connect(GTK_OBJECT(button), "focus_in_event", + GTK_SIGNAL_FUNC(widget_focus), dp); + + w = cols; + } + + } + if (ctrl->generic.label) { + GtkWidget *label, *container; + + label = gtk_label_new(ctrl->generic.label); + + container = columns_new(4); + if (ctrl->listbox.percentwidth == 100) { + columns_add(COLUMNS(container), label, 0, 1); + columns_force_left_align(COLUMNS(container), label); + columns_add(COLUMNS(container), w, 0, 1); + } else { + gint percentages[2]; + percentages[1] = ctrl->listbox.percentwidth; + percentages[0] = 100 - ctrl->listbox.percentwidth; + columns_set_cols(COLUMNS(container), 2, percentages); + columns_add(COLUMNS(container), label, 0, 1); + columns_force_left_align(COLUMNS(container), label); + columns_add(COLUMNS(container), w, 1, 1); + } + gtk_widget_show(label); + gtk_widget_show(w); + shortcut_add(scs, label, ctrl->listbox.shortcut, + SHORTCUT_UCTRL, uc); + w = container; + uc->label = label; + } + +#else /* !GTK_CHECK_VERSION(2,0,0) */ + /* + * GTK 2 list box setup. + */ /* * First construct the list data store, with the right * number of columns. @@ -1604,6 +2352,8 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, uc->label = label; } +#endif /* !GTK_CHECK_VERSION(2,0,0) */ + break; case CTRL_TEXT: /* @@ -1822,6 +2572,45 @@ int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) (GTK_OBJECT(sc->uc->buttons[i]), "clicked"); } } + break; + case CTRL_LISTBOX: + +#if !GTK_CHECK_VERSION(2,0,0) + + /* + * If the list is really an option menu, we focus + * and click it. Otherwise we tell it to focus one + * of its children, which appears to do the Right + * Thing. + */ + if (sc->uc->optmenu) { + GdkEventButton bev; + gint returnval; + + gtk_widget_grab_focus(sc->uc->optmenu); + /* Option menus don't work using the "clicked" signal. + * We need to manufacture a button press event :-/ */ + bev.type = GDK_BUTTON_PRESS; + bev.button = 1; + gtk_signal_emit_by_name(GTK_OBJECT(sc->uc->optmenu), + "button_press_event", + &bev, &returnval); + } else { + assert(sc->uc->list != NULL); + + gtk_container_focus(GTK_CONTAINER(sc->uc->list), + GTK_DIR_TAB_FORWARD); + } + +#else + + /* + * FIXME: apparently I forgot to put this back in + * for GTK 2. Oops. + */ + +#endif + break; } break; @@ -1949,6 +2738,13 @@ void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw, int get_listitemheight(GtkWidget *w) { +#if !GTK_CHECK_VERSION(2,0,0) + GtkWidget *listitem = gtk_list_item_new_with_label("foo"); + GtkRequisition req; + gtk_widget_size_request(listitem, &req); + gtk_object_sink(GTK_OBJECT(listitem)); + return req.height; +#else int height; GtkCellRenderer *cr = gtk_cell_renderer_text_new(); gtk_cell_renderer_get_size(cr, w, NULL, NULL, NULL, NULL, &height); @@ -1956,6 +2752,7 @@ int get_listitemheight(GtkWidget *w) gtk_object_sink(GTK_OBJECT(cr)); g_object_unref(G_OBJECT(cr)); return height; +#endif } void set_dialog_action_area(GtkDialog *dlg, GtkWidget *w) @@ -1970,8 +2767,7 @@ void set_dialog_action_area(GtkDialog *dlg, GtkWidget *w) * straight into that hbox, and it ends up just where we want * it. */ - gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->action_area), - w, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(dlg->action_area), w, TRUE, TRUE, 0); #else /* @@ -2783,9 +3579,16 @@ gint eventlog_selection_clear(GtkWidget *widget, GdkEventSelection *seldata, * Deselect everything in the list box. */ uc = dlg_find_byctrl(&es->dp, es->listctrl); + es->ignore_selchange = 1; +#if !GTK_CHECK_VERSION(2,0,0) + assert(uc->list); + gtk_list_unselect_all(GTK_LIST(uc->list)); +#else assert(uc->treeview); gtk_tree_selection_unselect_all (gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview))); +#endif + es->ignore_selchange = 0; sfree(es->seldata); es->sellen = 0; diff --git a/unix/gtkfont.c b/unix/gtkfont.c index 9b95cb3b..647f7aae 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -670,8 +670,10 @@ static char *x11font_scale_fontname(GtkWidget *widget, const char *name, return NULL; /* shan't */ } +#if GTK_CHECK_VERSION(2,0,0) + /* ---------------------------------------------------------------------- - * Pango font implementation. + * Pango font implementation (for GTK 2 only). */ static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, @@ -1143,6 +1145,8 @@ static char *pangofont_scale_fontname(GtkWidget *widget, const char *name, return retname; } +#endif /* GTK_CHECK_VERSION(2,0,0) */ + /* ---------------------------------------------------------------------- * Outermost functions which do the vtable dispatch. */ @@ -1155,7 +1159,9 @@ static char *pangofont_scale_fontname(GtkWidget *widget, const char *name, * of an explicit type-disambiguating prefix.) */ static const struct unifont_vtable *unifont_types[] = { +#if GTK_CHECK_VERSION(2,0,0) &pangofont_vtable, +#endif &x11font_vtable, }; @@ -1234,8 +1240,11 @@ void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, wide, bold, cellwidth); } +#if GTK_CHECK_VERSION(2,0,0) + /* ---------------------------------------------------------------------- - * Implementation of a unified font selector. + * Implementation of a unified font selector. Used on GTK 2 only; + * for GTK 1 we still use the standard font selector. */ typedef struct fontinfo fontinfo; @@ -2417,3 +2426,5 @@ char *unifontsel_get_name(unifontsel *fontsel) return dupstr(fs->selected->realname); } + +#endif /* GTK_CHECK_VERSION(2,0,0) */ From bfa9859f2a9cb5697299f33b9148960b59e4e3a4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 4 Apr 2008 11:02:26 +0000 Subject: [PATCH 44/53] I apparently missed out a piece of code when doing the new GTK2 list box: shortcut activations for list boxes are missing. That's the last thing on the to-do list. We're now ready to merge back to the trunk, given only some final testing! [originally from svn r7967] --- unix/GTK2.TODO | 11 ----------- unix/gtkdlg.c | 10 ++++++---- 2 files changed, 6 insertions(+), 15 deletions(-) delete mode 100644 unix/GTK2.TODO diff --git a/unix/GTK2.TODO b/unix/GTK2.TODO deleted file mode 100644 index 3ab394cf..00000000 --- a/unix/GTK2.TODO +++ /dev/null @@ -1,11 +0,0 @@ -TODO for PuTTY GTK2 port before merging back into main trunk code ------------------------------------------------------------------ - -Things left to do: - - - I apparently missed out a piece of code when doing the new GTK2 - list box: shortcut activations for list boxes are missing. - - - Merge to trunk. Colin is already mentioned in the licence in all - the branched copies, but mention him in the _website's_ licence - page too. diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index 08bdf239..9f7df3b4 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -2604,10 +2604,12 @@ int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) #else - /* - * FIXME: apparently I forgot to put this back in - * for GTK 2. Oops. - */ + if (sc->uc->treeview) { + gtk_widget_grab_focus(sc->uc->treeview); + } else if (sc->uc->combo) { + gtk_widget_grab_focus(sc->uc->combo); + gtk_combo_box_popup(GTK_COMBO_BOX(sc->uc->combo)); + } #endif From ceb2a9b862020f95b2b4f9efd2ef0a6fb0c96d28 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 4 Apr 2008 11:37:06 +0000 Subject: [PATCH 45/53] Fix the jarring change of window size on expanding the SSH branch of the configuration tree. [originally from svn r7968] --- unix/gtkdlg.c | 55 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index 9f7df3b4..12f8c5b9 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -2412,6 +2412,9 @@ struct selparam { GtkWidget *panel; #if !GTK_CHECK_VERSION(2,0,0) GtkWidget *treeitem; +#else + int depth; + GtkTreePath *treepath; #endif struct Shortcuts shortcuts; }; @@ -2991,16 +2994,19 @@ int do_config_box(const char *title, Config *cfg, int midsession, treeiterlevels[j] = treeiter; if (j > 0) { - GtkTreePath *path; - - path = gtk_tree_model_get_path(GTK_TREE_MODEL(treestore), - &treeiterlevels[j-1]); - if (j < 2) - gtk_tree_view_expand_row(GTK_TREE_VIEW(tree), path, - FALSE); - else - gtk_tree_view_collapse_row(GTK_TREE_VIEW(tree), path); - gtk_tree_path_free(path); + selparams[nselparams].treepath = + gtk_tree_model_get_path(GTK_TREE_MODEL(treestore), + &treeiterlevels[j-1]); + /* + * We are going to collapse all tree branches + * at depth greater than 2, but not _yet_; see + * the comment at the call to + * gtk_tree_view_collapse_row below. + */ + gtk_tree_view_expand_row(GTK_TREE_VIEW(tree), + selparams[nselparams].treepath, + FALSE); + selparams[nselparams].depth = j; } #else treeitem = gtk_tree_item_new_with_label(c); @@ -3044,6 +3050,35 @@ int do_config_box(const char *title, Config *cfg, int midsession, } } +#if GTK_CHECK_VERSION(2,0,0) + { + GtkRequisition req; + int i; + + /* + * We want our tree view to come up with all branches at + * depth 2 or more collapsed. However, if we start off + * with those branches collapsed, then the tree view's + * size request will be calculated based on the width of + * the collapsed tree. So instead we start with them all + * expanded; then we ask for the current size request, + * collapse the relevant rows, and force the width to the + * value we just computed. This arranges that the tree + * view is wide enough to have all branches expanded + * safely. + */ + + gtk_widget_size_request(tree, &req); + + for (i = 0; i < nselparams; i++) + if (selparams[i].depth >= 2) + gtk_tree_view_collapse_row(GTK_TREE_VIEW(tree), + selparams[i].treepath); + + gtk_widget_set_size_request(tree, req.width, -1); + } +#endif + #if GTK_CHECK_VERSION(2,0,0) g_signal_connect(G_OBJECT(treeselection), "changed", G_CALLBACK(treeselection_changed), selparams); From db281abd976170b021131dd13cc7a7435d2716fa Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 4 Apr 2008 12:23:29 +0000 Subject: [PATCH 46/53] gtk_combo_box_get_active_text didn't appear until GTK 2.6; bodge around it for earlier versions of GTK 2. [originally from svn r7969] --- unix/gtkdlg.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index 12f8c5b9..42b4d950 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -368,7 +368,12 @@ void dlg_editbox_get(union control *ctrl, void *dlg, char *buffer, int length) } else { assert(uc->combo != NULL); strncpy(buffer, +#if GTK_CHECK_VERSION(2,6,0) gtk_combo_box_get_active_text(GTK_COMBO_BOX(uc->combo)), +#else + gtk_entry_get_text + (GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uc->combo)))), +#endif length); buffer[length-1] = '\0'; } From 2503cd18613d6d05e6fbc24b79d17bd9dbcb8789 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 5 Apr 2008 12:53:32 +0000 Subject: [PATCH 47/53] Ensure the new `depth' and `treepath' structure fields in struct selparam are always properly initialised. [originally from svn r7972] --- unix/gtkdlg.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index 42b4d950..fbd9a4d9 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -2998,6 +2998,7 @@ int do_config_box(const char *title, Config *cfg, int midsession, -1); treeiterlevels[j] = treeiter; + selparams[nselparams].depth = j; if (j > 0) { selparams[nselparams].treepath = gtk_tree_model_get_path(GTK_TREE_MODEL(treestore), @@ -3011,7 +3012,8 @@ int do_config_box(const char *title, Config *cfg, int midsession, gtk_tree_view_expand_row(GTK_TREE_VIEW(tree), selparams[nselparams].treepath, FALSE); - selparams[nselparams].depth = j; + } else { + selparams[nselparams].treepath = NULL; } #else treeitem = gtk_tree_item_new_with_label(c); From 8ac9896853a2e35f844d1541e1f14b0dd76ffbfe Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 5 Apr 2008 13:37:20 +0000 Subject: [PATCH 48/53] In the new unified font handling, my strategy so far for combining client- and server-side fonts into a single namespace was mainly to hope there would naturally be no collisions, and to provide disambiguating "client:" and "server:" prefixes for manual use in emergencies. Jacob points out, however, that his system not only has a namespace clash but worse still the clash is at the name "fixed", which is our default font! So, modify my namespace policy to use the disambiguating prefixes everywhere by default, and use _unprefixed_ names only if the user types one in by hand. In particular, I've changed the keys used to store font names in Unix saved session files. Font names read from the new keys will be passed straight to the new unifont framework; font names read from the old keys will have "server:" prepended. So any existing configuration file for GTK1 PuTTY should now work reliably in GTK2 PuTTY and select the same font, even if that font is one on which your system (rather, your client+server combination) has a font namespace clash. [originally from svn r7973] --- unix/gtkfont.c | 131 ++++++++++++++++++++++++++++++++++++------------- unix/gtkwin.c | 2 +- unix/uxstore.c | 39 ++++++++++++++- 3 files changed, 135 insertions(+), 37 deletions(-) diff --git a/unix/gtkfont.c b/unix/gtkfont.c index 647f7aae..a909364c 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -26,6 +26,10 @@ /* * Future work: * + * - it would be nice to have a display of the current font name, + * and in particular whether it's client- or server-side, + * during the progress of the font selector. + * * - all the GDK font functions used in the x11font subclass are * deprecated, so one day they may go away. When this happens - * or before, if I'm feeling proactive - it oughtn't to be too @@ -59,6 +63,8 @@ #define FONTFLAG_SERVERALIAS 0x0004 #define FONTFLAG_NONMONOSPACED 0x0008 +#define FONTFLAG_SORT_MASK 0x0007 /* used to disambiguate font families */ + typedef void (*fontsel_add_entry)(void *ctx, const char *realfontname, const char *family, const char *charset, const char *style, const char *stylekey, @@ -78,7 +84,7 @@ struct unifont_vtable { void (*enum_fonts)(GtkWidget *widget, fontsel_add_entry callback, void *callback_ctx); char *(*canonify_fontname)(GtkWidget *widget, const char *name, int *size, - int resolve_aliases); + int *flags, int resolve_aliases); char *(*scale_fontname)(GtkWidget *widget, const char *name, int size); /* @@ -101,7 +107,8 @@ static void x11font_destroy(unifont *font); static void x11font_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, void *callback_ctx); static char *x11font_canonify_fontname(GtkWidget *widget, const char *name, - int *size, int resolve_aliases); + int *size, int *flags, + int resolve_aliases); static char *x11font_scale_fontname(GtkWidget *widget, const char *name, int size); @@ -144,7 +151,7 @@ static const struct unifont_vtable x11font_vtable = { x11font_enum_fonts, x11font_canonify_fontname, x11font_scale_fontname, - "server" + "server", }; char *x11_guess_derived_font_name(GdkFont *font, int bold, int wide) @@ -617,7 +624,8 @@ static void x11font_enum_fonts(GtkWidget *widget, } static char *x11font_canonify_fontname(GtkWidget *widget, const char *name, - int *size, int resolve_aliases) + int *size, int *flags, + int resolve_aliases) { /* * When given an X11 font name to try to make sense of for a @@ -654,6 +662,12 @@ static char *x11font_canonify_fontname(GtkWidget *widget, const char *name, if (XGetFontProperty(xfs, fontprop2, &fsize) && fsize > 0) { *size = fsize; gdk_font_unref(font); + if (flags) { + if (name[0] == '-' || resolve_aliases) + *flags = FONTFLAG_SERVERSIDE; + else + *flags = FONTFLAG_SERVERALIAS; + } return dupstr(name[0] == '-' || resolve_aliases ? newname : name); } @@ -686,7 +700,8 @@ static void pangofont_destroy(unifont *font); static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, void *callback_ctx); static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name, - int *size, int resolve_aliases); + int *size, int *flags, + int resolve_aliases); static char *pangofont_scale_fontname(GtkWidget *widget, const char *name, int size); @@ -714,7 +729,7 @@ static const struct unifont_vtable pangofont_vtable = { pangofont_enum_fonts, pangofont_canonify_fontname, pangofont_scale_fontname, - "client" + "client", }; /* @@ -1062,7 +1077,8 @@ static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, } static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name, - int *size, int resolve_aliases) + int *size, int *flags, + int resolve_aliases) { /* * When given a Pango font name to try to make sense of for a @@ -1292,19 +1308,10 @@ struct fontinfo { const struct unifont_vtable *fontclass; }; -static int fontinfo_realname_compare(void *av, void *bv) -{ - fontinfo *a = (fontinfo *)av; - fontinfo *b = (fontinfo *)bv; - return g_strcasecmp(a->realname, b->realname); -} - -static int fontinfo_realname_find(void *av, void *bv) -{ - const char *a = (const char *)av; - fontinfo *b = (fontinfo *)bv; - return g_strcasecmp(a, b->realname); -} +struct fontinfo_realname_find { + const char *realname; + int flags; +}; static int strnullcasecmp(const char *a, const char *b) { @@ -1329,6 +1336,34 @@ static int strnullcasecmp(const char *a, const char *b) return g_strcasecmp(a, b); } +static int fontinfo_realname_compare(void *av, void *bv) +{ + fontinfo *a = (fontinfo *)av; + fontinfo *b = (fontinfo *)bv; + int i; + + if ((i = strnullcasecmp(a->realname, b->realname)) != 0) + return i; + if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK)) + return ((a->flags & FONTFLAG_SORT_MASK) < + (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1); + return 0; +} + +static int fontinfo_realname_find(void *av, void *bv) +{ + struct fontinfo_realname_find *a = (struct fontinfo_realname_find *)av; + fontinfo *b = (fontinfo *)bv; + int i; + + if ((i = strnullcasecmp(a->realname, b->realname)) != 0) + return i; + if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK)) + return ((a->flags & FONTFLAG_SORT_MASK) < + (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1); + return 0; +} + static int fontinfo_selorder_compare(void *av, void *bv) { fontinfo *a = (fontinfo *)av; @@ -1336,6 +1371,13 @@ static int fontinfo_selorder_compare(void *av, void *bv) int i; if ((i = strnullcasecmp(a->family, b->family)) != 0) return i; + /* + * Font class comes immediately after family, so that fonts + * from different classes with the same family + */ + if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK)) + return ((a->flags & FONTFLAG_SORT_MASK) < + (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1); if ((i = strnullcasecmp(a->charset, b->charset)) != 0) return i; if ((i = strnullcasecmp(a->stylekey, b->stylekey)) != 0) @@ -1354,6 +1396,7 @@ static void unifontsel_setup_familylist(unifontsel_internal *fs) GtkTreeIter iter; int i, listindex, minpos = -1, maxpos = -1; char *currfamily = NULL; + int currflags = -1; fontinfo *info; gtk_list_store_clear(fs->family_model); @@ -1376,7 +1419,8 @@ static void unifontsel_setup_familylist(unifontsel_internal *fs) info->familyindex = -1; continue; /* we're filtering out this font */ } - if (!info || strnullcasecmp(currfamily, info->family)) { + if (!info || strnullcasecmp(currfamily, info->family) || + currflags != (info->flags & FONTFLAG_SORT_MASK)) { /* * We've either finished a family, or started a new * one, or both. @@ -1390,6 +1434,7 @@ static void unifontsel_setup_familylist(unifontsel_internal *fs) if (info) { minpos = i; currfamily = info->family; + currflags = info->flags & FONTFLAG_SORT_MASK; } } if (!info) @@ -1999,10 +2044,16 @@ static void alias_resolve(GtkTreeView *treeview, GtkTreePath *path, gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter, 1,&minval, -1); info = (fontinfo *)index234(fs->fonts_by_selorder, minval); if (info) { + int flags; + struct fontinfo_realname_find f; + newname = info->fontclass->canonify_fontname - (GTK_WIDGET(fs->u.window), info->realname, &newsize, TRUE); - newinfo = find234(fs->fonts_by_realname, (char *)newname, - fontinfo_realname_find); + (GTK_WIDGET(fs->u.window), info->realname, &newsize, &flags, TRUE); + + f.realname = newname; + f.flags = flags; + newinfo = find234(fs->fonts_by_realname, &f, fontinfo_realname_find); + sfree(newname); if (!newinfo) return; /* font name not in our index */ @@ -2359,7 +2410,7 @@ void unifontsel_destroy(unifontsel *fontsel) void unifontsel_set_name(unifontsel *fontsel, const char *fontname) { unifontsel_internal *fs = (unifontsel_internal *)fontsel; - int i, start, end, size; + int i, start, end, size, flags; const char *fontname2 = NULL; fontinfo *info; @@ -2367,7 +2418,7 @@ void unifontsel_set_name(unifontsel *fontsel, const char *fontname) * Provide a default if given an empty or null font name. */ if (!fontname || !*fontname) - fontname = "fixed"; /* Pango zealots might prefer "Monospace 12" */ + fontname = "server:fixed"; /* * Call the canonify_fontname function. @@ -2375,7 +2426,7 @@ void unifontsel_set_name(unifontsel *fontsel, const char *fontname) fontname = unifont_do_prefix(fontname, &start, &end); for (i = start; i < end; i++) { fontname2 = unifont_types[i]->canonify_fontname - (GTK_WIDGET(fs->u.window), fontname, &size, FALSE); + (GTK_WIDGET(fs->u.window), fontname, &size, &flags, FALSE); if (fontname2) break; } @@ -2385,8 +2436,12 @@ void unifontsel_set_name(unifontsel *fontsel, const char *fontname) /* * Now look up the canonified font name in our index. */ - info = find234(fs->fonts_by_realname, (char *)fontname2, - fontinfo_realname_find); + { + struct fontinfo_realname_find f; + f.realname = fontname2; + f.flags = flags; + info = find234(fs->fonts_by_realname, &f, fontinfo_realname_find); + } /* * If we've found the font, and its size field is either @@ -2395,8 +2450,11 @@ void unifontsel_set_name(unifontsel *fontsel, const char *fontname) * font name instead. */ if (!info || (info->size != size && info->size != 0)) { - info = find234(fs->fonts_by_realname, (char *)fontname, - fontinfo_realname_find); + struct fontinfo_realname_find f; + f.realname = fontname; + f.flags = flags; + + info = find234(fs->fonts_by_realname, &f, fontinfo_realname_find); if (!info || info->size != size) return; /* font name not in our index */ } @@ -2420,11 +2478,16 @@ char *unifontsel_get_name(unifontsel *fontsel) if (fs->selected->size == 0) { name = fs->selected->fontclass->scale_fontname (GTK_WIDGET(fs->u.window), fs->selected->realname, fs->selsize); - if (name) - return name; + if (name) { + char *ret = dupcat(fs->selected->fontclass->prefix, ":", + name, NULL); + sfree(name); + return ret; + } } - return dupstr(fs->selected->realname); + return dupcat(fs->selected->fontclass->prefix, ":", + fs->selected->realname, NULL); } #endif /* GTK_CHECK_VERSION(2,0,0) */ diff --git a/unix/gtkwin.c b/unix/gtkwin.c index 51a867e1..dc8d6607 100644 --- a/unix/gtkwin.c +++ b/unix/gtkwin.c @@ -134,7 +134,7 @@ FontSpec platform_default_fontspec(const char *name) { FontSpec ret; if (!strcmp(name, "Font")) - strcpy(ret.name, "fixed"); + strcpy(ret.name, "server:fixed"); else *ret.name = '\0'; return ret; diff --git a/unix/uxstore.c b/unix/uxstore.c index 80e21617..33b6d18d 100644 --- a/unix/uxstore.c +++ b/unix/uxstore.c @@ -362,7 +362,35 @@ int read_setting_i(void *handle, const char *key, int defvalue) int read_setting_fontspec(void *handle, const char *name, FontSpec *result) { - return !!read_setting_s(handle, name, result->name, sizeof(result->name)); + /* + * In GTK1-only PuTTY, we used to store font names simply as a + * valid X font description string (logical or alias), under a + * bare key such as "Font". + * + * In GTK2 PuTTY, we have a prefix system where "client:" + * indicates a Pango font and "server:" an X one; existing + * configuration needs to be reinterpreted as having the + * "server:" prefix, so we change the storage key from the + * provided name string (e.g. "Font") to a suffixed one + * ("FontName"). + */ + char *suffname = dupcat(name, "Name", NULL); + if (read_setting_s(handle, suffname, result->name, sizeof(result->name))) { + sfree(suffname); + return TRUE; /* got new-style name */ + } + sfree(suffname); + + /* Fall back to old-style name. */ + memcpy(result->name, "server:", 7); + if (!read_setting_s(handle, name, + result->name + 7, sizeof(result->name) - 7) || + !result->name[7]) { + result->name[0] = '\0'; + return FALSE; + } else { + return TRUE; + } } int read_setting_filename(void *handle, const char *name, Filename *result) { @@ -371,7 +399,14 @@ int read_setting_filename(void *handle, const char *name, Filename *result) void write_setting_fontspec(void *handle, const char *name, FontSpec result) { - write_setting_s(handle, name, result.name); + /* + * read_setting_fontspec had to handle two cases, but when + * writing our settings back out we simply always generate the + * new-style name. + */ + char *suffname = dupcat(name, "Name", NULL); + write_setting_s(handle, suffname, result.name); + sfree(suffname); } void write_setting_filename(void *handle, const char *name, Filename result) { From 6af8462765b88d4e3ef55d3843109c10c579af00 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 13 Apr 2008 07:48:10 +0000 Subject: [PATCH 49/53] Just noticed that selecting "client:Bitstream Vera Sans Mono 10" in the font config box and then invoking the unifontsel causes the box to come up empty rather than populated with that font. Turns out that I completely forgot to have pangofont_canonify_fontname() return the flags word, ahem. [originally from svn r7988] --- unix/gtkfont.c | 1 + 1 file changed, 1 insertion(+) diff --git a/unix/gtkfont.c b/unix/gtkfont.c index a909364c..d73e920a 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -1131,6 +1131,7 @@ static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name, } *size = PANGO_PIXELS(pango_font_description_get_size(desc)); + *flags = FONTFLAG_CLIENTSIDE; pango_font_description_set_size(desc, PANGO_DUMMY_SIZE * PANGO_SCALE); newname = pango_font_description_to_string(desc); retname = dupstr(newname); From 92caf21c2c6181a6deada35044159ab814d623ea Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 14 Apr 2008 17:57:45 +0000 Subject: [PATCH 50/53] Prevent assertion failure in the case where the user manipulates the filter checkboxes to filter the currently selected font out of the family list and then does something in one of the other list boxes or the size edit box. [originally from svn r7990] --- unix/gtkfont.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/unix/gtkfont.c b/unix/gtkfont.c index d73e920a..02eadb49 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -1392,6 +1392,15 @@ static int fontinfo_selorder_compare(void *av, void *bv) return 0; } +static void unifontsel_deselect(unifontsel_internal *fs) +{ + fs->selected = NULL; + gtk_list_store_clear(fs->style_model); + gtk_list_store_clear(fs->size_model); + gtk_widget_set_sensitive(fs->u.ok_button, FALSE); + gtk_widget_set_sensitive(fs->size_entry, FALSE); +} + static void unifontsel_setup_familylist(unifontsel_internal *fs) { GtkTreeIter iter; @@ -1443,6 +1452,13 @@ static void unifontsel_setup_familylist(unifontsel_internal *fs) info->familyindex = listindex; maxpos = i; } + + /* + * If we've just filtered out the previously selected font, + * deselect it thoroughly. + */ + if (fs->selected && fs->selected->familyindex < 0) + unifontsel_deselect(fs); } static void unifontsel_setup_stylelist(unifontsel_internal *fs, From cb18f9a6ebd956748f45ce06cd5e8e5f7fc76d30 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 14 Apr 2008 18:00:57 +0000 Subject: [PATCH 51/53] Oops; prevent further segfault during setup, which apparently only show up when building without debugging... [originally from svn r7991] --- unix/gtkfont.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unix/gtkfont.c b/unix/gtkfont.c index 02eadb49..0ad25c2b 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -2139,6 +2139,7 @@ unifontsel *unifontsel_new(const char *wintitle) int i; fs->inhibit_response = FALSE; + fs->selected = NULL; { /* @@ -2400,7 +2401,6 @@ unifontsel *unifontsel_new(const char *wintitle) */ unifontsel_setup_familylist(fs); - fs->selected = NULL; fs->selsize = fs->intendedsize = 13; /* random default */ gtk_widget_set_sensitive(fs->u.ok_button, FALSE); From 79f7249185f0bb6eddc30a94b4b21072b3ac460e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 31 May 2008 13:29:32 +0000 Subject: [PATCH 52/53] On some systems, strncpy is a macro, and putting preprocessor directives in the middle of a macro invocation appears to be frowned on. Irritating, but there we go. [originally from svn r8026] --- unix/gtkdlg.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index fbd9a4d9..aa974546 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -367,14 +367,16 @@ void dlg_editbox_get(union control *ctrl, void *dlg, char *buffer, int length) #if GTK_CHECK_VERSION(2,0,0) } else { assert(uc->combo != NULL); - strncpy(buffer, #if GTK_CHECK_VERSION(2,6,0) + strncpy(buffer, gtk_combo_box_get_active_text(GTK_COMBO_BOX(uc->combo)), + length); #else + strncpy(buffer, gtk_entry_get_text (GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uc->combo)))), -#endif length); +#endif buffer[length-1] = '\0'; } #endif From b3c1438a31eff853e5ff0209417890975264ae76 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 31 May 2008 19:23:45 +0000 Subject: [PATCH 53/53] Re-jig the combo box handling ifdefs so that we can compile with GTK versions >= 2.0 (when the new list boxes came in) but < 2.4 (when the new combo boxes came in). Since some combo boxes are handled using the old list-box code, this means that the two lots of code can both be compiled in at once in some situations! [originally from svn r8031] --- unix/gtkdlg.c | 529 ++++++++++++++++++++++++------------------------- unix/gtkfont.c | 10 + 2 files changed, 270 insertions(+), 269 deletions(-) diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index aa974546..53a70259 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -42,13 +42,15 @@ struct uctrl { GtkWidget **buttons; int nbuttons; /* for radio buttons */ GtkWidget *entry; /* for editbox, filesel, fontsel */ GtkWidget *button; /* for filesel, fontsel */ -#if !GTK_CHECK_VERSION(2,0,0) - GtkWidget *list; /* for combobox, listbox */ +#if !GTK_CHECK_VERSION(2,4,0) + GtkWidget *list; /* for listbox (in GTK1), combobox (<=GTK2.3) */ GtkWidget *menu; /* for optionmenu (==droplist) */ GtkWidget *optmenu; /* also for optionmenu */ #else GtkWidget *combo; /* for combo box (either editable or not) */ - GtkWidget *treeview; /* for list box (list, droplist, combo box) */ +#endif +#if GTK_CHECK_VERSION(2,0,0) + GtkWidget *treeview; /* for listbox (GTK2), droplist+combo (>=2.4) */ GtkListStore *listmodel; /* for all types of list box */ #endif GtkWidget *text; /* for text */ @@ -112,6 +114,8 @@ static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event, gpointer data); static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event, gpointer data); +#endif +#if !GTK_CHECK_VERSION(2,4,0) static void menuitem_activate(GtkMenuItem *item, gpointer data); #endif static void coloursel_ok(GtkButton *button, gpointer data); @@ -308,25 +312,14 @@ void dlg_editbox_set(union control *ctrl, void *dlg, char const *text) char *tmpstring; assert(uc->ctrl->generic.type == CTRL_EDITBOX); -#if !GTK_CHECK_VERSION(2,0,0) - /* - * In GTK 1, `entry' is valid for combo boxes and edit boxes - * alike. - */ - assert(uc->entry != NULL); - entry = uc->entry; -#else - /* - * In GTK 2, combo boxes use a completely different widget. - */ - if (!uc->ctrl->editbox.has_list) { - assert(uc->entry != NULL); - entry = uc->entry; - } else { - assert(uc->combo != NULL); +#if GTK_CHECK_VERSION(2,4,0) + if (uc->combo) entry = gtk_bin_get_child(GTK_BIN(uc->combo)); - } + else #endif + entry = uc->entry; + + assert(entry != NULL); /* * GTK 2 implements gtk_entry_set_text by means of two separate @@ -357,16 +350,8 @@ void dlg_editbox_get(union control *ctrl, void *dlg, char *buffer, int length) struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_EDITBOX); -#if GTK_CHECK_VERSION(2,0,0) - if (!uc->ctrl->editbox.has_list) { -#endif - assert(uc->entry != NULL); - strncpy(buffer, gtk_entry_get_text(GTK_ENTRY(uc->entry)), - length); - buffer[length-1] = '\0'; -#if GTK_CHECK_VERSION(2,0,0) - } else { - assert(uc->combo != NULL); +#if GTK_CHECK_VERSION(2,4,0) + if (uc->combo) { #if GTK_CHECK_VERSION(2,6,0) strncpy(buffer, gtk_combo_box_get_active_text(GTK_COMBO_BOX(uc->combo)), @@ -378,11 +363,21 @@ void dlg_editbox_get(union control *ctrl, void *dlg, char *buffer, int length) length); #endif buffer[length-1] = '\0'; + return; } #endif + + if (uc->entry) { + strncpy(buffer, gtk_entry_get_text(GTK_ENTRY(uc->entry)), + length); + buffer[length-1] = '\0'; + return; + } + + assert(!"We shouldn't get here"); } -#if !GTK_CHECK_VERSION(2,0,0) +#if !GTK_CHECK_VERSION(2,4,0) static void container_remove_and_destroy(GtkWidget *w, gpointer data) { GtkContainer *cont = GTK_CONTAINER(data); @@ -400,19 +395,25 @@ void dlg_listbox_clear(union control *ctrl, void *dlg) assert(uc->ctrl->generic.type == CTRL_EDITBOX || uc->ctrl->generic.type == CTRL_LISTBOX); -#if !GTK_CHECK_VERSION(2,0,0) - assert(uc->menu != NULL || uc->list != NULL); +#if !GTK_CHECK_VERSION(2,4,0) if (uc->menu) { gtk_container_foreach(GTK_CONTAINER(uc->menu), container_remove_and_destroy, GTK_CONTAINER(uc->menu)); - } else { - gtk_list_clear_items(GTK_LIST(uc->list), 0, -1); + return; + } + if (uc->list) { + gtk_list_clear_items(GTK_LIST(uc->list), 0, -1); + return; } -#else - assert(uc->listmodel != NULL); - gtk_list_store_clear(uc->listmodel); #endif +#if GTK_CHECK_VERSION(2,0,0) + if (uc->listmodel) { + gtk_list_store_clear(uc->listmodel); + return; + } +#endif + assert(!"We shouldn't get here"); } void dlg_listbox_del(union control *ctrl, void *dlg, int index) @@ -423,17 +424,20 @@ void dlg_listbox_del(union control *ctrl, void *dlg, int index) assert(uc->ctrl->generic.type == CTRL_EDITBOX || uc->ctrl->generic.type == CTRL_LISTBOX); -#if !GTK_CHECK_VERSION(2,0,0) - assert(uc->menu != NULL || uc->list != NULL); +#if !GTK_CHECK_VERSION(2,4,0) if (uc->menu) { gtk_container_remove (GTK_CONTAINER(uc->menu), g_list_nth_data(GTK_MENU_SHELL(uc->menu)->children, index)); - } else { - gtk_list_clear_items(GTK_LIST(uc->list), index, index+1); + return; } -#else - { + if (uc->list) { + gtk_list_clear_items(GTK_LIST(uc->list), index, index+1); + return; + } +#endif +#if GTK_CHECK_VERSION(2,0,0) + if (uc->listmodel) { GtkTreePath *path; GtkTreeIter iter; assert(uc->listmodel != NULL); @@ -441,8 +445,10 @@ void dlg_listbox_del(union control *ctrl, void *dlg, int index) gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path); gtk_list_store_remove(uc->listmodel, &iter); gtk_tree_path_free(path); + return; } #endif + assert(!"We shouldn't get here"); } void dlg_listbox_add(union control *ctrl, void *dlg, char const *text) @@ -470,12 +476,9 @@ void dlg_listbox_addwithid(union control *ctrl, void *dlg, * This routine is long and complicated in both GTK 1 and 2, * and completely different. Sigh. */ -#if !GTK_CHECK_VERSION(2,0,0) - - assert(uc->menu != NULL || uc->list != NULL); - dp->flags |= FLAG_UPDATING_COMBO_LIST; +#if !GTK_CHECK_VERSION(2,4,0) if (uc->menu) { /* * List item in a drop-down (but non-combo) list. Tabs are @@ -491,7 +494,26 @@ void dlg_listbox_addwithid(union control *ctrl, void *dlg, GINT_TO_POINTER(id)); gtk_signal_connect(GTK_OBJECT(menuitem), "activate", GTK_SIGNAL_FUNC(menuitem_activate), dp); - } else if (!uc->entry) { + goto done; + } + if (uc->list && uc->entry) { + /* + * List item in a combo-box list, which means the sensible + * thing to do is make it a perfectly normal label. Hence + * tabs are disregarded. + */ + GtkWidget *listitem = gtk_list_item_new_with_label(text); + + gtk_container_add(GTK_CONTAINER(uc->list), listitem); + gtk_widget_show(listitem); + + gtk_object_set_data(GTK_OBJECT(listitem), "user-data", + GINT_TO_POINTER(id)); + goto done; + } +#endif +#if !GTK_CHECK_VERSION(2,0,0) + if (uc->list) { /* * List item in a non-combo-box list box. We make all of * these Columns containing GtkLabels. This allows us to do @@ -554,31 +576,13 @@ void dlg_listbox_addwithid(union control *ctrl, void *dlg, GTK_SIGNAL_FUNC(listitem_button_release), dp); gtk_object_set_data(GTK_OBJECT(listitem), "user-data", GINT_TO_POINTER(id)); - } else { - /* - * List item in a combo-box list, which means the sensible - * thing to do is make it a perfectly normal label. Hence - * tabs are disregarded. - */ - GtkWidget *listitem = gtk_list_item_new_with_label(text); - - gtk_container_add(GTK_CONTAINER(uc->list), listitem); - gtk_widget_show(listitem); - - gtk_object_set_data(GTK_OBJECT(listitem), "user-data", - GINT_TO_POINTER(id)); + goto done; } - - dp->flags &= ~FLAG_UPDATING_COMBO_LIST; - #else - - { + if (uc->listmodel) { GtkTreeIter iter; int i, cols; - assert(uc->listmodel); - dp->flags |= FLAG_UPDATING_LISTBOX;/* inhibit drag-list update */ gtk_list_store_append(uc->listmodel, &iter); dp->flags &= ~FLAG_UPDATING_LISTBOX; @@ -600,10 +604,12 @@ void dlg_listbox_addwithid(union control *ctrl, void *dlg, text += collen; if (*text) text++; } + goto done; } - #endif - + assert(!"We shouldn't get here"); + done: + dp->flags &= ~FLAG_UPDATING_COMBO_LIST; } int dlg_listbox_getid(union control *ctrl, void *dlg, int index) @@ -614,13 +620,11 @@ int dlg_listbox_getid(union control *ctrl, void *dlg, int index) assert(uc->ctrl->generic.type == CTRL_EDITBOX || uc->ctrl->generic.type == CTRL_LISTBOX); -#if !GTK_CHECK_VERSION(2,0,0) - { +#if !GTK_CHECK_VERSION(2,4,0) + if (uc->menu || uc->list) { GList *children; GtkObject *item; - assert(uc->menu != NULL || uc->list != NULL); - children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu : uc->list)); item = GTK_OBJECT(g_list_nth_data(children, index)); @@ -629,14 +633,13 @@ int dlg_listbox_getid(union control *ctrl, void *dlg, int index) return GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(item), "user-data")); } -#else - { +#endif +#if GTK_CHECK_VERSION(2,0,0) + if (uc->listmodel) { GtkTreePath *path; GtkTreeIter iter; int ret; - assert(uc->listmodel != NULL); - path = gtk_tree_path_new_from_indices(index, -1); gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path); gtk_tree_model_get(GTK_TREE_MODEL(uc->listmodel), &iter, 0, &ret, -1); @@ -645,6 +648,8 @@ int dlg_listbox_getid(union control *ctrl, void *dlg, int index) return ret; } #endif + assert(!"We shouldn't get here"); + return -1; /* placate dataflow analysis */ } /* dlg_listbox_index returns <0 if no single element is selected. */ @@ -656,16 +661,13 @@ int dlg_listbox_index(union control *ctrl, void *dlg) assert(uc->ctrl->generic.type == CTRL_EDITBOX || uc->ctrl->generic.type == CTRL_LISTBOX); -#if !GTK_CHECK_VERSION(2,0,0) - - { +#if !GTK_CHECK_VERSION(2,4,0) + if (uc->menu || uc->list) { GList *children; GtkWidget *item, *activeitem; int i; int selected = -1; - assert(uc->menu != NULL || uc->list != NULL); - if (uc->menu) activeitem = gtk_menu_get_active(GTK_MENU(uc->menu)); else @@ -686,20 +688,17 @@ int dlg_listbox_index(union control *ctrl, void *dlg) g_list_free(children); return selected < 0 ? -1 : selected; } - #else - - /* - * We have to do this completely differently for a combo box - * (editable or otherwise) and a full-size list box. - */ if (uc->combo) { /* * This API function already does the right thing in the * case of no current selection. */ return gtk_combo_box_get_active(GTK_COMBO_BOX(uc->combo)); - } else { + } +#endif +#if GTK_CHECK_VERSION(2,0,0) + if (uc->treeview) { GtkTreeSelection *treesel; GtkTreePath *path; GList *sellist; @@ -733,8 +732,9 @@ int dlg_listbox_index(union control *ctrl, void *dlg) return ret; } - #endif + assert(!"We shouldn't get here"); + return -1; /* placate dataflow analysis */ } int dlg_listbox_issel(union control *ctrl, void *dlg, int index) @@ -745,9 +745,8 @@ int dlg_listbox_issel(union control *ctrl, void *dlg, int index) assert(uc->ctrl->generic.type == CTRL_EDITBOX || uc->ctrl->generic.type == CTRL_LISTBOX); -#if !GTK_CHECK_VERSION(2,0,0) - - { +#if !GTK_CHECK_VERSION(2,4,0) + if (uc->menu || uc->list) { GList *children; GtkWidget *item, *activeitem; @@ -767,20 +766,17 @@ int dlg_listbox_issel(union control *ctrl, void *dlg, int index) return GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED; } } - #else - - /* - * We have to do this completely differently for a combo box - * (editable or otherwise) and a full-size list box. - */ if (uc->combo) { /* * This API function already does the right thing in the * case of no current selection. */ return gtk_combo_box_get_active(GTK_COMBO_BOX(uc->combo)) == index; - } else { + } +#endif +#if GTK_CHECK_VERSION(2,0,0) + if (uc->treeview) { GtkTreeSelection *treesel; GtkTreePath *path; int ret; @@ -794,8 +790,9 @@ int dlg_listbox_issel(union control *ctrl, void *dlg, int index) return ret; } - #endif + assert(!"We shouldn't get here"); + return -1; /* placate dataflow analysis */ } void dlg_listbox_select(union control *ctrl, void *dlg, int index) @@ -806,13 +803,12 @@ void dlg_listbox_select(union control *ctrl, void *dlg, int index) assert(uc->ctrl->generic.type == CTRL_EDITBOX || uc->ctrl->generic.type == CTRL_LISTBOX); -#if !GTK_CHECK_VERSION(2,0,0) - - assert(uc->optmenu != NULL || uc->list != NULL); - +#if !GTK_CHECK_VERSION(2,4,0) if (uc->optmenu) { gtk_option_menu_set_history(GTK_OPTION_MENU(uc->optmenu), index); - } else { + return; + } + if (uc->list) { int nitems; GList *items; gdouble newtop, newbot; @@ -842,21 +838,19 @@ void dlg_listbox_select(union control *ctrl, void *dlg, int index) if (modified) gtk_adjustment_value_changed(uc->adj); } + return; } - #else - - /* - * We have to do this completely differently for a combo box - * (editable or otherwise) and a full-size list box. - */ if (uc->combo) { gtk_combo_box_set_active(GTK_COMBO_BOX(uc->combo), index); - } else { + return; + } +#endif +#if GTK_CHECK_VERSION(2,0,0) + if (uc->treeview) { GtkTreeSelection *treesel; GtkTreePath *path; - assert(uc->treeview != NULL); treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview)); path = gtk_tree_path_new_from_indices(index, -1); @@ -864,10 +858,10 @@ void dlg_listbox_select(union control *ctrl, void *dlg, int index) gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(uc->treeview), path, NULL, FALSE, 0.0, 0.0); gtk_tree_path_free(path); + return; } - #endif - + assert(!"We shouldn't get here"); } void dlg_text_set(union control *ctrl, void *dlg, char const *text) @@ -1000,7 +994,7 @@ void dlg_set_focus(union control *ctrl, void *dlg) /* Anything containing an edit box gets that focused. */ gtk_widget_grab_focus(uc->entry); } -#if GTK_CHECK_VERSION(2,0,0) +#if GTK_CHECK_VERSION(2,4,0) else if (uc->combo) { /* Failing that, there'll be a combo box. */ gtk_widget_grab_focus(uc->combo); @@ -1022,29 +1016,34 @@ void dlg_set_focus(union control *ctrl, void *dlg) } break; case CTRL_LISTBOX: -#if !GTK_CHECK_VERSION(2,0,0) - /* - * If the list is really an option menu, we focus it. - * Otherwise we tell it to focus one of its children, which - * appears to do the Right Thing. - */ +#if !GTK_CHECK_VERSION(2,4,0) if (uc->optmenu) { gtk_widget_grab_focus(uc->optmenu); - } else { - assert(uc->list != NULL); - gtk_container_focus(GTK_CONTAINER(uc->list), GTK_DIR_TAB_FORWARD); - } + break; + } #else - /* - * There might be a combo box (drop-down list) here, or a - * proper list box. - */ - if (uc->treeview) { - gtk_widget_grab_focus(uc->treeview); - } else if (uc->combo) { + if (uc->combo) { gtk_widget_grab_focus(uc->combo); + break; } #endif +#if !GTK_CHECK_VERSION(2,0,0) + if (uc->list) { + /* + * For GTK-1 style list boxes, we tell it to focus one + * of its children, which appears to do the Right + * Thing. + */ + gtk_container_focus(GTK_CONTAINER(uc->list), GTK_DIR_TAB_FORWARD); + break; + } +#else + if (uc->treeview) { + gtk_widget_grab_focus(uc->treeview); + break; + } +#endif + assert(!"We shouldn't get here"); break; } } @@ -1437,15 +1436,6 @@ static void list_selchange(GtkList *list, gpointer data) uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); } -static void menuitem_activate(GtkMenuItem *item, gpointer data) -{ - struct dlgparam *dp = (struct dlgparam *)data; - GtkWidget *menushell = GTK_WIDGET(item)->parent; - gpointer optmenu = gtk_object_get_data(GTK_OBJECT(menushell), "user-data"); - struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(optmenu)); - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); -} - static void draglist_move(struct dlgparam *dp, struct uctrl *uc, int direction) { int index = dlg_listbox_index(uc->ctrl, dp); @@ -1500,14 +1490,6 @@ static void listbox_doubleclick(GtkTreeView *treeview, GtkTreePath *path, uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION); } -static void droplist_selchange(GtkComboBox *combo, gpointer data) -{ - struct dlgparam *dp = (struct dlgparam *)data; - struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(combo)); - if (uc) - uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); -} - static void listbox_selchange(GtkTreeSelection *treeselection, gpointer data) { @@ -1575,6 +1557,29 @@ static void listbox_reorder(GtkTreeModel *treemodel, GtkTreePath *path, #endif /* !GTK_CHECK_VERSION(2,0,0) */ +#if !GTK_CHECK_VERSION(2,4,0) + +static void menuitem_activate(GtkMenuItem *item, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + GtkWidget *menushell = GTK_WIDGET(item)->parent; + gpointer optmenu = gtk_object_get_data(GTK_OBJECT(menushell), "user-data"); + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(optmenu)); + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); +} + +#else + +static void droplist_selchange(GtkComboBox *combo, gpointer data) +{ + struct dlgparam *dp = (struct dlgparam *)data; + struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(combo)); + if (uc) + uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); +} + +#endif /* !GTK_CHECK_VERSION(2,4,0) */ + static void filesel_ok(GtkButton *button, gpointer data) { /* struct dlgparam *dp = (struct dlgparam *)data; */ @@ -1836,10 +1841,13 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, uc->privdata_needs_free = FALSE; uc->buttons = NULL; uc->entry = NULL; -#if !GTK_CHECK_VERSION(2,0,0) +#if !GTK_CHECK_VERSION(2,4,0) uc->list = uc->menu = uc->optmenu = NULL; #else - uc->combo = uc->treeview = NULL; + uc->combo = NULL; +#endif +#if GTK_CHECK_VERSION(2,0,0) + uc->treeview = NULL; uc->listmodel = NULL; #endif uc->button = uc->text = NULL; @@ -1938,7 +1946,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, GtkWidget *signalobject; if (ctrl->editbox.has_list) { -#if !GTK_CHECK_VERSION(2,0,0) +#if !GTK_CHECK_VERSION(2,4,0) /* * GTK 1 combo box. */ @@ -2075,12 +2083,45 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, break; case CTRL_LISTBOX: -#if !GTK_CHECK_VERSION(2,0,0) +#if GTK_CHECK_VERSION(2,0,0) /* - * GTK 1 list box setup. + * First construct the list data store, with the right + * number of columns. */ +# if !GTK_CHECK_VERSION(2,4,0) + /* (For GTK 2.0 to 2.3, we do this for full listboxes only, + * because combo boxes are still done the old GTK1 way.) */ + if (ctrl->listbox.height > 0) +# endif + { + GType *types; + int i; + int cols; - if (ctrl->listbox.height == 0) { + cols = ctrl->listbox.ncols; + cols = cols ? cols : 1; + types = snewn(1 + cols, GType); + + types[0] = G_TYPE_INT; + for (i = 0; i < cols; i++) + types[i+1] = G_TYPE_STRING; + + uc->listmodel = gtk_list_store_newv(1 + cols, types); + + sfree(types); + } +#endif + + /* + * See if it's a drop-down list (non-editable combo + * box). + */ + if (ctrl->listbox.height == 0) { +#if !GTK_CHECK_VERSION(2,4,0) + /* + * GTK1 and early-GTK2 option-menu style of + * drop-down list. + */ uc->optmenu = w = gtk_option_menu_new(); uc->menu = gtk_menu_new(); gtk_option_menu_set_menu(GTK_OPTION_MENU(w), uc->menu); @@ -2088,7 +2129,41 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, (gpointer)uc->optmenu); gtk_signal_connect(GTK_OBJECT(uc->optmenu), "focus_in_event", GTK_SIGNAL_FUNC(widget_focus), dp); +#else + /* + * Late-GTK2 style using a GtkComboBox. + */ + GtkCellRenderer *cr; + + /* + * Create a non-editable GtkComboBox (that is, not + * its subclass GtkComboBoxEntry). + */ + w = gtk_combo_box_new_with_model + (GTK_TREE_MODEL(uc->listmodel)); + uc->combo = w; + + /* + * Tell it how to render a list item (i.e. which + * column to look at in the list model). + */ + cr = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), cr, TRUE); + gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(w), cr, + "text", 1, NULL); + + /* + * And tell it to notify us when the selection + * changes. + */ + g_signal_connect(G_OBJECT(w), "changed", + G_CALLBACK(droplist_selchange), dp); +#endif } else { +#if !GTK_CHECK_VERSION(2,0,0) + /* + * GTK1-style full list box. + */ uc->list = gtk_list_new(); if (ctrl->listbox.multisel == 2) { gtk_list_set_selection_mode(GTK_LIST(uc->list), @@ -2129,11 +2204,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, */ { int edge; -#if GTK_CHECK_VERSION(2,0,0) - edge = GTK_WIDGET(uc->list)->style->ythickness; -#else edge = GTK_WIDGET(uc->list)->style->klass->ythickness; -#endif gtk_widget_set_usize(w, 10, 2*edge + (ctrl->listbox.height * get_listitemheight(w))); @@ -2171,91 +2242,10 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, w = cols; } - - } - if (ctrl->generic.label) { - GtkWidget *label, *container; - - label = gtk_label_new(ctrl->generic.label); - - container = columns_new(4); - if (ctrl->listbox.percentwidth == 100) { - columns_add(COLUMNS(container), label, 0, 1); - columns_force_left_align(COLUMNS(container), label); - columns_add(COLUMNS(container), w, 0, 1); - } else { - gint percentages[2]; - percentages[1] = ctrl->listbox.percentwidth; - percentages[0] = 100 - ctrl->listbox.percentwidth; - columns_set_cols(COLUMNS(container), 2, percentages); - columns_add(COLUMNS(container), label, 0, 1); - columns_force_left_align(COLUMNS(container), label); - columns_add(COLUMNS(container), w, 1, 1); - } - gtk_widget_show(label); - gtk_widget_show(w); - shortcut_add(scs, label, ctrl->listbox.shortcut, - SHORTCUT_UCTRL, uc); - w = container; - uc->label = label; - } - -#else /* !GTK_CHECK_VERSION(2,0,0) */ - /* - * GTK 2 list box setup. - */ - /* - * First construct the list data store, with the right - * number of columns. - */ - { - GType *types; - int i; - int cols; - - cols = ctrl->listbox.ncols; - cols = cols ? cols : 1; - types = snewn(1 + cols, GType); - - types[0] = G_TYPE_INT; - for (i = 0; i < cols; i++) - types[i+1] = G_TYPE_STRING; - - uc->listmodel = gtk_list_store_newv(1 + cols, types); - - sfree(types); - } - - /* - * Drop-down lists are done completely differently. - */ - if (ctrl->listbox.height == 0) { - GtkCellRenderer *cr; - +#else /* - * Create a non-editable GtkComboBox (that is, not - * its subclass GtkComboBoxEntry). + * GTK2 treeview-based full list box. */ - w = gtk_combo_box_new_with_model - (GTK_TREE_MODEL(uc->listmodel)); - uc->combo = w; - - /* - * Tell it how to render a list item (i.e. which - * column to look at in the list model). - */ - cr = gtk_cell_renderer_text_new(); - gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), cr, TRUE); - gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(w), cr, - "text", 1, NULL); - - /* - * And tell it to notify us when the selection - * changes. - */ - g_signal_connect(G_OBJECT(w), "changed", - G_CALLBACK(droplist_selchange), dp); - } else { GtkTreeSelection *sel; /* @@ -2323,7 +2313,8 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, w = scroll; } - } +#endif + } if (ctrl->generic.label) { GtkWidget *label, *container; @@ -2359,8 +2350,6 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, uc->label = label; } -#endif /* !GTK_CHECK_VERSION(2,0,0) */ - break; case CTRL_TEXT: /* @@ -2585,14 +2574,7 @@ int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) break; case CTRL_LISTBOX: -#if !GTK_CHECK_VERSION(2,0,0) - - /* - * If the list is really an option menu, we focus - * and click it. Otherwise we tell it to focus one - * of its children, which appears to do the Right - * Thing. - */ +#if !GTK_CHECK_VERSION(2,4,0) if (sc->uc->optmenu) { GdkEventButton bev; gint returnval; @@ -2605,24 +2587,33 @@ int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) gtk_signal_emit_by_name(GTK_OBJECT(sc->uc->optmenu), "button_press_event", &bev, &returnval); - } else { - assert(sc->uc->list != NULL); - - gtk_container_focus(GTK_CONTAINER(sc->uc->list), - GTK_DIR_TAB_FORWARD); + break; } - #else - - if (sc->uc->treeview) { - gtk_widget_grab_focus(sc->uc->treeview); - } else if (sc->uc->combo) { + if (sc->uc->combo) { gtk_widget_grab_focus(sc->uc->combo); gtk_combo_box_popup(GTK_COMBO_BOX(sc->uc->combo)); + break; } - #endif - +#if !GTK_CHECK_VERSION(2,0,0) + if (sc->uc->list) { + /* + * For GTK-1 style list boxes, we tell it to + * focus one of its children, which appears to + * do the Right Thing. + */ + gtk_container_focus(GTK_CONTAINER(sc->uc->list), + GTK_DIR_TAB_FORWARD); + break; + } +#else + if (sc->uc->treeview) { + gtk_widget_grab_focus(sc->uc->treeview); + break; + } +#endif + assert(!"We shouldn't get here"); break; } break; diff --git a/unix/gtkfont.c b/unix/gtkfont.c index 0ad25c2b..fb756309 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -690,6 +690,10 @@ static char *x11font_scale_fontname(GtkWidget *widget, const char *name, * Pango font implementation (for GTK 2 only). */ +#if defined PANGO_PRE_1POINT4 && !defined PANGO_PRE_1POINT6 +#define PANGO_PRE_1POINT6 /* make life easier for pre-1.4 folk */ +#endif + static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, int x, int y, const char *string, int len, int wide, int bold, int cellwidth); @@ -2185,11 +2189,15 @@ unifontsel *unifontsel_new(const char *wintitle) table = gtk_table_new(8, 3, FALSE); gtk_widget_show(table); gtk_table_set_col_spacings(GTK_TABLE(table), 8); +#if GTK_CHECK_VERSION(2,4,0) /* GtkAlignment seems to be the simplest way to put padding round things */ w = gtk_alignment_new(0, 0, 1, 1); gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8); gtk_container_add(GTK_CONTAINER(w), table); gtk_widget_show(w); +#else + w = table; +#endif gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fs->u.window)->vbox), w, TRUE, TRUE, 0); @@ -2334,12 +2342,14 @@ unifontsel *unifontsel_new(const char *wintitle) w = gtk_frame_new(NULL); gtk_container_add(GTK_CONTAINER(w), ww); gtk_widget_show(w); +#if GTK_CHECK_VERSION(2,4,0) ww = w; /* GtkAlignment seems to be the simplest way to put padding round things */ w = gtk_alignment_new(0, 0, 1, 1); gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8); gtk_container_add(GTK_CONTAINER(w), ww); gtk_widget_show(w); +#endif ww = w; w = gtk_frame_new("Preview of font"); gtk_container_add(GTK_CONTAINER(w), ww);