Support for using variable-pitch fonts for the terminal on Windows.
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Wed, 29 Dec 2010 14:11:25 +0000 (14:11 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Wed, 29 Dec 2010 14:11:25 +0000 (14:11 +0000)
Done in much the same way as it is in the GTK front end: the character
cell width is determined using the font's digits (which seems to give
generally not-too-offensive spacing in most cases, at the expense of
Ms and Ws typically overhanging a bit into adjacent cells) and each
character is centred in its cell. Overhangs never leave permanent
droppings on the window, because the existing work done in r5003
handles them just fine even in this stressful scenario.

There's a hacky new checkbox in the Appearance panel to make
variable-pitch fonts appear in the font selector (they still don't by
default, because I still think it's _usually_ not What You Want); the
checkbox state is not actually stored as part of a saved session, but
it should be automatically ticked when reloading a session that's got
a variable pitch font selected.

(I'm half-expecting a potential flurry of requests for this feature in
the wake of http://xkcd.com/840/ , so I thought I'd pre-empt them :-)

git-svn-id: svn://svn.tartarus.org/sgt/putty@9063 cda61777-01e9-0310-a592-d414129be87e

doc/config.but
windows/wincfg.c
windows/winctrls.c
windows/windlg.c
windows/window.c
windows/winstuff.h

index daa20ed..106cc5b 100644 (file)
@@ -1096,10 +1096,15 @@ works in any of the cursor modes.
 \cfg{winhelp-topic}{appearance.font}
 
 This option allows you to choose what font, in what \I{font size}size,
-the PuTTY terminal window uses to display the text in the session. You
-will be offered a choice from all the fixed-width fonts installed on the
-system. (VT100-style terminal handling can only deal with fixed-width
-fonts.)
+the PuTTY terminal window uses to display the text in the session.
+
+By default, you will be offered a choice from all the fixed-width
+fonts installed on the system, since VT100-style terminal handling
+expects a fixed-width font. If you tick the box marked \q{Allow
+selection of variable-pitch fonts}, however, PuTTY will offer
+variable-width fonts as well: if you select one of these, the font
+will be coerced into fixed-size character cells, which will probably
+not look very good (but can work OK with some fonts).
 
 \S{config-mouseptr} \q{Hide \i{mouse pointer} when typing in window}
 
index 389eb38..1cf56c8 100644 (file)
@@ -30,6 +30,16 @@ static void help_handler(union control *ctrl, void *dlg,
     }
 }
 
+static void variable_pitch_handler(union control *ctrl, void *dlg,
+                                   void *data, int event)
+{
+    if (event == EVENT_REFRESH) {
+       dlg_checkbox_set(ctrl, dlg, !dlg_get_fixed_pitch_flag(dlg));
+    } else if (event == EVENT_VALCHANGE) {
+       dlg_set_fixed_pitch_flag(dlg, !dlg_checkbox_get(ctrl, dlg));
+    }
+}
+
 void win_setup_config_box(struct controlbox *b, HWND *hwndp, int has_help,
                          int midsession, int protocol)
 {
@@ -177,6 +187,8 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, int has_help,
      */
     s = ctrl_getset(b, "Window/Appearance", "font",
                    "Font settings");
+    ctrl_checkbox(s, "Allow selection of variable-pitch fonts", NO_SHORTCUT,
+                  HELPCTX(appearance_font), variable_pitch_handler, I(0));
     ctrl_radiobuttons(s, "Font quality:", 'q', 2,
                      HELPCTX(appearance_font),
                      dlg_stdradiobutton_handler,
index a618a69..7c6bf8d 100644 (file)
@@ -1955,8 +1955,8 @@ int winctrl_handle_command(struct dlgparam *dp, UINT msg,
            cf.lStructSize = sizeof(cf);
            cf.hwndOwner = dp->hwnd;
            cf.lpLogFont = &lf;
-           cf.Flags = CF_FIXEDPITCHONLY | CF_FORCEFONTEXIST |
-               CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;
+           cf.Flags = (dp->fixed_pitch_fonts ? CF_FIXEDPITCHONLY : 0) |
+                CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;
 
            if (ChooseFont(&cf)) {
                strncpy(fs.name, lf.lfFaceName,
@@ -2321,6 +2321,8 @@ void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec fs)
                        (fs.height < 0 ? "pixel" : "point"));
     SetDlgItemText(dp->hwnd, c->base_id+1, buf);
     sfree(buf);
+
+    dlg_auto_set_fixed_pitch_flag(dp);
 }
 
 void dlg_fontsel_get(union control *ctrl, void *dlg, FontSpec *fs)
@@ -2466,6 +2468,57 @@ int dlg_coloursel_results(union control *ctrl, void *dlg,
        return 0;
 }
 
+void dlg_auto_set_fixed_pitch_flag(void *dlg)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    Config *cfg = (Config *)dp->data;
+    HFONT font;
+    HDC hdc;
+    TEXTMETRIC tm;
+    int is_var;
+
+    /*
+     * Attempt to load the current font, and see if it's
+     * variable-pitch. If so, start off the fixed-pitch flag for the
+     * dialog box as false.
+     *
+     * We assume here that any client of the dlg_* mechanism which is
+     * using font selectors at all is also using a normal 'Config *'
+     * as dp->data.
+     */
+
+    font = CreateFont(0, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE,
+                      DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
+                      CLIP_DEFAULT_PRECIS, FONT_QUALITY(cfg->font_quality),
+                      FIXED_PITCH | FF_DONTCARE, cfg->font.name);
+    hdc = GetDC(NULL);
+    if (font && hdc && SelectObject(hdc, font) && GetTextMetrics(hdc, &tm)) {
+        /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */
+        is_var = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH);
+    } else {
+        is_var = FALSE;                /* assume it's basically normal */
+    }
+    if (hdc)
+        ReleaseDC(NULL, hdc);
+    if (font)
+        DeleteObject(font);
+
+    if (is_var)
+        dp->fixed_pitch_fonts = FALSE;
+}
+
+int dlg_get_fixed_pitch_flag(void *dlg)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    return dp->fixed_pitch_fonts;
+}
+
+void dlg_set_fixed_pitch_flag(void *dlg, int flag)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    dp->fixed_pitch_fonts = flag;
+}
+
 struct perctrl_privdata {
     union control *ctrl;
     void *data;
@@ -2493,6 +2546,7 @@ void dp_init(struct dlgparam *dp)
     dp->hwnd = NULL;
     dp->wintitle = dp->errtitle = NULL;
     dp->privdata = newtree234(perctrl_privdata_cmp);
+    dp->fixed_pitch_fonts = TRUE;
 }
 
 void dp_add_tree(struct dlgparam *dp, struct winctrls *wc)
