Rectangular-block selection. Enabled by Alt+drag, unless you
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Wed, 31 Oct 2001 18:50:09 +0000 (18:50 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Wed, 31 Oct 2001 18:50:09 +0000 (18:50 +0000)
configure it to be the default in which case it's _dis_abled by
Alt+drag.

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

putty.h
settings.c
terminal.c
winctrls.c
windlg.c
window.c

diff --git a/putty.h b/putty.h
index 8bd681e..5a521cb 100644 (file)
--- a/putty.h
+++ b/putty.h
@@ -326,6 +326,7 @@ typedef struct {
     unsigned char colours[22][3];
     /* Selection options */
     int mouse_is_xterm;
+    int rect_select;
     int rawcnp;
     int mouse_override;
     short wordness[256];
@@ -452,7 +453,7 @@ void term_paint(Context, int, int, int, int);
 void term_scroll(int, int);
 void term_pwron(void);
 void term_clrsb(void);
-void term_mouse(Mouse_Button, Mouse_Action, int, int, int, int);
+void term_mouse(Mouse_Button, Mouse_Action, int, int, int, int, int);
 void term_deselect(void);
 void term_update(void);
 void term_invalidate(void);
index 8afd6c2..65c508a 100644 (file)
@@ -235,6 +235,7 @@ void save_settings(char *section, int do_host, Config * cfg)
     }
     write_setting_i(sesskey, "RawCNP", cfg->rawcnp);
     write_setting_i(sesskey, "MouseIsXterm", cfg->mouse_is_xterm);
+    write_setting_i(sesskey, "RectSelect", cfg->rect_select);
     write_setting_i(sesskey, "MouseOverride", cfg->mouse_override);
     for (i = 0; i < 256; i += 32) {
        char buf[20], buf2[256];
@@ -440,6 +441,7 @@ void load_settings(char *section, int do_host, Config * cfg)
     }
     gppi(sesskey, "RawCNP", 0, &cfg->rawcnp);
     gppi(sesskey, "MouseIsXterm", 0, &cfg->mouse_is_xterm);
+    gppi(sesskey, "RectSelect", 0, &cfg->rect_select);
     gppi(sesskey, "MouseOverride", 1, &cfg->mouse_override);
     for (i = 0; i < 256; i += 32) {
        static char *defaults[] = {
index 1e2d50c..7f6f8bb 100644 (file)
@@ -86,6 +86,10 @@ typedef struct {
 #define incpos(p) ( (p).x == cols ? ((p).x = 0, (p).y++, 1) : ((p).x++, 0) )
 #define decpos(p) ( (p).x == 0 ? ((p).x = cols, (p).y--, 1) : ((p).x--, 0) )
 
+/* Product-order comparisons for rectangular block selection. */
+#define posPlt(p1,p2) ( (p1).y <= (p2).y && (p1).x < (p2).x )
+#define posPle(p1,p2) ( (p1).y <= (p2).y && (p1).x <= (p2).x )
+
 static bufchain inbuf;                /* terminal input buffer */
 static pos curs;                      /* cursor */
 static pos savecurs;                  /* saved cursor position */
@@ -163,6 +167,9 @@ static enum {
     NO_SELECTION, ABOUT_TO, DRAGGING, SELECTED
 } selstate;
 static enum {
+    LEXICOGRAPHIC, RECTANGULAR
+} seltype;
+static enum {
     SM_CHAR, SM_WORD, SM_LINE
 } selmode;
 static pos selstart, selend, selanchor;
@@ -2586,7 +2593,7 @@ static void do_paint(Context ctx, int may_optimise)
     for (i = 0; i < rows; i++) {
        unsigned long *ldata;
        int lattr;
-       int idx, dirty_line, dirty_run;
+       int idx, dirty_line, dirty_run, selected;
        unsigned long attr = 0;
        int updated_line = 0;
        int start = 0;
@@ -2626,9 +2633,12 @@ static void do_paint(Context ctx, int may_optimise)
                    tattr |= ATTR_WIDE;
 
            /* Video reversing things */
+           if (seltype == LEXICOGRAPHIC)
+               selected = posle(selstart, scrpos) && poslt(scrpos, selend);
+           else
+               selected = posPle(selstart, scrpos) && posPlt(scrpos, selend);
            tattr = (tattr ^ rv
-                    ^ (posle(selstart, scrpos) &&
-                       poslt(scrpos, selend) ? ATTR_REVERSE : 0));
+                    ^ (selected ? ATTR_REVERSE : 0));
 
            /* 'Real' blinking ? */
            if (blink_is_real && (tattr & ATTR_BLINK)) {
@@ -2816,25 +2826,38 @@ void term_scroll(int rel, int where)
     term_update();
 }
 
-static void clipme(pos top, pos bottom)
+static void clipme(pos top, pos bottom, int rect)
 {
     wchar_t *workbuf;
     wchar_t *wbptr;                   /* where next char goes within workbuf */
+    int old_top_x;
     int wblen = 0;                    /* workbuf len */
     int buflen;                               /* amount of memory allocated to workbuf */
 
     buflen = 5120;                    /* Default size */
     workbuf = smalloc(buflen * sizeof(wchar_t));
     wbptr = workbuf;                  /* start filling here */
+    old_top_x = top.x;                /* needed for rect==1 */
 
     while (poslt(top, bottom)) {
        int nl = FALSE;
        unsigned long *ldata = lineptr(top.y);
        pos nlpos;
 
+       /*
+        * nlpos will point at the maximum position on this line we
+        * should copy up to. So we start it at the end of the
+        * line...
+        */
        nlpos.y = top.y;
        nlpos.x = cols;
 
+       /*
+        * ... move it backwards if there's unused space at the end
+        * of the line (and also set `nl' if this is the case,
+        * because in normal selection mode this means we need a
+        * newline at the end)...
+        */
        if (!(ldata[cols] & LATTR_WRAPPED)) {
            while (((ldata[nlpos.x - 1] & 0xFF) == 0x20 ||
                    (DIRECT_CHAR(ldata[nlpos.x - 1]) &&
@@ -2844,6 +2867,20 @@ static void clipme(pos top, pos bottom)
            if (poslt(nlpos, bottom))
                nl = TRUE;
        }
+
+       /*
+        * ... and then clip it to the terminal x coordinate if
+        * we're doing rectangular selection. (In this case we
+        * still did the above, so that copying e.g. the right-hand
+        * column from a table doesn't fill with spaces on the
+        * right.)
+        */
+       if (rect) {
+           if (nlpos.x > bottom.x)
+               nlpos.x = bottom.x;
+           nl = (top.y < bottom.y);
+       }
+
        while (poslt(top, bottom) && poslt(top, nlpos)) {
 #if 0
            char cbuf[16], *p;
@@ -2931,7 +2968,7 @@ static void clipme(pos top, pos bottom)
            }
        }
        top.y++;
-       top.x = 0;
+       top.x = rect ? old_top_x : 0;
     }
     wblen++;
     *wbptr++ = 0;
@@ -2945,7 +2982,7 @@ void term_copyall(void)
     pos top;
     top.y = -count234(scrollback);
     top.x = 0;
-    clipme(top, curs);
+    clipme(top, curs, 0);
 }
 
 /*
@@ -3149,10 +3186,12 @@ static pos sel_spread_half(pos p, int dir)
 
 static void sel_spread(void)
 {
-    selstart = sel_spread_half(selstart, -1);
-    decpos(selend);
-    selend = sel_spread_half(selend, +1);
-    incpos(selend);
+    if (seltype == LEXICOGRAPHIC) {
+       selstart = sel_spread_half(selstart, -1);
+       decpos(selend);
+       selend = sel_spread_half(selend, +1);
+       incpos(selend);
+    }
 }
 
 void term_do_paste(void)
@@ -3204,11 +3243,12 @@ void term_do_paste(void)
 }
 
 void term_mouse(Mouse_Button b, Mouse_Action a, int x, int y,
-               int shift, int ctrl)
+               int shift, int ctrl, int alt)
 {
     pos selpoint;
     unsigned long *ldata;
     int raw_mouse = xterm_mouse && !(cfg.mouse_override && shift);
+    int default_seltype;
 
     if (y < 0) {
        y = 0;
@@ -3290,9 +3330,23 @@ void term_mouse(Mouse_Button b, Mouse_Action a, int x, int y,
 
     b = translate_button(b);
 
+    /*
+     * Set the selection type (rectangular or normal) at the start
+     * of a selection attempt, from the state of Alt.
+     */
+    if (!alt ^ !cfg.rect_select)
+       default_seltype = RECTANGULAR;
+    else
+       default_seltype = LEXICOGRAPHIC;
+       
+    if (selstate == NO_SELECTION) {
+       seltype = default_seltype;
+    }
+
     if (b == MBT_SELECT && a == MA_CLICK) {
        deselect();
        selstate = ABOUT_TO;
+       seltype = default_seltype;
        selanchor = selpoint;
        selmode = SM_CHAR;
     } else if (b == MBT_SELECT && (a == MA_2CLK || a == MA_3CLK)) {
@@ -3308,26 +3362,64 @@ void term_mouse(Mouse_Button b, Mouse_Action a, int x, int y,
        if (selstate == ABOUT_TO && poseq(selanchor, selpoint))
            return;
        if (b == MBT_EXTEND && a != MA_DRAG && selstate == SELECTED) {
-           if (posdiff(selpoint, selstart) <
-               posdiff(selend, selstart) / 2) {
-               selanchor = selend;
-               decpos(selanchor);
+           if (seltype == LEXICOGRAPHIC) {
+               /*
+                * For normal selection, we extend by moving
+                * whichever end of the current selection is closer
+                * to the mouse.
+                */
+               if (posdiff(selpoint, selstart) <
+                   posdiff(selend, selstart) / 2) {
+                   selanchor = selend;
+                   decpos(selanchor);
+               } else {
+                   selanchor = selstart;
+               }
            } else {
-               selanchor = selstart;
+               /*
+                * For rectangular selection, we have a choice of
+                * _four_ places to put selanchor and selpoint: the
+                * four corners of the selection.
+                */
+               if (2*selpoint.x < selstart.x + selend.x)
+                   selanchor.x = selend.x-1;
+               else
+                   selanchor.x = selstart.x;
+
+               if (2*selpoint.y < selstart.y + selend.y)
+                   selanchor.y = selend.y;
+               else
+                   selanchor.y = selstart.y;
            }
            selstate = DRAGGING;
        }
        if (selstate != ABOUT_TO && selstate != DRAGGING)
            selanchor = selpoint;
        selstate = DRAGGING;
-       if (poslt(selpoint, selanchor)) {
-           selstart = selpoint;
-           selend = selanchor;
-           incpos(selend);
+       if (seltype == LEXICOGRAPHIC) {
+           /*
+            * For normal selection, we set (selstart,selend) to
+            * (selpoint,selanchor) in some order.
+            */
+           if (poslt(selpoint, selanchor)) {
+               selstart = selpoint;
+               selend = selanchor;
+               incpos(selend);
+           } else {
+               selstart = selanchor;
+               selend = selpoint;
+               incpos(selend);
+           }
        } else {
-           selstart = selanchor;
-           selend = selpoint;
-           incpos(selend);
+           /*
+            * For rectangular selection, we may need to
+            * interchange x and y coordinates (if the user has
+            * dragged in the -x and +y directions, or vice versa).
+            */
+           selstart.x = min(selanchor.x, selpoint.x);
+           selend.x = 1+max(selanchor.x, selpoint.x);
+           selstart.y = min(selanchor.y, selpoint.y);
+           selend.y =   max(selanchor.y, selpoint.y);
        }
        sel_spread();
     } else if ((b == MBT_SELECT || b == MBT_EXTEND) && a == MA_RELEASE) {
@@ -3336,7 +3428,7 @@ void term_mouse(Mouse_Button b, Mouse_Action a, int x, int y,
             * We've completed a selection. We now transfer the
             * data to the clipboard.
             */
-           clipme(selstart, selend);
+           clipme(selstart, selend, (seltype == RECTANGULAR));
            selstate = SELECTED;
        } else
            selstate = NO_SELECTION;
index eb082f3..126c4a7 100644 (file)
@@ -712,7 +712,7 @@ void charclass(struct ctlpos *cp, char *stext, int sid, int listid,
                        PUSHBTNHEIGHT ? EDITHEIGHT : PUSHBTNHEIGHT);
     const static int percents[] = { 30, 40, 30 };
     int i, xpos, percent;
-    const int LISTHEIGHT = 66;
+    const int LISTHEIGHT = 52;
 
     /* The static control. */
     r.left = GAPBETWEEN;
index 25b8445..52e092d 100644 (file)
--- a/windlg.c
+++ b/windlg.c
@@ -479,6 +479,9 @@ enum { IDCX_ABOUT =
     IDC_MBWINDOWS,
     IDC_MBXTERM,
     IDC_MOUSEOVERRIDE,
+    IDC_SELTYPESTATIC,
+    IDC_SELTYPELEX,
+    IDC_SELTYPERECT,
     IDC_CCSTATIC,
     IDC_CCLIST,
     IDC_CCSET,
@@ -763,9 +766,10 @@ static void init_dlg_ctrls(HWND hwnd, int keepsess)
 
     }
 
-
     CheckRadioButton(hwnd, IDC_MBWINDOWS, IDC_MBXTERM,
                     cfg.mouse_is_xterm ? IDC_MBXTERM : IDC_MBWINDOWS);
+    CheckRadioButton(hwnd, IDC_SELTYPELEX, IDC_SELTYPERECT,
+                    cfg.rect_select == 0 ? IDC_SELTYPELEX : IDC_SELTYPERECT);
     CheckDlgButton(hwnd, IDC_MOUSEOVERRIDE, cfg.mouse_override);
     CheckDlgButton(hwnd, IDC_RAWCNP, cfg.rawcnp);
     {
@@ -1141,7 +1145,7 @@ static void create_controls(HWND hwnd, int dlgtype, int panel)
     }
 
     if (panel == selectionpanelstart) {
-       /* The Selection panel. Accelerators used: [acgo] d wxp hst */
+       /* The Selection panel. Accelerators used: [acgo] d wxp hst nr */
        struct ctlpos cp;
        ctlposinit(&cp, hwnd, 80, 3, 13);
        bartitle(&cp, "Options controlling copy and paste",
@@ -1161,6 +1165,11 @@ static void create_controls(HWND hwnd, int dlgtype, int panel)
        checkbox(&cp,
                 "Shift overrides a&pplication's use of mouse",
                 IDC_MOUSEOVERRIDE);
+        radioline(&cp,
+                  "Default selection mode (Alt+drag does the other one):",
+                  IDC_SELTYPESTATIC, 2,
+                 "&Normal", IDC_SELTYPELEX,
+                 "&Rectangular block", IDC_SELTYPERECT, NULL);
        endbox(&cp);
        beginbox(&cp, "Control the select-one-word-at-a-time mode",
                 IDC_BOX_SELECTION3);
@@ -2407,6 +2416,10 @@ static int GenericMainDlgProc(HWND hwnd, UINT msg,
              case IDC_MBXTERM:
                cfg.mouse_is_xterm = IsDlgButtonChecked(hwnd, IDC_MBXTERM);
                break;
+             case IDC_SELTYPELEX:
+             case IDC_SELTYPERECT:
+               cfg.rect_select = IsDlgButtonChecked(hwnd, IDC_SELTYPERECT);
+               break;
              case IDC_MOUSEOVERRIDE:
                cfg.mouse_override = IsDlgButtonChecked(hwnd, IDC_MOUSEOVERRIDE);
                break;
index 006e5ff..9e09223 100644 (file)
--- a/window.c
+++ b/window.c
@@ -1335,13 +1335,13 @@ static void reset_window(int reinit) {
     }
 }
 
-static void click(Mouse_Button b, int x, int y, int shift, int ctrl)
+static void click(Mouse_Button b, int x, int y, int shift, int ctrl, int alt)
 {
     int thistime = GetMessageTime();
 
     if (send_raw_mouse && !(cfg.mouse_override && shift)) {
        lastbtn = MBT_NOTHING;
-       term_mouse(b, MA_CLICK, x, y, shift, ctrl);
+       term_mouse(b, MA_CLICK, x, y, shift, ctrl, alt);
        return;
     }
 
@@ -1354,7 +1354,7 @@ static void click(Mouse_Button b, int x, int y, int shift, int ctrl)
        lastact = MA_CLICK;
     }
     if (lastact != MA_NOTHING)
-       term_mouse(b, lastact, x, y, shift, ctrl);
+       term_mouse(b, lastact, x, y, shift, ctrl, alt);
     lasttime = thistime;
 }
 
@@ -1385,6 +1385,19 @@ static void show_mouseptr(int show)
     cursor_visible = show;
 }
 
+static int is_alt_pressed(void)
+{
+    BYTE keystate[256];
+    int r = GetKeyboardState(keystate);
+    if (!r)
+       return FALSE;
+    if (keystate[VK_MENU] & 0x80)
+       return TRUE;
+    if (keystate[VK_RMENU] & 0x80)
+       return TRUE;
+    return FALSE;
+}
+
 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
                                WPARAM wParam, LPARAM lParam)
 {
@@ -1730,14 +1743,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
 
                if (send_raw_mouse) {
                    /* send a mouse-down followed by a mouse up */
+                   
                    term_mouse(b,
                               MA_CLICK,
                               TO_CHR_X(X_POS(lParam)),
                               TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
-                              wParam & MK_CONTROL);
+                              wParam & MK_CONTROL, is_alt_pressed());
                    term_mouse(b, MA_RELEASE, TO_CHR_X(X_POS(lParam)),
                               TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
-                              wParam & MK_CONTROL);
+                              wParam & MK_CONTROL, is_alt_pressed());
                } else {
                    /* trigger a scroll */
                    term_scroll(0,
@@ -1754,6 +1768,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
       case WM_RBUTTONUP:
        {
            int button, press;
+
            switch (message) {
              case WM_LBUTTONDOWN:
                button = MBT_LEFT;
@@ -1786,13 +1801,14 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
            if (press) {
                click(button,
                      TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
-                     wParam & MK_SHIFT, wParam & MK_CONTROL);
+                     wParam & MK_SHIFT, wParam & MK_CONTROL,
+                     is_alt_pressed());
                SetCapture(hwnd);
            } else {
                term_mouse(button, MA_RELEASE,
                           TO_CHR_X(X_POS(lParam)),
                           TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
-                          wParam & MK_CONTROL);
+                          wParam & MK_CONTROL, is_alt_pressed());
                ReleaseCapture();
            }
        }
@@ -1815,7 +1831,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
                b = MBT_RIGHT;
            term_mouse(b, MA_DRAG, TO_CHR_X(X_POS(lParam)),
                       TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
-                      wParam & MK_CONTROL);
+                      wParam & MK_CONTROL, is_alt_pressed());
        }
        return 0;
       case WM_NCMOUSEMOVE: