From 1276c13e6a0c06041b4d58ae2e2ac7ce5ddd2528 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 3 Apr 2021 17:45:31 +0100 Subject: [PATCH] dialog system: add a side-by-side alignment feature. This will let us put two controls side by side (e.g. in disjoint columns of a multi-col layout) and indicate that instead of the default behaviour of aligning their top edges, their centreline (or, even better if available, font baseline) should be aligned. NFC: nothing uses this yet. --- dialog.c | 1 + dialog.h | 15 ++++++++++- unix/gtkdlg.c | 22 ++++++++++++++++ windows/winctrls.c | 63 +++++++++++++++++++++++++++++++++++++++++++--- windows/winstuff.h | 6 +++++ 5 files changed, 103 insertions(+), 4 deletions(-) diff --git a/dialog.c b/dialog.c index f8004eee..7409daaa 100644 --- a/dialog.c +++ b/dialog.c @@ -221,6 +221,7 @@ static union control *ctrl_new(struct controlset *s, int type, c->generic.handler = handler; c->generic.context = context; c->generic.label = NULL; + c->generic.align_next_to = NULL; return c; } diff --git a/dialog.h b/dialog.h index e1887efd..86ebfc20 100644 --- a/dialog.h +++ b/dialog.h @@ -113,7 +113,8 @@ typedef void (*handler_fn)(union control *ctrl, dlgparam *dp, int column; \ handler_fn handler; \ intorptr context; \ - intorptr helpctx + intorptr helpctx; \ + union control *align_next_to union control { /* @@ -179,6 +180,18 @@ union control { * to ensure it brings up the right piece of help text. */ intorptr helpctx; + /* + * Setting this to non-NULL coerces two controls to have their + * y-coordinates adjusted so that they can sit alongside each + * other and look nicely aligned, even if they're different + * heights. + * + * Set this field on the _second_ control of the pair (in + * terms of order in the data structure), so that when it's + * instantiated, the first one is already there to be referred + * to. + */ + union control *align_next_to; } generic; struct { STANDARD_PREFIX; diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index 7641e788..8e7421db 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -2491,6 +2491,28 @@ GtkWidget *layout_ctrls( COLUMN_SPAN(ctrl->generic.column)); if (left) columns_force_left_align(cols, w); + if (ctrl->generic.align_next_to) { + /* + * Implement align_next_to by simply forcing the two + * controls to have the same height of size allocation. At + * least for the controls we're currently doing this with, + * the GTK layout system will automatically vertically + * centre each control within its allocation, which will + * get the two controls aligned alongside each other + * reasonably well. + */ + struct uctrl *uc2 = dlg_find_byctrl( + dp, ctrl->generic.align_next_to); + assert(uc2); + columns_force_same_height(cols, w, uc2->toplevel); + +#if GTK_CHECK_VERSION(3, 10, 0) + /* Slightly nicer to align baselines than just vertically + * centring, where the option is available */ + gtk_widget_set_valign(w, GTK_ALIGN_BASELINE); + gtk_widget_set_valign(uc2->toplevel, GTK_ALIGN_BASELINE); +#endif + } gtk_widget_show(w); uc->toplevel = w; diff --git a/windows/winctrls.c b/windows/winctrls.c index c5f43e36..59129eab 100644 --- a/windows/winctrls.c +++ b/windows/winctrls.c @@ -1325,6 +1325,28 @@ struct winctrl *winctrl_findbyindex(struct winctrls *wc, int index) return index234(wc->byid, index); } +static void move_windows(HWND hwnd, int base_id, int num_ids, LONG dy) +{ + if (!dy) + return; + for (int i = 0; i < num_ids; i++) { + HWND win = GetDlgItem(hwnd, base_id + i); + + RECT rect; + if (!GetWindowRect(win, &rect)) + continue; + + POINT p; + p.x = rect.left; + p.y = rect.top + dy; + if (!ScreenToClient(hwnd, &p)) + continue; + + SetWindowPos(win, NULL, p.x, p.y, 0, 0, + SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER); + } +} + void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, struct ctlpos *cp, struct controlset *s, int *id) { @@ -1340,7 +1362,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, char shortcuts[MAX_SHORTCUTS_PER_CTRL]; int nshortcuts; char *escaped; - int i, actual_base_id, base_id, num_ids; + int i, actual_base_id, base_id, num_ids, align_id_relative; void *data; base_id = *id; @@ -1349,7 +1371,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, if (s->boxname && *s->boxname) { struct winctrl *c = snew(struct winctrl); c->ctrl = NULL; - c->base_id = base_id; + c->base_id = c->align_id = base_id; c->num_ids = 1; c->data = NULL; memset(c->shortcuts, NO_SHORTCUT, lenof(c->shortcuts)); @@ -1362,7 +1384,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, if (!s->boxname && s->boxtitle) { struct winctrl *c = snew(struct winctrl); c->ctrl = NULL; - c->base_id = base_id; + c->base_id = c->align_id = base_id; c->num_ids = 1; c->data = dupstr(s->boxtitle); memset(c->shortcuts, NO_SHORTCUT, lenof(c->shortcuts)); @@ -1491,6 +1513,11 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, /* Almost all controls start at base_id. */ actual_base_id = base_id; + /* For vertical alignment purposes, the most relevant control + * in a group is usually the last one. But that can be + * overridden occasionally. */ + align_id_relative = -1; + /* * Now we're ready to actually create the control, by * switching on its type. @@ -1659,6 +1686,10 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, unreachable("bad control type in winctrl_layout"); } + /* Translate the original align_id_relative of -1 into n-1 */ + if (align_id_relative < 0) + align_id_relative += num_ids; + /* * Create a `struct winctrl' for this control, and advance * the dialog ID counter, if it's actually been created @@ -1669,6 +1700,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, c->ctrl = ctrl; c->base_id = actual_base_id; + c->align_id = c->base_id + align_id_relative; c->num_ids = num_ids; c->data = data; memcpy(c->shortcuts, shortcuts, sizeof(shortcuts)); @@ -1676,6 +1708,31 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, winctrl_add_shortcuts(dp, c); if (actual_base_id == base_id) base_id += num_ids; + + if (ctrl->generic.align_next_to) { + /* + * Implement align_next_to by looking at the y extents + * of the two controls now that both are created, and + * moving one or the other downwards so that they're + * centred on a common horizontal line. + */ + struct winctrl *c2 = winctrl_findbyctrl( + wc, ctrl->generic.align_next_to); + HWND win1 = GetDlgItem(pos.hwnd, c->align_id); + HWND win2 = GetDlgItem(pos.hwnd, c2->align_id); + RECT rect1, rect2; + if (win1 && win2 && + GetWindowRect(win1, &rect1) && + GetWindowRect(win2, &rect2)) { + LONG top = (rect1.top < rect2.top ? rect1.top : rect2.top); + LONG bottom = (rect1.bottom > rect2.bottom ? + rect1.bottom : rect2.bottom); + move_windows(pos.hwnd, c->base_id, c->num_ids, + (top + bottom - rect1.top - rect1.bottom)/2); + move_windows(pos.hwnd, c2->base_id, c2->num_ids, + (top + bottom - rect2.top - rect2.bottom)/2); + } + } } else { sfree(data); } diff --git a/windows/winstuff.h b/windows/winstuff.h index 21c96764..c0df5a31 100644 --- a/windows/winstuff.h +++ b/windows/winstuff.h @@ -491,6 +491,12 @@ struct winctrl { */ int base_id; int num_ids; + /* + * For vertical alignment, the id of a particular representative + * control that has the y-extent of the sensible part of the + * control. + */ + int align_id; /* * Remember what keyboard shortcuts were used by this control, * so that when we remove it again we can take them out of the