index beedeb2..7377cd3 100644 (file)
@@ -649,6 +649,7 @@ int do_config(void)
     dp.wintitle = dupprintf("%s Configuration", appname);
     dp.errtitle = dupprintf("%s Error", appname);
     dp.data = &cfg;
+    dlg_auto_set_fixed_pitch_flag(&dp);
     dp.shortcuts['g'] = TRUE;         /* the treeview: `Cate&gory' */
 
     ret =
@@ -682,6 +683,7 @@ int do_reconfig(HWND hwnd, int protcfginfo)
     dp.wintitle = dupprintf("%s Reconfiguration", appname);
     dp.errtitle = dupprintf("%s Error", appname);
     dp.data = &cfg;
+    dlg_auto_set_fixed_pitch_flag(&dp);
     dp.shortcuts['g'] = TRUE;         /* the treeview: `Cate&gory' */
 
     ret = SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
index 386f109..f47fb47 100644 (file)
@@ -99,7 +99,7 @@ static int process_clipdata(HGLOBAL clipdata, int unicode);
 /* Window layout information */
 static void reset_window(int);
 static int extra_width, extra_height;
-static int font_width, font_height, font_dualwidth;
+static int font_width, font_height, font_dualwidth, font_varpitch;
 static int offset_width, offset_height;
 static int was_zoomed = 0;
 static int prev_rows, prev_cols;
