home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / emxtutor.zip / emxsrcd1.zip / emx / src / pmgdb / pmtxt.cc < prev    next >
C/C++ Source or Header  |  1996-09-07  |  55KB  |  2,007 lines

  1. /* pmtxt.cc
  2.    Copyright (c) 1996 Eberhard Mattes
  3.  
  4. This file is part of pmgdb.
  5.  
  6. pmgdb is free software; you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation; either version 2, or (at your option)
  9. any later version.
  10.  
  11. pmgdb is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. GNU General Public License for more details.
  15.  
  16. You should have received a copy of the GNU General Public License
  17. along with pmgdb; see the file COPYING.  If not, write to
  18. the Free Software Foundation, 59 Temple Place - Suite 330,
  19. Boston, MA 02111-1307, USA.  */
  20.  
  21.  
  22. #define INCL_DOS
  23. #define INCL_WIN
  24. #define INCL_GPI
  25. #include <os2.h>
  26. #include <stdlib.h>
  27. #include <string.h>
  28. #include "string.h"
  29. #include "pmapp.h"
  30. #include "pmframe.h"
  31. #include "pmtxt.h"
  32.  
  33. #define UWM_PMTHREAD            (WM_USER+200)
  34.  
  35. #define PMTHREAD_SBAR_X         0x01
  36. #define PMTHREAD_SBAR_Y         0x02
  37.  
  38.  
  39. #define LF_UNDERLINE            0x01
  40.  
  41. #define EOL_COLUMN      (pmtxt::max_line_len + 1)
  42.  
  43. #define MAX(a,b) ((a) > (b) ? (a) : (b))
  44.  
  45. struct pmtxt_line_attr
  46. {
  47.   // TODO: x
  48.   struct pmtxt_line_attr *next;
  49.   int column;
  50.   pmtxt::element el;
  51.   pmtxt_attr attr;
  52. };
  53.  
  54. struct pmtxt_line
  55. {
  56.   char *str;
  57.   pmtxt_line_attr *attr;
  58.   int len, alloc;
  59.   // TODO: Add flag bits (HAS_TABS, OUTPUT_PENDING, ...)
  60.   unsigned char flags;
  61. };
  62.  
  63.  
  64. pmtxt::pmtxt (pmapp *in_app, unsigned in_id, ULONG frame_flags,
  65.               const SWP *pswp, const char *fontnamesize)
  66.   : pmframe (in_app)
  67. {
  68.   _fmutex_checked_create (&mutex, 0);
  69.  
  70.   track = true;
  71.   default_font = true;
  72.   init_fontnamesize = fontnamesize;
  73.   initializing_font = false;
  74.  
  75.   aptl_line = new POINTL[max_line_len+1];
  76.  
  77.   pmthread_pending = 0;
  78.   x_off = 0; y_off = 0;
  79.   x_pos = 0; max_x_pos = 0;
  80.   output_stopped = false; output_pending = false;
  81.   cache_line = -1;
  82.  
  83.   lines_used = 0; lines_alloc = 0;
  84.   lines_vector = NULL;
  85.  
  86.   tab_x = NULL; tab_count = 0; tab_x_size = 0;
  87.   tab_repaint = false;
  88.  
  89.   default_attr.fg_color = CLR_BLACK;
  90.   default_attr.bg_color = CLR_WHITE;
  91.  
  92.   create ("pmtxt.client", in_id, frame_flags, pswp);
  93. }
  94.  
  95.  
  96. pmtxt::~pmtxt ()
  97. {
  98.   delete_all ();
  99.   delete[] aptl_line;
  100.   delete[] tab_x;
  101.   // TODO: mutex
  102. }
  103.  
  104.  
  105. void pmtxt::delete_all ()
  106. {
  107.   _fmutex_checked_request (&mutex, _FMR_IGNINT);
  108.  
  109.   output_pending = lines_used != 0;
  110.  
  111.   for (int i = 0; i < lines_alloc; ++i)
  112.     free_line (i);
  113.   delete[] lines_vector;
  114.  
  115.   x_off = 0; y_off = 0;
  116.   x_pos = 0; max_x_pos = 0;
  117.   cache_line = -1;
  118.  
  119.   lines_used = 0; lines_alloc = 0;
  120.   lines_vector = NULL;
  121.  
  122.   tab_count = 0;
  123.  
  124.   unlock ();
  125. }
  126.  
  127.  
  128. void pmtxt::free_line (int line)
  129. {
  130.   delete[] lines_vector[line].str;
  131.   delete_attr (lines_vector[line].attr);
  132. }
  133.  
  134.  
  135. void pmtxt::init_line (int line)
  136. {
  137.   lines_vector[line].str = NULL;
  138.   lines_vector[line].attr = NULL;
  139.   lines_vector[line].len = 0;
  140.   lines_vector[line].alloc = 0;
  141.   lines_vector[line].flags = 0;
  142. }
  143.  
  144.  
  145. void pmtxt::delete_attr (pmtxt_line_attr *a)
  146. {
  147.   pmtxt_line_attr *next;
  148.  
  149.   while (a != NULL)
  150.     {
  151.       next = a->next;
  152.       delete a;
  153.       a = next;
  154.     }
  155. }
  156.  
  157.  
  158. void pmtxt::more_lines (int new_count)
  159. {
  160.   int n = new_count < 1024 ? new_count + 128 : new_count + 1024;
  161.   pmtxt_line *new_vec = new pmtxt_line[n];
  162.   for (int i = 0; i < lines_alloc; ++i)
  163.     new_vec[i] = lines_vector[i];
  164.   delete[] lines_vector;
  165.   lines_vector = new_vec;
  166.   for (int i = lines_alloc; i < n; ++i)
  167.     init_line (i);
  168.   lines_alloc = n;
  169. }
  170.  
  171.  
  172. static inline void query_hps_attr (HPS hps, pmtxt_attr &cache)
  173. {
  174.   cache.fg_color = GpiQueryColor (hps);
  175.   cache.bg_color = GpiQueryBackColor (hps);
  176. }
  177.  
  178.  
  179. static inline void set_hps_attr (HPS hps, pmtxt_attr &cache,
  180.                                  const pmtxt_attr &attr)
  181. {
  182.   if (attr.fg_color != cache.fg_color)
  183.     {
  184.       GpiSetColor (hps, attr.fg_color);
  185.       cache.fg_color = attr.fg_color;
  186.     }
  187.   if (attr.bg_color != cache.bg_color)
  188.     {
  189.       GpiSetBackColor (hps, attr.bg_color);
  190.       cache.bg_color = attr.bg_color;
  191.     }
  192. }
  193.  
  194.  
  195. static inline bool eq_attr (const pmtxt_attr &a1, const pmtxt_attr &a2)
  196. {
  197.   return (a1.fg_color == a2.fg_color
  198.           && a1.bg_color == a2.bg_color);
  199. }
  200.  
  201.  
  202. MRESULT pmtxt::wm_create (HWND hwnd, ULONG, MPARAM, MPARAM)
  203. {
  204.   hwndHbar = WinWindowFromID (get_hwndFrame (), FID_HORZSCROLL);
  205.   hwndVbar = WinWindowFromID (get_hwndFrame (), FID_VERTSCROLL);
  206.  
  207.   // Set the initial font
  208.  
  209.   if (init_fontnamesize)
  210.     {
  211.       initializing_font = true;
  212.       WinSetPresParam (hwnd, PP_FONTNAMESIZE, strlen (init_fontnamesize) + 1,
  213.                        (PVOID)init_fontnamesize);
  214.       initializing_font = false;
  215.     }
  216.  
  217.   // Create a presentation space handle for asynchronous painting (and
  218.   // for retrieving the font metrics).
  219.  
  220.   create_hpsClient ();
  221.  
  222.   // Get the default line (rule) width
  223.   // TODO: Does this really yield the width in pixels?
  224.   LINEBUNDLE lbundle;
  225.   if (GpiQueryAttrs (hpsClient, PRIM_LINE, LBB_WIDTH, &lbundle)
  226.       == GPI_ALTERROR)
  227.     rule_width = 1;
  228.   else
  229.     rule_width = FIXEDINT (lbundle.fxWidth);
  230.  
  231.   // Retrieve the current attributes of hpsClient.
  232.  
  233.   query_hps_attr (hpsClient, hpsClient_attr);
  234.  
  235.   return FALSE;
  236. }
  237.  
  238.  
  239. // Retrieve the font metrics of the default font.
  240.  
  241. void pmtxt::get_fontmetrics ()
  242. {
  243.   FONTMETRICS fm;
  244.   GpiQueryFontMetrics (hpsClient, (LONG)sizeof (fm), &fm);
  245.   cxChar = (int)fm.lMaxCharInc;
  246.   cyChar = (int)fm.lMaxBaselineExt;
  247.   cyDesc = (int)fm.lMaxDescender;
  248.  
  249.   fixed_pitch = (fm.fsType & FM_TYPE_FIXED);
  250. }
  251.  
  252.  
  253. void pmtxt::create_hpsClient ()
  254. {
  255.   hpsClient = WinGetPS (get_hwndClient ());
  256.   setup_ps (hpsClient);
  257.   get_fontmetrics ();
  258. }
  259.  
  260.  
  261. void pmtxt::setup_ps (HPS hps)
  262. {
  263.   GpiSetBackMix (hps, BM_OVERPAINT);
  264.   set_font (hps);
  265. }
  266.  
  267.  
  268. void pmtxt::set_font (HPS hps)
  269. {
  270.   if (!default_font)
  271.     {
  272.       GpiCreateLogFont (hps, NULL, 1, &fattrs);
  273.       GpiSetCharSet (hps, 1);
  274.     }
  275. }
  276.  
  277.  
  278. void pmtxt::set_font (const pmtxt &s)
  279. {
  280.   SIZEF charbox;
  281.   char fontnamesize[FACESIZE+10];
  282.  
  283.   // TODO: dropped font (and don't forget to update the constructor
  284.   // of source_window which depends on the current behavior)
  285.   if (!s.default_font && GpiQueryCharBox (s.hpsClient, &charbox))
  286.     {
  287.       _fmutex_checked_request (&mutex, _FMR_IGNINT);
  288.       fattrs = s.fattrs;
  289.       default_font = false;
  290.       GpiCreateLogFont (hpsClient, NULL, 1, &fattrs);
  291.       GpiSetCharSet (hpsClient, 1);
  292.       GpiSetCharBox (hpsClient, &charbox);
  293.       get_fontmetrics ();
  294.       cache_line = -1;
  295.       unlock ();
  296.       clear_tabs ();
  297.       change_size ();
  298.       font_changed ();
  299.       repaint ();
  300.     }
  301.   else if (s.default_font
  302.            && WinQueryPresParam (s.get_hwndClient(), PP_FONTNAMESIZE, 0,
  303.                                  NULL, sizeof (fontnamesize), fontnamesize,
  304.                                  0) != 0)
  305.     WinSetPresParam (get_hwndClient (), PP_FONTNAMESIZE,
  306.                      strlen (fontnamesize) + 1, fontnamesize);
  307. }
  308.  
  309.  
  310. MRESULT pmtxt::wm_paint (HWND hwnd, ULONG, MPARAM, MPARAM)
  311. {
  312.   pmtxt_attr hps_attr;
  313.  
  314.   _fmutex_checked_request (&mutex, _FMR_IGNINT);
  315.   HPS hps = WinBeginPaint (hwnd, NULLHANDLE, NULL);
  316.   setup_ps (hps);
  317.   query_hps_attr (hps, hps_attr);
  318.   painting (true);
  319.   GpiErase (hps);
  320.  
  321.   // TODO: examine update region to find lines to be displayed
  322.  
  323.   int line = y_off;
  324.  
  325.   // Compute the number of lines to display
  326.   int count = window_lines;
  327.   if (count > lines_used - line)
  328.     count = lines_used - line;
  329.  
  330.   tab_repaint = false;
  331.   for (int i = 0; i < count; ++i)
  332.     paint (hps, hps_attr, line++, 0, false);
  333.   painting (false);
  334.   WinEndPaint (hps);
  335.   if (tab_repaint)
  336.     repaint ();
  337.   else
  338.     pmthread_pending |= PMTHREAD_SBAR_X | PMTHREAD_SBAR_Y;
  339.   unlock ();
  340.   return 0;
  341. }
  342.  
  343.  
  344. // Side effect: tab_repaint
  345.  
  346. void pmtxt::paint (HPS hps, pmtxt_attr &cur_attr, int line, int column,
  347.                    bool paint_eol)
  348. {
  349.   int end;
  350.   POINTL ptl;
  351.  
  352.   if (!get_bbox_pos (hps, &ptl, line, column))
  353.     return;
  354.   if (ptl.y < 0 || ptl.y >= cyClient)
  355.     return;
  356.   ptl.y += cyDesc;
  357.  
  358.   const pmtxt_line *pline = &lines_vector[line];
  359.   char *p = pline->str + column;
  360.  
  361.   if (pline->attr == NULL)
  362.     {
  363.       // Optimization
  364.       set_hps_attr (hps, cur_attr, default_attr);
  365.       GpiCharStringAt (hps, &ptl, pline->len - column, (PCH)p);
  366.       if (paint_eol)
  367.         {
  368.           // TODO
  369.           pmtxt_line_attr la;
  370.           la.el = E_FILL;
  371.           la.attr = default_attr;
  372.           paint (hps, cur_attr, &la, (char *)NULL, -1);
  373.         }
  374.     }
  375.   else
  376.     {
  377.       tab_index = 0;
  378.       const pmtxt_line_attr *a = pline->attr;
  379.       while (a->column < column)
  380.         {
  381.           if (a->next == NULL || a->next->column > column)
  382.             break;
  383.           if (a->el == E_TAB && a->next != NULL
  384.               && a->next->column != EOL_COLUMN)
  385.             tab_index += a->next->column - a->column;
  386.           a = a->next;
  387.         }
  388.  
  389.       if (column < a->column)
  390.         {
  391.           set_hps_attr (hps, cur_attr, default_attr);
  392.           end = a->column == EOL_COLUMN ? pline->len : a->column;
  393.           GpiCharStringAt (hps, &ptl, end - column, (PCH)p);
  394.           p += end - column;
  395.           column = end;
  396.         }
  397.       else
  398.         GpiMove (hps, &ptl);
  399.  
  400.       while (a != NULL && a->column != EOL_COLUMN)
  401.         {
  402.           if (a->next == NULL || a->next->column == EOL_COLUMN)
  403.             end = pline->len;
  404.           else
  405.             end = a->next->column;
  406.           if (column < end)
  407.             paint (hps, cur_attr, a, p, end - column);
  408.           p += end - column; column = end;
  409.           a = a->next;
  410.         }
  411.       // TODO: Don't always clear to the end of the line (we need
  412.       // to remember the previous max x
  413.       if (a != NULL)            // a->column == EOL_COLUMN
  414.         paint (hps, cur_attr, a, p, -1);
  415.       else if (paint_eol)
  416.         {
  417.           // TODO
  418.           pmtxt_line_attr la;
  419.           la.el = E_FILL;
  420.           la.attr = default_attr;
  421.           paint (hps, cur_attr, &la, (char *)NULL, -1);
  422.         }
  423.     }
  424.  
  425.   POINTL ptl_end;
  426.   GpiQueryCurrentPosition (hps, &ptl_end);
  427.   int x = (ptl_end.x - (-x_off * cxChar) + cxChar - 1) / cxChar;
  428.   if (x > max_x_pos)
  429.     max_x_pos = x;
  430.  
  431.   if (pline->flags & LF_UNDERLINE)
  432.     {
  433.       RECTL rcli;
  434.       rcli.xLeft = 0; rcli.xRight = cxClient;
  435.       rcli.yBottom = ptl.y - cyDesc;
  436.       rcli.yTop = rcli.yBottom + 1;
  437.       WinFillRect (hps, &rcli, default_attr.fg_color);
  438.     }
  439. }
  440.  
  441.  
  442. void pmtxt::paint (HPS hps, pmtxt_attr &cur_attr, const pmtxt_line_attr *pla,
  443.                    const char *p, int len)
  444. {
  445.   POINTL ptl;
  446.   RECTL rcli;
  447.   int x, add, i;
  448.  
  449.   switch (pla->el)
  450.     {
  451.     case E_FILL:
  452.       // TODO: Use GpiBox
  453.       GpiQueryCurrentPosition (hps, &ptl);
  454.       rcli.xLeft = ptl.x;
  455.       // TODO: Width specified in string (pixels)
  456.       rcli.xRight = len == -1 ? cxClient : rcli.xLeft + len * cxChar;
  457.       rcli.yBottom = ptl.y - cyDesc;
  458.       rcli.yTop = rcli.yBottom + cyChar;
  459.       WinFillRect (hps, &rcli, pla->attr.bg_color);
  460.       if (len != -1)
  461.         {
  462.           ptl.x = rcli.xRight;
  463.           GpiMove (hps, &ptl);
  464.         }
  465.       break;
  466.  
  467.     case E_TEXT:
  468.       set_hps_attr (hps, cur_attr, pla->attr);
  469.       GpiCharString (hps, len, (PCH)p);
  470.       break;
  471.  
  472.     case E_VRULE:
  473.       // TODO: Use GpiBox
  474.       GpiQueryCurrentPosition (hps, &ptl);
  475.       rcli.xLeft = ptl.x;
  476.       rcli.xRight = rcli.xLeft + rule_width;
  477.       rcli.yBottom = ptl.y - cyDesc;
  478.       rcli.yTop = rcli.yBottom + cyChar;
  479.       // Note: CLR_DEFAULT does not work (background vs. foreground)
  480.       WinFillRect (hps, &rcli, pla->attr.fg_color);
  481.       ptl.x += rule_width;
  482.       GpiMove (hps, &ptl);
  483.       break;
  484.  
  485.     case E_TAB:
  486.       // TODO: Use GpiBox
  487.       GpiQueryCurrentPosition (hps, &ptl);
  488.       x = ptl.x + x_off * cxChar; add = 0;
  489.       while (len != 0)
  490.         {
  491.           if (x > tab_x[tab_index])
  492.             {
  493.               tab_repaint = true;
  494.               add += x - tab_x[tab_index];
  495.               for (i = tab_index; i < tab_count; ++i)
  496.                 tab_x[i] += add;
  497.             }
  498.           x = tab_x[tab_index];
  499.           ++tab_index; --len;
  500.         }
  501.  
  502.       rcli.xLeft = ptl.x;
  503.       rcli.xRight = x - x_off * cxChar;
  504.       rcli.yBottom = ptl.y - cyDesc;
  505.       rcli.yTop = rcli.yBottom + cyChar;
  506.       WinFillRect (hps, &rcli, pla->attr.bg_color);
  507.       ptl.x = rcli.xRight;
  508.       GpiMove (hps, &ptl);
  509.       break;
  510.  
  511.     default:
  512.       break;
  513.     }
  514. }
  515.  
  516.  
  517. void pmtxt::sync ()
  518. {
  519.   if (output_pending)
  520.     repaint ();
  521. }
  522.  
  523.  
  524. // TODO: Cache current scrollbar status to avoid WinSendMsg if nothing changed
  525.  
  526. static void check_size (int new_value, int target, HWND sbar, int *off)
  527. {
  528.   int m = target - new_value;
  529.   if (m > 0)
  530.     {
  531.       WinEnableWindow (sbar, TRUE);
  532.       if (*off > m)
  533.         *off = m;
  534.       WinSendMsg (sbar, SBM_SETSCROLLBAR,
  535.                   MPFROMSHORT (*off), MPFROM2SHORT (0, MAX (0, (SHORT)m)));
  536.       WinSendMsg (sbar, SBM_SETTHUMBSIZE,
  537.                   MPFROM2SHORT ((USHORT)new_value, (USHORT)target), 0);
  538.     }
  539.   else
  540.     {
  541.       WinEnableWindow (sbar, FALSE);
  542.       *off = 0;
  543.     }
  544. }
  545.  
  546.  
  547. void pmtxt::check_x_size ()
  548. {
  549.   if (output_stopped)
  550.     pmthread_pending |= PMTHREAD_SBAR_X;
  551.   else if (same_tid ())
  552.     {
  553.       // Note that check_y_size() might be called recursively because
  554.       // check_size() calls WinSendMsg
  555.       unsigned rc = _fmutex_request (&mutex, _FMR_IGNINT | _FMR_NOWAIT);
  556.       if (rc != 0)
  557.         {
  558.           pmthread_pending |= PMTHREAD_SBAR_X;
  559.           // Avoid time window
  560.           rc = _fmutex_request (&mutex, _FMR_IGNINT | _FMR_NOWAIT);
  561.         }
  562.       if (rc == 0)
  563.         {
  564.           check_size (window_columns, max_x_pos, hwndHbar, &x_off);
  565.           unlock ();
  566.         }
  567.     }
  568.   else
  569.     pmthread_request (PMTHREAD_SBAR_X);
  570. }
  571.  
  572.  
  573. void pmtxt::check_y_size ()
  574. {
  575.   if (output_stopped)
  576.     pmthread_pending |= PMTHREAD_SBAR_Y;
  577.   else if (same_tid ())
  578.     {
  579.       // Note that check_y_size() might be called recursively because
  580.       // check_size() calls WinSendMsg
  581.       unsigned rc = _fmutex_request (&mutex, _FMR_IGNINT | _FMR_NOWAIT);
  582.       if (rc != 0)
  583.         {
  584.           pmthread_pending |= PMTHREAD_SBAR_Y;
  585.           // Avoid time window
  586.           rc = _fmutex_request (&mutex, _FMR_IGNINT | _FMR_NOWAIT);
  587.         }
  588.       if (rc == 0)
  589.         {
  590.           check_size (window_lines, lines_used, hwndVbar, &y_off);
  591.           unlock ();
  592.         }
  593.     }
  594.   else
  595.     pmthread_request (PMTHREAD_SBAR_Y);
  596. }
  597.  
  598.  
  599. void pmtxt::painting (bool)
  600. {
  601. }
  602.  
  603.  
  604. void pmtxt::button_event (int, int, int, int, int)
  605. {
  606. }
  607.  
  608.  
  609. MRESULT pmtxt::button_event (HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2,
  610.                              int button, int clicks)
  611. {
  612.   if (WinQueryFocus (HWND_DESKTOP) == hwnd)
  613.     {
  614.       int line, column, tab;
  615.       _fmutex_checked_request (&mutex, _FMR_IGNINT);
  616.       line_column_from_x_y (hpsClient, &line, &column, &tab,
  617.                             (USHORT)SHORT1FROMMP (mp1),
  618.                             (USHORT)SHORT2FROMMP (mp1));
  619.       unlock ();
  620.       button_event (line, column, tab, button, clicks);
  621.       return (MRESULT)TRUE;
  622.     }
  623.  
  624.   // Continue processing -- make window active.
  625.   return WinDefWindowProc (hwnd, msg, mp1, mp2);
  626. }
  627.  
  628.  
  629. MRESULT pmtxt::wm_size (HWND, ULONG, MPARAM, MPARAM mp2)
  630. {
  631.   cxClient = SHORT1FROMMP (mp2);
  632.   cyClient = SHORT2FROMMP (mp2);
  633.   change_size ();
  634.   return 0;
  635. }
  636.  
  637.  
  638. void pmtxt::change_size ()
  639. {
  640.   window_lines = (int)(cyClient / cyChar);
  641.   window_columns = (int)(cxClient / cxChar);
  642.   if (cyClient != window_lines * cyChar && !get_minimized ())
  643.     {
  644.       // I don't like calling WinSetWindowPos from WM_SIZE processing;
  645.       // however, there seems to be no other way which works with
  646.       // FCF_SHELLPOSITION (WM_ADJUSTWINDOWPOS does not work)
  647.       SWP swp;
  648.       WinQueryWindowPos (get_hwndClient (), &swp);
  649.       POINTL ptl;
  650.       ptl.x = swp.x;
  651.       ptl.y = swp.y + cyClient;
  652.       WinMapWindowPoints (get_hwndFrame (), HWND_DESKTOP, &ptl, 1);
  653.       RECTL rcl;
  654.       rcl.xLeft = ptl.x;
  655.       rcl.xRight = rcl.xLeft + cxClient;
  656.       rcl.yTop = ptl.y;
  657.       rcl.yBottom = rcl.yTop - window_lines * cyChar;
  658.       WinCalcFrameRect (get_hwndFrame (), &rcl, FALSE);
  659.       // This causes recursion on WM_SIZE
  660.       WinSetWindowPos (get_hwndFrame (), HWND_TOP, rcl.xLeft, rcl.yBottom,
  661.                        rcl.xRight - rcl.xLeft, rcl.yTop - rcl.yBottom,
  662.                        SWP_SIZE | SWP_MOVE);
  663.     }
  664.   check_x_size ();
  665.   check_y_size ();
  666. }
  667.  
  668.  
  669. MRESULT pmtxt::wm_button (HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
  670. {
  671.   switch (msg)
  672.     {
  673.     case WM_BUTTON1DOWN:
  674.       return button_event (hwnd, msg, mp1, mp2, 1, 0);
  675.  
  676.     case WM_BUTTON2DOWN:
  677.       return button_event (hwnd, msg, mp1, mp2, 2, 0);
  678.  
  679.     case WM_BUTTON3DOWN:
  680.       return button_event (hwnd, msg, mp1, mp2, 3, 0);
  681.  
  682.     case WM_BUTTON1CLICK:
  683.       return button_event (hwnd, msg, mp1, mp2, 1, 1);
  684.  
  685.     case WM_BUTTON2CLICK:
  686.       return button_event (hwnd, msg, mp1, mp2, 2, 1);
  687.  
  688.     case WM_BUTTON3CLICK:
  689.       return button_event (hwnd, msg, mp1, mp2, 3, 1);
  690.  
  691.     case WM_BUTTON1DBLCLK:
  692.       return button_event (hwnd, msg, mp1, mp2, 1, 2);
  693.  
  694.     case WM_BUTTON2DBLCLK:
  695.       return button_event (hwnd, msg, mp1, mp2, 2, 2);
  696.  
  697.     case WM_BUTTON3DBLCLK:
  698.       return button_event (hwnd, msg, mp1, mp2, 3, 2);
  699.     }
  700.   return 0;
  701. }
  702.  
  703.  
  704. MRESULT pmtxt::wm_vscroll (HWND, ULONG, MPARAM, MPARAM mp2)
  705. {
  706.   int n, m, diff;
  707.  
  708.   m = lines_used - window_lines;
  709.   switch (SHORT2FROMMP (mp2))
  710.     {
  711.     case SB_LINEUP:
  712.       n = y_off - 1; break;
  713.     case SB_LINEDOWN:
  714.       n = y_off + 1; break;
  715.     case SB_PAGEUP:
  716.       n = y_off - window_lines; break;
  717.     case SB_PAGEDOWN:
  718.       n = y_off + window_lines; break;
  719.     case SB_SLIDERTRACK:
  720.       disable_output ();
  721.       if (!track)
  722.         return 0;
  723.       n = SHORT1FROMMP (mp2);
  724.       break;
  725.     case SB_SLIDERPOSITION:
  726.       enable_output ();
  727.       n = SHORT1FROMMP (mp2);
  728.       break;
  729.     default:
  730.       return 0;
  731.     }
  732.   if (n > m) n = m;
  733.   if (n < 0) n = 0;
  734.   diff = n - y_off;
  735.   if (diff > 0)
  736.     {
  737.       y_off = n;
  738.       scroll_up (diff, true);
  739.     }
  740.   else if (diff < 0)
  741.     {
  742.       y_off = n;
  743.       scroll_down (-diff);
  744.     }
  745.   if (SHORT2FROMMP (mp2) != SB_SLIDERTRACK)
  746.     WinSendMsg (hwndVbar, SBM_SETSCROLLBAR, MPFROMSHORT (y_off),
  747.                 MPFROM2SHORT (0, (SHORT)m));
  748.   return 0;
  749. }
  750.  
  751.  
  752. MRESULT pmtxt::wm_hscroll (HWND, ULONG, MPARAM, MPARAM mp2)
  753. {
  754.   int n, m;
  755.  
  756.   m = max_x_pos - window_columns;
  757.   switch (SHORT2FROMMP (mp2))
  758.     {
  759.     case SB_LINELEFT:
  760.       n = x_off - 1; break;
  761.     case SB_LINERIGHT:
  762.       n = x_off + 1; break;
  763.     case SB_PAGELEFT:
  764.       n = x_off - window_columns; break;
  765.     case SB_PAGERIGHT:
  766.       n = x_off + window_columns; break;
  767.     case SB_SLIDERTRACK:
  768.       disable_output ();
  769.       if (!track)
  770.         return 0;
  771.       n = SHORT1FROMMP (mp2);
  772.       break;
  773.     case SB_SLIDERPOSITION:
  774.       enable_output ();
  775.       n = SHORT1FROMMP (mp2);
  776.       break;
  777.     default:
  778.       return 0;
  779.     }
  780.   if (n > m) n = m;
  781.   if (n < 0) n = 0;
  782.   if (n != x_off)
  783.     {
  784.       // TODO: use GpiBitBlt
  785.       x_pos += (x_off - n) * cxChar;
  786.       x_off = n;
  787.       repaint ();
  788.     }
  789.   if (SHORT2FROMMP (mp2) != SB_SLIDERTRACK)
  790.     WinSendMsg (hwndHbar, SBM_SETSCROLLBAR, MPFROMSHORT (x_off),
  791.                 MPFROM2SHORT (0, (SHORT)m));
  792.   return 0;
  793. }
  794.  
  795.  
  796. MRESULT pmtxt::wm_presparamchanged (HWND hwnd, ULONG msg,
  797.                                     MPARAM mp1, MPARAM mp2)
  798. {
  799.   parent::wm_presparamchanged (hwnd, msg, mp1, mp2);
  800.   if (LONGFROMMP (mp1) == PP_FONTNAMESIZE && !initializing_font)
  801.     {
  802.       _fmutex_checked_request (&mutex, _FMR_IGNINT);
  803.       default_font = true;
  804.       WinReleasePS (hpsClient);
  805.       create_hpsClient ();
  806.       query_hps_attr (hpsClient, hpsClient_attr);
  807.       cache_line = -1;
  808.       unlock ();
  809.       clear_tabs ();
  810.       change_size ();
  811.       repaint ();
  812.     }
  813.   return 0;
  814. }
  815.  
  816.  
  817. void pmtxt::font_changed ()
  818. {
  819. }
  820.  
  821.  
  822. bool pmtxt::font_dialog ()
  823. {
  824.   FONTMETRICS fm;
  825.   FONTDLG fd;
  826.   UCHAR family_name[FACESIZE+1];
  827.   LONG pel_per_inch;
  828.   SIZEF charbox;
  829.  
  830.   _fmutex_checked_request (&mutex, _FMR_IGNINT);
  831.   HDC hdc = GpiQueryDevice (hpsClient);
  832.   bool ok = (GpiQueryFontMetrics (hpsClient, (LONG)sizeof (fm), &fm)
  833.              && DevQueryCaps (hdc, CAPS_VERTICAL_FONT_RES, 1, &pel_per_inch)
  834.              && GpiQueryCharBox (hpsClient, &charbox)
  835.              && pel_per_inch != 0);
  836.   unlock ();
  837.   if (!ok)
  838.     {
  839.       WinAlarm (HWND_DESKTOP, WA_ERROR);
  840.       return false;
  841.     }
  842.  
  843.   memcpy (family_name, fm.szFamilyname, FACESIZE);
  844.   family_name[FACESIZE] = 0;
  845.  
  846.   memset (&fd, 0, sizeof (fd));
  847.   fd.cbSize = sizeof (fd);
  848.   fd.hpsScreen = hpsClient;
  849.   fd.hpsPrinter = NULLHANDLE;
  850.   fd.pszTitle = (PSZ)"Font";
  851.   fd.pszPreview = NULL;
  852.   fd.pszPtSizeList = NULL;
  853.   fd.pfnDlgProc = NULL;
  854.   fd.pszFamilyname = family_name;
  855.   if (fixed_pitch)
  856.     fd.fxPointSize = MAKEFIXED (fm.sNominalPointSize, 0) / 10;
  857.   else
  858.     fd.fxPointSize = (72 * charbox.cy + pel_per_inch / 2) / pel_per_inch;
  859.   fd.fl = (FNTS_HELPBUTTON | FNTS_RESETBUTTON | FNTS_CENTER
  860.            | FNTS_INITFROMFATTRS);
  861.   fd.flFlags = 0;
  862.   fd.flType = fm.fsType;
  863.   fd.flTypeMask = 0;
  864.   fd.flStyle = 0;
  865.   fd.flStyleMask = 0;
  866.   fd.clrFore = CLR_BLACK;
  867.   fd.clrBack = CLR_WHITE;
  868.   fd.ulUser = 0;
  869.   fd.lEmHeight = fm.lEmHeight;
  870.   fd.lXHeight = fm.lXHeight;
  871.   fd.lExternalLeading = fm.lExternalLeading;
  872.   fd.fAttrs.usRecordLength = sizeof (FATTRS);
  873.   fd.fAttrs.fsSelection = fm.fsSelection;
  874.   fd.fAttrs.lMatch = fm.lMatch;
  875.   memcpy (fd.fAttrs.szFacename, fm.szFacename, FACESIZE);
  876.   fd.fAttrs.idRegistry = fm.idRegistry;
  877.   fd.fAttrs.usCodePage = fm.usCodePage;
  878.   fd.fAttrs.lMaxBaselineExt = fm.lMaxBaselineExt;
  879.   fd.fAttrs.lAveCharWidth = fm.lAveCharWidth;
  880.   fd.fAttrs.fsType = fm.fsType;
  881.   fd.fAttrs.fsFontUse = 0;      // TODO?
  882.   fd.sNominalPointSize = fm.sNominalPointSize;
  883.   fd.usWeight = fm.usWeightClass;
  884.   fd.usWidth = fm.usWidthClass;
  885.   fd.usFamilyBufLen = FACESIZE;
  886.   if (WinFontDlg (HWND_DESKTOP, get_hwndClient (), &fd) == NULLHANDLE)
  887.     WinAlarm (HWND_DESKTOP, WA_ERROR);
  888.   else if (fd.lReturn == DID_OK)
  889.     {
  890.       fd.fAttrs.fsFontUse = FATTR_FONTUSE_NOMIX;
  891.       charbox.cx = charbox.cy = (fd.fxPointSize * pel_per_inch + 72/2)  / 72;
  892.       _fmutex_checked_request (&mutex, _FMR_IGNINT);
  893.       if (GpiCreateLogFont (hpsClient, NULL, 1, &fd.fAttrs) != FONT_MATCH
  894.           || !GpiSetCharSet (hpsClient, 1)
  895.           || !GpiSetCharBox (hpsClient, &charbox))
  896.         {
  897.           unlock ();
  898.           WinAlarm (HWND_DESKTOP, WA_ERROR);
  899.         }
  900.       else
  901.         {
  902.           fattrs = fd.fAttrs;
  903.           default_font = false;
  904.           get_fontmetrics ();
  905.           cache_line = -1;
  906.           unlock ();
  907.           clear_tabs ();
  908.           change_size ();
  909.           font_changed ();
  910.           repaint ();
  911.           return true;
  912.         }
  913.     }
  914.   return false;
  915. }
  916.  
  917.  
  918. void pmtxt::unlock ()
  919. {
  920.   _fmutex_checked_release (&mutex);
  921.   pmthread_perform ();
  922. }
  923.  
  924.  
  925. void pmtxt::pmthread_request (unsigned char bits)
  926. {
  927.   if (bits & ~pmthread_pending)
  928.     {
  929.       pmthread_pending |= bits;
  930.       WinPostMsg (get_hwndClient (), UWM_PMTHREAD, 0, 0);
  931.     }
  932. }
  933.  
  934.  
  935. void pmtxt::pmthread_perform ()
  936. {
  937.   if (same_tid ())
  938.     {
  939.       // TODO: Atomic
  940.       unsigned char x = pmthread_pending;
  941.       pmthread_pending = 0;
  942.       if (x & PMTHREAD_SBAR_X)
  943.         check_x_size ();
  944.       if (x & PMTHREAD_SBAR_Y)
  945.         check_y_size ();
  946.     }
  947.   else if (pmthread_pending != 0)
  948.     WinPostMsg (get_hwndClient (), UWM_PMTHREAD, 0, 0);
  949. }
  950.  
  951.  
  952. bool pmtxt::wm_user (HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
  953. {
  954.   if (parent::wm_user (hwnd, msg, mp1, mp2))
  955.     return true;
  956.   switch (msg)
  957.     {
  958.     case UWM_PMTHREAD:
  959.       pmthread_perform ();
  960.       return true;
  961.  
  962.     default:
  963.       return false;
  964.     }
  965. }
  966.  
  967.  
  968. // Fill-in TRACKINFO structure for resizing or moving a window.
  969. // This function is called from the frame window procedure.
  970.  
  971. MRESULT pmtxt::wm_querytrackinfo (HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
  972. {
  973.   // First call the normal frame window procedure to set up the
  974.   // structure.
  975.  
  976.   MRESULT mr = get_old_frame_msg () (hwnd, msg, mp1, mp2);
  977.  
  978.   // If we're resizing the window, set the grid.
  979.  
  980.   USHORT fs = SHORT1FROMMP (mp1);
  981.   if (((fs & TF_MOVE) != TF_MOVE)
  982.       && ((fs & TF_MOVE) || (fs & TF_SETPOINTERPOS))
  983.       && cxChar != 0 && cyChar != 0)
  984.     {
  985.       PTRACKINFO pti = (PTRACKINFO)mp2;
  986.       pti->fs |= TF_GRID;
  987.       if (fixed_pitch)
  988.         pti->cxGrid = pti->cxKeyboard = cxChar;
  989.       else
  990.         pti->cxGrid = pti->cxKeyboard = 1;
  991.       pti->cyGrid = pti->cyKeyboard = cyChar;
  992.     }
  993.   return mr;
  994. }
  995.  
  996.  
  997. void pmtxt::delete_lines (int line, int count, bool do_paint)
  998. {
  999.   _fmutex_checked_request (&mutex, _FMR_IGNINT);
  1000.   if (line >= 0 && line < lines_used && count >= 1)
  1001.     {
  1002.       if (line + count > lines_used)
  1003.         count = lines_used - line;
  1004.       if (cache_line >= line)
  1005.         cache_line = -1;
  1006.       int i;
  1007.       for (i = 0; i < count; ++i)
  1008.         free_line (line + i);
  1009.       for (i = line; i < lines_used - count; ++i)
  1010.         lines_vector[i] = lines_vector[i+count];
  1011.       for (i = lines_used - count; i < lines_used; ++i)
  1012.         init_line (i);
  1013.       lines_used -= count;
  1014.       int first = line - y_off;
  1015.       if (first < 0) first = 0;
  1016.       if (y_off > lines_used - window_lines)
  1017.         {
  1018.           y_off = lines_used - window_lines;
  1019.           if (y_off < 0)
  1020.             y_off = 0;
  1021.           // TODO: Use GpiBitBlt?
  1022.           if (do_paint && !output_stopped)
  1023.             repaint ();
  1024.           else
  1025.             output_pending = true;
  1026.         }
  1027.       else if (first < window_lines)
  1028.         {
  1029.           if (!(do_paint && !output_stopped))
  1030.             output_pending = true;
  1031.           else if (first + count >= window_lines)
  1032.             {
  1033.               // No scrolling, just overwrite the last line(s)
  1034.               // Clear first because paint() does nothing for lines
  1035.               // beyond the current end of lines_vector[]
  1036.               int n = window_lines - first;
  1037.               RECTL rcli;
  1038.               rcli.xLeft = 0; rcli.xRight = cxClient;
  1039.               rcli.yBottom = 0; rcli.yTop = n * cyChar;
  1040.               painting (true);
  1041.               WinFillRect (hpsClient, &rcli, CLR_BACKGROUND);
  1042.  
  1043.               // Repaint the last line(s)
  1044.               for (i = 0; i < n; ++i)
  1045.                 paint (hpsClient, hpsClient_attr,
  1046.                        window_lines - n + i + y_off, 0, false);
  1047.               painting (false);
  1048.             }
  1049.           else
  1050.             {
  1051.               RECTL rcli, rcl, rcl_update, rcl_dummy;
  1052.               rcli.xLeft = 0; rcli.xRight = cxClient;
  1053.               rcli.yBottom = 0; rcli.yTop = cyClient - first * cyChar;
  1054.               rcl.xLeft = rcli.xLeft; rcl.xRight = rcli.xRight - 1;
  1055.               rcl.yBottom = rcli.yBottom; rcl.yTop = rcli.yTop - 1;
  1056.               if (!output_pending
  1057.                   && GpiRectVisible (hpsClient, &rcl) == RVIS_VISIBLE
  1058.                   && (!WinQueryUpdateRect (get_hwndClient (), &rcl_update)
  1059.                       || !WinIntersectRect (get_hab (), &rcl_dummy,
  1060.                                             &rcl_update, &rcli)))
  1061.                 {
  1062.                   // Scroll
  1063.                   POINTL aptl[3];
  1064.                   aptl[0].x = 0; aptl[0].y = count * cyChar;
  1065.                   aptl[1].x = cxClient; aptl[1].y = cyClient - first * cyChar;
  1066.                   aptl[2].x = 0; aptl[2].y = 0;
  1067.                   painting (true);
  1068.                   GpiBitBlt (hpsClient, hpsClient, 3, aptl, ROP_SRCCOPY,
  1069.                              BBO_IGNORE);
  1070.  
  1071.                   // Clear the last line(s)
  1072.                   rcli.xLeft = 0; rcli.xRight = cxClient;
  1073.                   rcli.yBottom = 0; rcli.yTop = count * cyChar;
  1074.                   WinFillRect (hpsClient, &rcli, CLR_BACKGROUND);
  1075.  
  1076.                   // Repaint the last line(s)
  1077.                   for (i = 0; i < count; ++i)
  1078.                     paint (hpsClient, hpsClient_attr,
  1079.                            window_lines - count + i + y_off, 0, false);
  1080.  
  1081.                   painting (false);
  1082.                 }
  1083.               else
  1084.                 {
  1085.                   // TODO: Don't have to repaint all the lines
  1086.                   repaint ();
  1087.                 }
  1088.             }
  1089.         }
  1090.     }
  1091.   unlock ();
  1092.   check_y_size ();
  1093. }
  1094.  
  1095.  
  1096. void pmtxt::insert_lines (int line, int count, bool do_paint)
  1097. {
  1098.   _fmutex_checked_request (&mutex, _FMR_IGNINT);
  1099.   if (line >= 0 && line <= lines_used && count >= 1)
  1100.     {
  1101.       if (lines_used + count > lines_alloc)
  1102.         more_lines (lines_used + count);
  1103.       if (cache_line >= line)
  1104.         cache_line = -1;
  1105.       if (line == lines_used)
  1106.         lines_used += count;
  1107.       else
  1108.         {
  1109.           int i;
  1110.           for (i = 0; i < count; ++i)
  1111.             free_line (lines_used + i);
  1112.           for (i = lines_used - 1; i >= line; --i)
  1113.             lines_vector[i+count] = lines_vector[i];
  1114.           for (i = 0; i < count; ++i)
  1115.             init_line (line + i);
  1116.           lines_used += count;
  1117.  
  1118.           int first = line - y_off;
  1119.           if (first < 0) first = 0;
  1120.           if (first < window_lines)
  1121.             {
  1122.               if (!(do_paint && !output_stopped))
  1123.                 output_pending = true;
  1124.               else if (first + count >= window_lines)
  1125.                 {
  1126.                   // No scrolling, just clear the inserted line(s)
  1127.                   int n = window_lines - first;
  1128.                   RECTL rcli;
  1129.                   rcli.xLeft = 0; rcli.xRight = cxClient;
  1130.                   rcli.yBottom = 0; rcli.yTop = n * cyChar;
  1131.                   painting (true);
  1132.                   WinFillRect (hpsClient, &rcli, CLR_BACKGROUND);
  1133.                   painting (false);
  1134.                 }
  1135.               else
  1136.                 {
  1137.                   RECTL rcli, rcl, rcl_update, rcl_dummy;
  1138.                   rcli.xLeft = 0; rcli.xRight = cxClient;
  1139.                   rcli.yBottom = 0; rcli.yTop = cyClient - first * cyChar;
  1140.                   rcl.xLeft = rcli.xLeft; rcl.xRight = rcli.xRight - 1;
  1141.                   rcl.yBottom = rcli.yBottom; rcl.yTop = rcli.yTop - 1;
  1142.                   if (!output_pending
  1143.                       && GpiRectVisible (hpsClient, &rcl) == RVIS_VISIBLE
  1144.                       && (!WinQueryUpdateRect (get_hwndClient (), &rcl_update)
  1145.                           || !WinIntersectRect (get_hab (), &rcl_dummy,
  1146.                                                 &rcl_update, &rcli)))
  1147.                     {
  1148.                       // Scroll
  1149.                       POINTL aptl[3];
  1150.                       aptl[0].x = 0; aptl[0].y = 0;
  1151.                       aptl[1].x = cxClient;
  1152.                       aptl[1].y = (window_lines - first - count) * cyChar;
  1153.                       aptl[2].x = 0; aptl[2].y = count * cyChar;
  1154.                       painting (true);
  1155.                       GpiBitBlt (hpsClient, hpsClient, 3, aptl, ROP_SRCCOPY,
  1156.                                  BBO_IGNORE);
  1157.  
  1158.                       // Clear the inserted line(s)
  1159.                       rcli.xLeft = 0; rcli.xRight = cxClient;
  1160.                       rcli.yTop = cyClient - first * cyChar;
  1161.                       rcli.yBottom = rcl.yTop - count * cyChar;
  1162.                       WinFillRect (hpsClient, &rcli, CLR_BACKGROUND);
  1163.                       painting (false);
  1164.                     }
  1165.                   else
  1166.                     {
  1167.                       // TODO: Don't have to repaint all the lines
  1168.                       repaint ();
  1169.                     }
  1170.                 }
  1171.             }
  1172.         }
  1173.     }
  1174.   unlock ();
  1175.   check_y_size ();
  1176. }
  1177.  
  1178.  
  1179. void pmtxt::clear_lines (int line, int count, bool do_paint)
  1180. {
  1181.   _fmutex_checked_request (&mutex, _FMR_IGNINT);
  1182.   if (line >= 0 && line < lines_used && count >= 1)
  1183.     {
  1184.       if (line + count > lines_used)
  1185.         count = lines_used - line;
  1186.       if (cache_line >= line && cache_line < line + count)
  1187.         cache_line = -1;
  1188.       for (int i = 0; i < count; ++i)
  1189.         {
  1190.           lines_vector[line + i].len = 0;
  1191.           delete_attr (lines_vector[line + i].attr);
  1192.           lines_vector[line + i].attr = NULL;
  1193.         }
  1194.  
  1195.       int first = line - y_off;
  1196.       if (first < window_lines && first + count > 0)
  1197.         {
  1198.           if (first < 0)
  1199.             {
  1200.               count += first;
  1201.               first = 0;
  1202.             }
  1203.           if (first + count > window_lines)
  1204.             count = window_lines - first;
  1205.           if (do_paint && !output_stopped)
  1206.             {
  1207.               RECTL rcli;
  1208.               rcli.xLeft = 0; rcli.xRight = cxClient;
  1209.               rcli.yTop =  cyClient - first * cyChar;
  1210.               rcli.yBottom = rcli.yTop - count * cyChar;
  1211.               painting (true);
  1212.               WinFillRect (hpsClient, &rcli, CLR_BACKGROUND);
  1213.               painting (false);
  1214.             }
  1215.           else
  1216.             output_pending = true;
  1217.         }
  1218.     }
  1219.   unlock ();
  1220. }
  1221.  
  1222.  
  1223. void pmtxt::truncate_line (int line, int count, bool do_paint)
  1224. {
  1225.   _fmutex_checked_request (&mutex, _FMR_IGNINT);
  1226.   if (line >= 0 && line < lines_used)
  1227.     {
  1228.       pmtxt_line *pline = &lines_vector[line];
  1229.       if (pline->len > count)
  1230.         {
  1231.           pmtxt_line_attr **pa = &pline->attr;
  1232.           while (*pa != NULL && (*pa)->column <= count)
  1233.             pa = &(*pa)->next;
  1234.           pmtxt_line_attr *a = *pa, *next;
  1235.           while (a != NULL && a->column != EOL_COLUMN)
  1236.             {
  1237.               next = a->next;
  1238.               delete a;
  1239.               a = next;
  1240.             }
  1241.           *pa = a;
  1242.           pline->len = count;
  1243.           if (cache_line == line && cache_column > count)
  1244.             cache_line = -1;
  1245.           if (do_paint)
  1246.             paint (hpsClient, hpsClient_attr, line, count, true);
  1247.         }
  1248.     }
  1249.   unlock ();
  1250. }
  1251.  
  1252.  
  1253. // Return true iff the caller is responsible for painting the area
  1254. // becoming free
  1255.  
  1256. bool pmtxt::scroll_up (int n, bool do_paint)
  1257. {
  1258.   RECTL rcl, rcli, rcl_update, rcl_dummy;
  1259.  
  1260.   if (n >= window_lines || output_pending)
  1261.     {
  1262.       if (!do_paint)
  1263.         cache_line = -1;
  1264.       repaint ();
  1265.       output_pending = false;
  1266.       return false;
  1267.     }
  1268.   rcli.xLeft = 0; rcli.xRight = cxClient;
  1269.   rcli.yBottom = 0; rcli.yTop = cyClient - n * cyChar;
  1270.   rcl.xLeft = 0; rcl.xRight = cxClient - 1;
  1271.   rcl.yBottom = 0; rcl.yTop = cyClient - n * cyChar - 1;
  1272.   if (GpiRectVisible (hpsClient, &rcl) == RVIS_VISIBLE
  1273.       && (!WinQueryUpdateRect (get_hwndClient (), &rcl_update)
  1274.           || !WinIntersectRect (get_hab (), &rcl_dummy, &rcl_update, &rcli)))
  1275.     {
  1276.       // Source rectangle completely visible and not to be updated; scroll!
  1277.       POINTL aptl[3];
  1278.       aptl[0].x = 0; aptl[0].y = n * cyChar;
  1279.       aptl[1].x = cxClient; aptl[1].y = cyClient;
  1280.       aptl[2].x = 0; aptl[2].y = 0;
  1281.       painting (true);
  1282.       GpiBitBlt (hpsClient, hpsClient, 3, aptl, ROP_SRCCOPY, BBO_IGNORE);
  1283.       rcli.xLeft = 0; rcli.xRight = cxClient;
  1284.       rcli.yBottom = 0; rcli.yTop = n * cyChar;
  1285.       if (do_paint)
  1286.         {
  1287.           painting (false);
  1288.           repaint (&rcli);
  1289.           return false;
  1290.         }
  1291.       else
  1292.         {
  1293.           WinFillRect (hpsClient, &rcli, CLR_BACKGROUND);
  1294.           painting (false);
  1295.           return true;
  1296.         }
  1297.     }
  1298.   else
  1299.     {
  1300.       if (!do_paint)
  1301.         cache_line = -1;
  1302.       repaint ();
  1303.       return false;
  1304.     }
  1305. }
  1306.  
  1307.  
  1308. void pmtxt::scroll_down (int n)
  1309. {
  1310.   RECTL rcl, rcli, rcl_update, rcl_dummy;
  1311.  
  1312.   if (n >= window_lines || output_pending)
  1313.     {
  1314.       repaint ();
  1315.       output_pending = false;
  1316.       return;
  1317.     }
  1318.   rcli.xLeft = 0; rcli.xRight = cxClient;
  1319.   rcli.yBottom = n * cyChar; rcli.yTop = cyClient;
  1320.   rcl.xLeft = 0; rcl.xRight = cxClient - 1;
  1321.   rcl.yBottom = n * cyChar; rcl.yTop = cyClient - 1;
  1322.   if (GpiRectVisible (hpsClient, &rcl) == RVIS_VISIBLE
  1323.       && (!WinQueryUpdateRect (get_hwndClient (), &rcl_update)
  1324.           || !WinIntersectRect (get_hab (), &rcl_dummy, &rcl_update, &rcli)))
  1325.     {
  1326.       // Source rectangle completely visible and not to be updated; scroll!
  1327.       POINTL aptl[3];
  1328.       aptl[0].x = 0; aptl[0].y = 0;
  1329.       aptl[1].x = cxClient; aptl[1].y = cyClient - n * cyChar;
  1330.       aptl[2].x = 0; aptl[2].y = n * cyChar;
  1331.       painting (true);
  1332.       GpiBitBlt (hpsClient, hpsClient, 3, aptl, ROP_SRCCOPY, BBO_IGNORE);
  1333.       painting (false);
  1334.       rcli.xLeft = 0; rcli.xRight = cxClient;
  1335.       rcli.yTop = cyClient; rcli.yBottom = cyClient - n * cyChar;
  1336.       repaint (&rcli);
  1337.     }
  1338.   else
  1339.     repaint ();
  1340. }
  1341.  
  1342.  
  1343. void pmtxt::disable_output ()
  1344. {
  1345.   output_stopped = true;
  1346. }
  1347.  
  1348.  
  1349. void pmtxt::enable_output ()
  1350. {
  1351.   output_stopped = false;
  1352.   if (output_pending)
  1353.     {
  1354.       output_pending = false;
  1355.       repaint ();
  1356.     }
  1357. }
  1358.  
  1359.  
  1360. void pmtxt::show_line (int line, int threshold, int overshoot)
  1361. {
  1362.   int n;
  1363.  
  1364.   if (overshoot < threshold)
  1365.     overshoot = threshold;
  1366.   if (line - threshold < y_off)
  1367.     {
  1368.       line = line >= overshoot ? line - overshoot : 0;
  1369.       n = y_off - line;
  1370.       y_off = line;
  1371.       scroll_down (n);
  1372.       check_y_size ();
  1373.     }
  1374.   else if (line + threshold >= y_off + window_lines)
  1375.     {
  1376.       line = line + overshoot < lines_used ? line + overshoot : lines_used - 1;
  1377.       n = line - (y_off + window_lines - 1);
  1378.       y_off = line - (window_lines - 1);
  1379.       scroll_up (n, true);
  1380.       check_y_size ();
  1381.     }
  1382. }
  1383.  
  1384.  
  1385. // TODO: E_TEXT only
  1386. int pmtxt::get_char (int line, int column)
  1387. {
  1388.   if (line < 0 || line >= lines_used || column < 0)
  1389.     return -1;
  1390.   if (column >= lines_vector[line].len)
  1391.     return -1;
  1392.   return (unsigned char)lines_vector[line].str[column];
  1393. }
  1394.  
  1395.  
  1396. // TODO: E_TEXT only
  1397. bool pmtxt::get_string (int line, int column, char *str, size_t count)
  1398. {
  1399.   if (line < 0 || line >= lines_used || column < 0)
  1400.     return false;
  1401.   if (column >= lines_vector[line].len)
  1402.     count = 0;
  1403.   else if (column + (int)count > lines_vector[line].len)
  1404.     count = lines_vector[line].len - column;
  1405.   memcpy (str, lines_vector[line].str + column, count);
  1406.   str[count] = 0;
  1407.   return true;
  1408. }
  1409.  
  1410.  
  1411. void pmtxt::clear_tabs ()
  1412. {
  1413.   _fmutex_checked_request (&mutex, _FMR_IGNINT);
  1414.   for (int i = 0; i < tab_count;++i)
  1415.     tab_x[i] = 0;
  1416.   unlock ();
  1417. }
  1418.  
  1419.  
  1420. bool pmtxt::get_bbox_pos (HPS hps, POINTL *pptl, int line, int column)
  1421. {
  1422.   if (line >= lines_used)
  1423.     return false;
  1424.  
  1425.   int x = -1;
  1426.   int line_len = lines_vector[line].len;
  1427.  
  1428.   if (column == 0)
  1429.     x = 0;
  1430.   else if (line == cache_line && column == cache_column)
  1431.     x = cache_x;
  1432.  
  1433.   if (column > line_len)
  1434.     return false;
  1435.  
  1436.   if (x == -1)
  1437.     {
  1438.       POINTL ptl; ptl.x = 0; ptl.y = 0;
  1439.       char *p = lines_vector[line].str;
  1440.       const pmtxt_line_attr *a = lines_vector[line].attr;
  1441.  
  1442.       // TODO: Use extra PS when adding fonts
  1443.       // TODO: Start at cache_column if possible
  1444.  
  1445.       if (a == NULL || column <= a->column)
  1446.         {
  1447.           if (fixed_pitch)
  1448.             x = ptl.x + column * cxChar;
  1449.           else
  1450.             {
  1451.               GpiQueryCharStringPosAt (hps, &ptl, 0, column, (PCH)p,
  1452.                                        NULL, aptl_line);
  1453.               x = aptl_line[column].x;
  1454.             }
  1455.         }
  1456.       else
  1457.         {
  1458.           int cur_column = 0, t = 0, end, len;
  1459.           x = 0;
  1460.  
  1461.           if (a->column != 0)
  1462.             {
  1463.               if (fixed_pitch)
  1464.                 x += a->column * cxChar;
  1465.               else
  1466.                 {
  1467.                   GpiQueryCharStringPosAt (hps, &ptl, 0, a->column,
  1468.                                            (PCH)p, NULL, aptl_line);
  1469.                   x += aptl_line[a->column].x;
  1470.                 }
  1471.               cur_column = a->column;
  1472.             }
  1473.  
  1474.           while (a != NULL && cur_column != column)
  1475.             {
  1476.               // TODO: Don't skip past font changes
  1477.               while (a->next != NULL && a->next->el == a->el)
  1478.                 {
  1479.                   if (a->el == E_TAB && a->next->column != EOL_COLUMN)
  1480.                     t += a->next->column - a->column;
  1481.                   a = a->next;
  1482.                 }
  1483.  
  1484.               if (a->column == EOL_COLUMN || a->next == NULL)
  1485.                 end = line_len;
  1486.               else
  1487.                 end = a->next->column;
  1488.               if (column < end)
  1489.                 len = column - cur_column;
  1490.               else
  1491.                 len = end - cur_column;
  1492.               switch (a->el)
  1493.                 {
  1494.                 case E_TEXT:
  1495.                   if (fixed_pitch)
  1496.                     x += len * cxChar;
  1497.                   else
  1498.                     {
  1499.                       GpiQueryCharStringPosAt (hps, &ptl, 0, len,
  1500.                                                (PCH)p + cur_column, NULL,
  1501.                                                aptl_line);
  1502.                       x += aptl_line[len].x;
  1503.                     }
  1504.                   break;
  1505.                 case E_FILL:
  1506.                   // TODO: Width specified in string (pixels)
  1507.                   x += len * cxChar;
  1508.                   break;
  1509.                 case E_VRULE:
  1510.                   x += rule_width;
  1511.                   break;
  1512.                 case E_TAB:
  1513.                   t += len;
  1514.                   x = tab_x[t-1];
  1515.                   break;
  1516.                 default:
  1517.                   break;
  1518.                 }
  1519.               a = a->next;
  1520.               cur_column += len;
  1521.             }
  1522.         }
  1523.       cache_line = line; cache_column = column; cache_x = x;
  1524.     }
  1525.   pptl->x = x - x_off * cxChar;
  1526.   pptl->y = cyClient - cyChar * (line + 1 - y_off);
  1527.   return true;
  1528. }
  1529.  
  1530.  
  1531. // This is the reverse of get_bbox_pos()
  1532. // TODO: Use cache
  1533.  
  1534. void pmtxt::line_column_from_x_y (HPS hps, int *out_line, int *out_column,
  1535.                                   int *out_tab, int x, int y)
  1536. {
  1537.   int line = (cyClient - 1 - y) / cyChar + y_off;
  1538.   if (line >= 0 && line < lines_used)
  1539.     {
  1540.       *out_line = line;
  1541.       pmtxt_line *pline = &lines_vector[line];
  1542.       POINTL ptl;
  1543.       ptl.x = -x_off * cxChar; ptl.y = 0;
  1544.       if (x >= ptl.x)
  1545.         {
  1546.           const pmtxt_line_attr *a = pline->attr;
  1547.           pmtxt_line_attr temp;
  1548.           if (a == NULL || a->column > 0)
  1549.             {
  1550.               temp.el = E_TEXT;
  1551.               temp.next = (pmtxt_line_attr *)a; // Remove `const'
  1552.               temp.column = 0;
  1553.               a = &temp;
  1554.             }
  1555.           int tab_index = 0;
  1556.           while (a != NULL && a->column != EOL_COLUMN)
  1557.             {
  1558.               int len;
  1559.               if (a->next != NULL && a->next->column != EOL_COLUMN)
  1560.                 len = a->next->column - a->column;
  1561.               else
  1562.                 len = pline->len - a->column;
  1563.               if (a->el == E_TEXT)
  1564.                 {
  1565.                   if (fixed_pitch)
  1566.                     {
  1567.                       if (x < ptl.x + len * cxChar)
  1568.                         {
  1569.                           *out_column = a->column + (x - ptl.x) / cxChar;
  1570.                           *out_tab = tab_index;
  1571.                           return;
  1572.                         }
  1573.                       ptl.x += len * cxChar;
  1574.                     }
  1575.                   else
  1576.                     {
  1577.                       GpiQueryCharStringPosAt (hps, &ptl, 0, len,
  1578.                                                (PCH)pline->str + a->column,
  1579.                                                NULL, aptl_line);
  1580.                       for (int i = 0; i < len; ++i)
  1581.                         if (x < aptl_line[i+1].x)
  1582.                           {
  1583.                             *out_column = a->column + i;
  1584.                             *out_tab = tab_index;
  1585.                             return;
  1586.                           }
  1587.                       ptl.x = aptl_line[len].x;
  1588.                     }
  1589.                 }
  1590.               else if (a->el == E_VRULE)
  1591.                 {
  1592.                   if (x < ptl.x + rule_width)
  1593.                     {
  1594.                       *out_column = a->column;
  1595.                       *out_tab = tab_index;
  1596.                       return;
  1597.                     }
  1598.                   ptl.x += rule_width;
  1599.                 }
  1600.               else if (a->el == E_FILL)
  1601.                 {
  1602.                   if (x < ptl.x + len * cxChar)
  1603.                     {
  1604.                       *out_column = a->column + (x - ptl.x) / cxChar;
  1605.                       *out_tab = tab_index;
  1606.                       return;
  1607.                     }
  1608.                   ptl.x += len * cxChar;
  1609.                 }
  1610.               else if (a->el == E_TAB)
  1611.                 {
  1612.                   if (x < tab_x[tab_index])
  1613.                     {
  1614.                       *out_column = a->column;
  1615.                       *out_tab = tab_index;
  1616.                       return;
  1617.                     }
  1618.                   ptl.x = tab_x[tab_index++] - x_off * cxChar;
  1619.                 }
  1620.               a = a->next;
  1621.             }
  1622.         }
  1623.     }
  1624.   else
  1625.     *out_line = -1; 
  1626.   *out_column = -1;
  1627.   *out_tab = -1;
  1628. }
  1629.  
  1630.  
  1631. // 
  1632. // Add text
  1633. //
  1634. // Note that this function is not a no-op with LEN==0: it may add
  1635. // an empty line at the end, for the cursor.
  1636.  
  1637. void pmtxt::put (int line, int column, size_t len, const char *s,
  1638.                  const pmtxt_attr &attr, element el, bool do_paint)
  1639. {
  1640.   int lines_added;
  1641.  
  1642.   if (line < 0 || column < 0)
  1643.     return;
  1644.  
  1645.   _fmutex_checked_request (&mutex, _FMR_IGNINT);
  1646.  
  1647.   if (line >= lines_alloc)
  1648.     more_lines (line + 1);
  1649.  
  1650.   pmtxt_line *pline = &lines_vector[line];
  1651.   int tlen = pline->len;
  1652.  
  1653.   if (column > tlen)
  1654.     {
  1655.       unlock ();
  1656.       return;
  1657.     }
  1658.  
  1659.   if (line >= lines_used)
  1660.     {
  1661.       lines_added = line + 1 - lines_used;
  1662.       lines_used = line + 1;
  1663.     }
  1664.   else
  1665.     lines_added = 0;
  1666.  
  1667.   if (column + (int)len > max_line_len)
  1668.     {
  1669.       len = max_line_len - column;
  1670.       if (len == 0)
  1671.         {
  1672.           unlock ();
  1673.           return;
  1674.         }
  1675.     }
  1676.   if (column + (int)len > pline->alloc)
  1677.     {
  1678.       int new_alloc = ((column + len + 39) / 40) * 40;
  1679.       char *t = new char[new_alloc];
  1680.       memcpy (t, pline->str, tlen);
  1681.       delete[] pline->str;
  1682.       pline->alloc = new_alloc;
  1683.       pline->str = t;
  1684.     }
  1685.  
  1686.   char *target = pline->str;
  1687.  
  1688.   memcpy (target + column, s, len);
  1689.  
  1690.   if (len != 0)
  1691.     {
  1692.       if (column + (int)len > tlen)
  1693.         pline->len = column + len;
  1694.       change_attr (pline, column, column + (int)len, attr, el);
  1695.       if (line == cache_line && column < cache_column)
  1696.         cache_line = -1;
  1697.     }
  1698.  
  1699.   if (do_paint && !output_stopped)
  1700.     {
  1701.       if (lines_added != 0 && lines_used > window_lines)
  1702.         {
  1703.           if (y_off + lines_added == lines_used - window_lines)
  1704.             {
  1705.               y_off += lines_added;
  1706.               bool paint_after_scroll = scroll_up (lines_added, false);
  1707.               check_y_size ();
  1708.               if (!paint_after_scroll)
  1709.                 {
  1710.                   unlock ();
  1711.                   return;
  1712.                 }
  1713.             }
  1714.           else
  1715.             check_y_size ();
  1716.         }
  1717.  
  1718.       if (len != 0)
  1719.         {
  1720.           tab_repaint = false;
  1721.           // TODO: Set do_paint to true only if required
  1722.           painting (true);
  1723.           paint (hpsClient, hpsClient_attr, line, column, true);
  1724.           painting (false);
  1725.           if (tab_repaint)
  1726.             repaint ();
  1727.         }
  1728.     }
  1729.   else
  1730.     {
  1731.       if (output_stopped && (lines_added || len != 0))
  1732.         output_pending = true;
  1733.     }
  1734.  
  1735.   unlock ();
  1736. }
  1737.  
  1738.  
  1739. //
  1740. // Change attributes of existing text
  1741. //
  1742. void pmtxt::put (int line, int column, size_t len, const pmtxt_attr &attr,
  1743.                  bool do_paint)
  1744. {
  1745.   if (line < 0 || line >= lines_used || column < 0)
  1746.     return;
  1747.  
  1748.   _fmutex_checked_request (&mutex, _FMR_IGNINT);
  1749.  
  1750.   pmtxt_line *pline = &lines_vector[line];
  1751.   int tlen = pline->len;
  1752.   if (column + (int)len > tlen)
  1753.     {
  1754.       if (column >= tlen)
  1755.         len = 0;
  1756.       else
  1757.         len = tlen - column;
  1758.     }
  1759.   if (len == 0)
  1760.     {
  1761.       unlock ();
  1762.       return;
  1763.     }
  1764.  
  1765.   change_attr (pline, column, column + (int)len, attr, E_KEEP);
  1766.  
  1767.   // TODO: Invalidate cache if font width changed
  1768.  
  1769.   if (do_paint && !output_stopped)
  1770.     {
  1771.       tab_repaint = false;
  1772.       painting (true);
  1773.       paint (hpsClient, hpsClient_attr, line, column, true);
  1774.       painting (false);
  1775.       if (tab_repaint)
  1776.         repaint ();
  1777.     }
  1778.   else
  1779.     {
  1780.       if (output_stopped)
  1781.         output_pending = true;
  1782.     }
  1783.  
  1784.   unlock ();
  1785. }
  1786.  
  1787.  
  1788. void pmtxt::set_eol_attr (int line, const pmtxt_attr &attr, bool do_paint)
  1789. {
  1790.   _fmutex_checked_request (&mutex, _FMR_IGNINT);
  1791.   if (line >= 0 && line < lines_used)
  1792.     {
  1793.       pmtxt_line_attr **ppa;
  1794.       for (ppa = &lines_vector[line].attr; *ppa != NULL; ppa = &(*ppa)->next)
  1795.         if ((*ppa)->column == EOL_COLUMN)
  1796.           break;
  1797.       bool change = false;
  1798.       if (*ppa == NULL)
  1799.         {
  1800.           if (!eq_attr (attr, default_attr))
  1801.             {
  1802.               pmtxt_line_attr *a = new pmtxt_line_attr;
  1803.               a->next = NULL;
  1804.               a->column = EOL_COLUMN;
  1805.               a->el = E_FILL;
  1806.               a->attr = attr;
  1807.               *ppa = a;
  1808.               change = true;
  1809.             }
  1810.         }
  1811.       else
  1812.         {
  1813.           if (eq_attr (attr, default_attr))
  1814.             {
  1815.               pmtxt_line_attr *a = *ppa;
  1816.               *ppa = NULL;
  1817.               delete a;
  1818.               change = true;
  1819.             }
  1820.           else if (!eq_attr (attr, (*ppa)->attr))
  1821.             {
  1822.               (*ppa)->attr = attr;
  1823.               change = true;
  1824.             }
  1825.         }
  1826.       if (change)
  1827.         {
  1828.           if (do_paint && !output_stopped)
  1829.             {
  1830.               POINTL ptl;
  1831.               RECTL rcli;
  1832.               if (!get_bbox_pos (hpsClient, &ptl, line,
  1833.                                  lines_vector[line].len))
  1834.                 abort ();
  1835.               rcli.xLeft = ptl.x; rcli.xRight = cxClient;
  1836.               rcli.yBottom = ptl.y; rcli.yTop = ptl.y + cyChar;
  1837.               painting (true);
  1838.               WinFillRect (hpsClient, &rcli, attr.bg_color);
  1839.               painting (false);
  1840.             }
  1841.           else
  1842.             output_pending = true;
  1843.         }
  1844.     }
  1845.   unlock ();
  1846. }
  1847.  
  1848.  
  1849. // TODO: underline off, underline to end of line, underline to width of window
  1850. // TODO: attribute
  1851. void pmtxt::underline (int line, bool on, bool do_paint)
  1852. {
  1853.   _fmutex_checked_request (&mutex, _FMR_IGNINT);
  1854.   if (line >= 0 && line < lines_used
  1855.       && (bool)(lines_vector[line].flags & LF_UNDERLINE) != on)
  1856.     {
  1857.       if (on)
  1858.         lines_vector[line].flags |= LF_UNDERLINE;
  1859.       else
  1860.         lines_vector[line].flags &= ~LF_UNDERLINE;
  1861.       if (do_paint && !output_stopped)
  1862.         {
  1863.           painting (true);
  1864.           paint (hpsClient, hpsClient_attr, line, 0, true);
  1865.           painting (false);
  1866.         }
  1867.     }
  1868.   unlock ();
  1869. }
  1870.  
  1871.  
  1872. struct change_data
  1873. {
  1874.   pmtxt_line_attr **add;
  1875.   pmtxt_line_attr *last;
  1876.   pmtxt_attr cur_attr;
  1877.   pmtxt::element cur_el;
  1878. };
  1879.  
  1880.  
  1881. void change_at (change_data &c, pmtxt_line_attr *a, int column,
  1882.                 const pmtxt_attr &attr, pmtxt::element el)
  1883. {
  1884.   if (column != EOL_COLUMN && eq_attr (attr, c.cur_attr) && el == c.cur_el)
  1885.     {
  1886.       if (a != NULL)
  1887.         delete a;
  1888.     }
  1889.   else if (c.last != NULL && c.last->column == column)
  1890.     {
  1891.       c.last->attr = attr;
  1892.       c.last->el = el;
  1893.       c.cur_attr = attr;
  1894.       c.cur_el = el;
  1895.       if (a != NULL)
  1896.         delete a;
  1897.     }
  1898.   else
  1899.     {
  1900.       if (a == NULL)
  1901.         a = new pmtxt_line_attr;
  1902.       a->next = NULL;
  1903.       a->column = column;
  1904.       a->attr = attr;
  1905.       a->el = el;
  1906.       c.cur_attr = attr;
  1907.       c.cur_el = el;
  1908.       c.last = a;
  1909.       *c.add = a;
  1910.       c.add = &a->next;
  1911.     }
  1912. }
  1913.  
  1914.  
  1915. // TODO: Optimize to avoid copying/writing
  1916.  
  1917. void pmtxt::change_attr (pmtxt_line *p, int start, int end,
  1918.                          const pmtxt_attr &new_attr, element new_el)
  1919. {
  1920.   pmtxt_line_attr *new_list, *a, *next;
  1921.   pmtxt_attr prev_attr;
  1922.   element prev_el;
  1923.   change_data c;
  1924.  
  1925.   new_list = NULL;
  1926.   c.add = &new_list;
  1927.   c.last = NULL;
  1928.   c.cur_attr = default_attr;
  1929.   c.cur_el = E_TEXT;
  1930.  
  1931.   a = p->attr;
  1932.   while (a != NULL && a->column < start)
  1933.     {
  1934.       next = a->next;
  1935.       change_at (c, a, a->column, a->attr, a->el);
  1936.       a = next;
  1937.     }
  1938.  
  1939.   prev_attr = c.cur_attr; prev_el = c.cur_el;
  1940.   if (a == NULL)
  1941.     change_at (c, NULL, start, new_attr,
  1942.                new_el != E_KEEP ? new_el : c.cur_el);
  1943.   else if (new_el == E_KEEP)
  1944.     {
  1945.       change_at (c, NULL, start, new_attr, c.cur_el);
  1946.       while (a != NULL && a->column < end)
  1947.         {
  1948.           next = a->next;
  1949.           change_at (c, a, a->column, new_attr, a->el);
  1950.           a = next;
  1951.         }
  1952.     }
  1953.   else
  1954.     {
  1955.       if (a->column == start)
  1956.         {
  1957.           prev_attr = a->attr; prev_el = a->el;
  1958.           next = a->next;
  1959.           change_at (c, a, start, new_attr, new_el);
  1960.           a = next;
  1961.         }
  1962.       else
  1963.         {
  1964.           // TODO: if a->column < end, we can reuse the node
  1965.           change_at (c, NULL, start, new_attr, new_el);
  1966.         }
  1967.       while (a != NULL && a->column < end)
  1968.         {
  1969.           next = a->next;
  1970.           prev_attr = a->attr; prev_el = a->el;
  1971.           delete a;
  1972.           a = next;
  1973.         }
  1974.     }
  1975.  
  1976.   if (a == NULL || a->column != end)
  1977.     change_at (c, NULL, end, prev_attr, prev_el);
  1978.  
  1979.   while (a != NULL)
  1980.     {
  1981.       next = a->next;
  1982.       change_at (c, a, a->column, a->attr, a->el);
  1983.       a = next;
  1984.     }
  1985.  
  1986.   p->attr = new_list;
  1987.  
  1988.   int t = 0, i;
  1989.    for (a = p->attr; a != NULL; a = a->next)
  1990.     ++t;
  1991.   if (t > tab_x_size)
  1992.     {
  1993.       tab_x_size = t + 16;
  1994.       int *new_x = new int[tab_x_size];
  1995.       for (i = 0; i < tab_count; ++i)
  1996.         new_x[i] = tab_x[i];
  1997.       delete[] tab_x;
  1998.       tab_x = new_x;
  1999.     }
  2000.   if (t > tab_count)
  2001.     {
  2002.       for (i = tab_count; i < t; ++i)
  2003.         tab_x[i] = 0;
  2004.       tab_count = t;
  2005.     }
  2006. }
  2007.