@@ -1260,7 +1260,6 @@ static void exact_textout(HDC hdc, int x, int y, CONST RECT *lprc,
     gcpr.lpGlyphs = (void *)buffer;
     gcpr.lpClass = (void *)classbuffer;
     gcpr.nGlyphs = cbCount;
-
     GetCharacterPlacementW(hdc, lpString, cbCount, 0, &gcpr,
                           FLI_MASK | GCP_CLASSIN | GCP_DIACRITIC);
 
@@ -1352,6 +1351,37 @@ debug(("general_textout: done, xn=%d\n", xn));
     assert(xn - x >= lprc->right - lprc->left);
 }
 
+static int get_font_width(HDC hdc, const TEXTMETRIC *tm)
+{
+    int ret;
+    /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */
+    if (!(tm->tmPitchAndFamily & TMPF_FIXED_PITCH)) {
+        ret = tm->tmAveCharWidth;
+    } else {
+#define FIRST '0'
+#define LAST '9'
+        ABCFLOAT widths[LAST-FIRST + 1];
+        int j;
+
+        font_varpitch = TRUE;
+        font_dualwidth = TRUE;
+        if (GetCharABCWidthsFloat(hdc, FIRST, LAST, widths)) {
+            ret = 0;
+            for (j = 0; j < lenof(widths); j++) {
+                int width = (int)(0.5 + widths[j].abcfA +
+                                  widths[j].abcfB + widths[j].abcfC);
+                if (ret < width)
+                    ret = width;
+            }
+        } else {
+            ret = tm->tmMaxCharWidth;
+        }
+#undef FIRST
+#undef LAST
+    }
+    return ret;
+}
+
 /*
  * Initialise all the fonts we will need initially. There may be as many as
  * three or as few as one.  The other (potentially) twenty-one fonts are done
@@ -1418,11 +1448,18 @@ static void init_fonts(int pick_width, int pick_height)
 
     GetObject(fonts[FONT_NORMAL], sizeof(LOGFONT), &lfont);
 
+    /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */
+    if (!(tm.tmPitchAndFamily & TMPF_FIXED_PITCH)) {
+        font_varpitch = FALSE;
+        font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth);
+    } else {
+        font_varpitch = TRUE;
+        font_dualwidth = TRUE;
+    }
     if (pick_width == 0 || pick_height == 0) {
        font_height = tm.tmHeight;
-       font_width = tm.tmAveCharWidth;
+        font_width = get_font_width(hdc, &tm);
     }
-    font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth);
 
 #ifdef RDB_DEBUG_PATCH
     debug(23, "Primary font H=%d, AW=%d, MW=%d",
@@ -1509,7 +1546,7 @@ static void init_fonts(int pick_width, int pick_height)
     for (i = 0; i < 3; i++) {
        if (fonts[i]) {
            if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
-               fontsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
+               fontsize[i] = get_font_width(hdc, &tm) + 256 * tm.tmHeight;
            else
                fontsize[i] = -i;
        } else
@@ -1578,7 +1615,7 @@ static void another_font(int fontno)
        CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w,
                   FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS,
                   CLIP_DEFAULT_PRECIS, FONT_QUALITY(cfg.font_quality),
-                  FIXED_PITCH | FF_DONTCARE, s);
+                  DEFAULT_PITCH | FF_DONTCARE, s);
 
     fontflag[fontno] = 1;
 }
@@ -3198,7 +3235,10 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len,
     int force_manual_underline = 0;
     int fnt_width, char_width;
     int text_adjust = 0;
+    int xoffset = 0;
+    int maxlen, remaining, opaque;
     static int *IpDx = 0, IpDxLEN = 0;
+    int *IpDxReal;
 
     lattr &= LATTR_MODE;
 
@@ -3338,111 +3378,155 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len,
     if (line_box.right > font_width*term->cols+offset_width)
        line_box.right = font_width*term->cols+offset_width;
 
-    /* We're using a private area for direct to font. (512 chars.) */
-    if (ucsdata.dbcs_screenfont && (text[0] & CSET_MASK) == CSET_ACP) {
-       /* Ho Hum, dbcs fonts are a PITA! */
-       /* To display on W9x I have to convert to UCS */
-       static wchar_t *uni_buf = 0;
-       static int uni_len = 0;
-       int nlen, mptr;
-       if (len > uni_len) {
-           sfree(uni_buf);
-           uni_len = len;
-           uni_buf = snewn(uni_len, wchar_t);
-       }
-
-       for(nlen = mptr = 0; mptr<len; mptr++) {
-           uni_buf[nlen] = 0xFFFD;
-           if (IsDBCSLeadByteEx(ucsdata.font_codepage, (BYTE) text[mptr])) {
-               char dbcstext[2];
-               dbcstext[0] = text[mptr] & 0xFF;
-               dbcstext[1] = text[mptr+1] & 0xFF;
-               IpDx[nlen] += char_width;
-               MultiByteToWideChar(ucsdata.font_codepage, MB_USEGLYPHCHARS,
-                                   dbcstext, 2, uni_buf+nlen, 1);
-               mptr++;
-           }
-           else
-           {
-               char dbcstext[1];
-               dbcstext[0] = text[mptr] & 0xFF;
-               MultiByteToWideChar(ucsdata.font_codepage, MB_USEGLYPHCHARS,
-                                   dbcstext, 1, uni_buf+nlen, 1);
-           }
-           nlen++;
-       }
-       if (nlen <= 0)
-           return;                    /* Eeek! */
-
-       ExtTextOutW(hdc, x,
-                   y - font_height * (lattr == LATTR_BOT) + text_adjust,
-                   ETO_CLIPPED | ETO_OPAQUE, &line_box, uni_buf, nlen, IpDx);
-       if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
-           SetBkMode(hdc, TRANSPARENT);
-           ExtTextOutW(hdc, x - 1,
-                       y - font_height * (lattr ==
-                                          LATTR_BOT) + text_adjust,
-                       ETO_CLIPPED, &line_box, uni_buf, nlen, IpDx);
-       }
+    if (font_varpitch) {
+        /*
+         * If we're using a variable-pitch font, we unconditionally
+         * draw the glyphs one at a time and centre them in their
+         * character cells (which means in particular that we must
+         * disable the IpDx mechanism). This gives slightly odd but
+         * generally reasonable results.
+         */
+        xoffset = char_width / 2;
+        SetTextAlign(hdc, TA_TOP | TA_CENTER | TA_NOUPDATECP);
+        IpDxReal = NULL;
+        maxlen = 1;
+    } else {
+        /*
+         * In a fixed-pitch font, we draw the whole string in one go
+         * in the normal way.
+         */
+        xoffset = 0;
+        SetTextAlign(hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
+        IpDxReal = IpDx;
+        maxlen = len;
+    }
 
-       IpDx[0] = -1;
-    } else if (DIRECT_FONT(text[0])) {
-       static char *directbuf = NULL;
-       static int directlen = 0;
-       int i;
-       if (len > directlen) {
-           directlen = len;
-           directbuf = sresize(directbuf, directlen, char);
-       }
+    opaque = TRUE;                     /* start by erasing the rectangle */
+    for (remaining = len; remaining > 0;
+         text += len, remaining -= len, x += char_width * len) {
+        len = (maxlen < remaining ? maxlen : remaining);
+
+        /* We're using a private area for direct to font. (512 chars.) */
+        if (ucsdata.dbcs_screenfont && (text[0] & CSET_MASK) == CSET_ACP) {
+            /* Ho Hum, dbcs fonts are a PITA! */
+            /* To display on W9x I have to convert to UCS */
+            static wchar_t *uni_buf = 0;
+            static int uni_len = 0;
+            int nlen, mptr;
+            if (len > uni_len) {
+                sfree(uni_buf);
+                uni_len = len;
+                uni_buf = snewn(uni_len, wchar_t);
+            }
 
-       for (i = 0; i < len; i++)
-           directbuf[i] = text[i] & 0xFF;
+            for(nlen = mptr = 0; mptr<len; mptr++) {
+                uni_buf[nlen] = 0xFFFD;
+                if (IsDBCSLeadByteEx(ucsdata.font_codepage,
+                                     (BYTE) text[mptr])) {
+                    char dbcstext[2];
+                    dbcstext[0] = text[mptr] & 0xFF;
+                    dbcstext[1] = text[mptr+1] & 0xFF;
+                    IpDx[nlen] += char_width;
+                    MultiByteToWideChar(ucsdata.font_codepage, MB_USEGLYPHCHARS,
+                                        dbcstext, 2, uni_buf+nlen, 1);
+                    mptr++;
+                }
+                else
+                {
+                    char dbcstext[1];
+                    dbcstext[0] = text[mptr] & 0xFF;
+                    MultiByteToWideChar(ucsdata.font_codepage, MB_USEGLYPHCHARS,
+                                        dbcstext, 1, uni_buf+nlen, 1);
+                }
+                nlen++;
+            }
+            if (nlen <= 0)
+                return;                       /* Eeek! */
+
+            ExtTextOutW(hdc, x + xoffset,
+                        y - font_height * (lattr == LATTR_BOT) + text_adjust,
+                        ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0),
+                        &line_box, uni_buf, nlen,
+                        IpDxReal);
+            if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
+                SetBkMode(hdc, TRANSPARENT);
+                ExtTextOutW(hdc, x + xoffset - 1,
+                            y - font_height * (lattr ==
+                                               LATTR_BOT) + text_adjust,
+                            ETO_CLIPPED, &line_box, uni_buf, nlen, IpDxReal);
+            }
 
-       ExtTextOut(hdc, x,
-                  y - font_height * (lattr == LATTR_BOT) + text_adjust,
-                  ETO_CLIPPED | ETO_OPAQUE, &line_box, directbuf, len, IpDx);
-       if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
-           SetBkMode(hdc, TRANSPARENT);
+            IpDx[0] = -1;
+        } else if (DIRECT_FONT(text[0])) {
+            static char *directbuf = NULL;
+            static int directlen = 0;
+            int i;
+            if (len > directlen) {
+                directlen = len;
+                directbuf = sresize(directbuf, directlen, char);
+            }
 
-           /* GRR: This draws the character outside its box and can leave
-            * 'droppings' even with the clip box! I suppose I could loop it
-            * one character at a time ... yuk. 
-            * 
-            * Or ... I could do a test print with "W", and use +1 or -1 for this
-            * shift depending on if the leftmost column is blank...
-            */
-           ExtTextOut(hdc, x - 1,
-                      y - font_height * (lattr ==
-                                         LATTR_BOT) + text_adjust,
-                      ETO_CLIPPED, &line_box, directbuf, len, IpDx);
-       }
-    } else {
-       /* And 'normal' unicode characters */
-       static WCHAR *wbuf = NULL;
-       static int wlen = 0;
-       int i;
+            for (i = 0; i < len; i++)
+                directbuf[i] = text[i] & 0xFF;
+
+            ExtTextOut(hdc, x + xoffset,
+                       y - font_height * (lattr == LATTR_BOT) + text_adjust,
+                       ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0),
+                       &line_box, directbuf, len, IpDxReal);
+            if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
+                SetBkMode(hdc, TRANSPARENT);
+
+                /* GRR: This draws the character outside its box and
+                 * can leave 'droppings' even with the clip box! I
+                 * suppose I could loop it one character at a time ...
+                 * yuk.
+                 * 
+                 * Or ... I could do a test print with "W", and use +1
+                 * or -1 for this shift depending on if the leftmost
+                 * column is blank...
+                 */
+                ExtTextOut(hdc, x + xoffset - 1,
+                           y - font_height * (lattr ==
+                                              LATTR_BOT) + text_adjust,
+                           ETO_CLIPPED, &line_box, directbuf, len, IpDxReal);
+            }
+        } else {
+            /* And 'normal' unicode characters */
+            static WCHAR *wbuf = NULL;
+            static int wlen = 0;
+            int i;
+
+            if (wlen < len) {
+                sfree(wbuf);
+                wlen = len;
+                wbuf = snewn(wlen, WCHAR);
+            }
 
-       if (wlen < len) {
-           sfree(wbuf);
-           wlen = len;
-           wbuf = snewn(wlen, WCHAR);
-       }
+            for (i = 0; i < len; i++)
+                wbuf[i] = text[i];
+
+            /* print Glyphs as they are, without Windows' Shaping*/
+            general_textout(hdc, x + xoffset,
+                            y - font_height * (lattr==LATTR_BOT) + text_adjust,
+                            &line_box, wbuf, len, IpDxReal,
+                            opaque && !(attr & TATTR_COMBINING));
+
+            /* And the shadow bold hack. */
+            if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
+                SetBkMode(hdc, TRANSPARENT);
+                ExtTextOutW(hdc, x + xoffset - 1,
+                            y - font_height * (lattr ==
+                                               LATTR_BOT) + text_adjust,
+                            ETO_CLIPPED, &line_box, wbuf, len, IpDxReal);
+            }
+        }
 
-       for (i = 0; i < len; i++)
-           wbuf[i] = text[i];
-
-       /* print Glyphs as they are, without Windows' Shaping*/
-       general_textout(hdc, x, y - font_height * (lattr == LATTR_BOT) + text_adjust,
-                       &line_box, wbuf, len, IpDx, !(attr & TATTR_COMBINING));
-
-       /* And the shadow bold hack. */
-       if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
-           SetBkMode(hdc, TRANSPARENT);
-           ExtTextOutW(hdc, x - 1,
-                       y - font_height * (lattr ==
-                                          LATTR_BOT) + text_adjust,
-                       ETO_CLIPPED, &line_box, wbuf, len, IpDx);
-       }
+        /*
+         * If we're looping round again, stop erasing the background
+         * rectangle.
+         */
+        SetBkMode(hdc, TRANSPARENT);
+        opaque = FALSE;
     }
     if (lattr != LATTR_TOP && (force_manual_underline ||
                               (und_mode == UND_LINE
index 610d71a..6d5175d 100644 (file)
@@ -316,6 +316,7 @@ struct dlgparam {
     struct { unsigned char r, g, b, ok; } coloursel_result;   /* 0-255 */
     tree234 *privdata;                /* stores per-control private data */
     int ended, endresult;             /* has the dialog been ended? */
+    int fixed_pitch_fonts;             /* are we constrained to fixed fonts? */
 };
 
 /*
@@ -374,6 +375,10 @@ void fwdsetter(struct ctlpos *cp, int listid, char *stext, int sid,
               char *btext, int bid,
               char *r1text, int r1id, char *r2text, int r2id);
 
+void dlg_auto_set_fixed_pitch_flag(void *dlg);
+int dlg_get_fixed_pitch_flag(void *dlg);
+void dlg_set_fixed_pitch_flag(void *dlg, int flag);
+
 #define MAX_SHORTCUTS_PER_CTRL 16
 
 /*