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 >
Wrap
C/C++ Source or Header
|
1996-09-07
|
55KB
|
2,007 lines
/* pmtxt.cc
Copyright (c) 1996 Eberhard Mattes
This file is part of pmgdb.
pmgdb is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
pmgdb is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pmgdb; see the file COPYING. If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA. */
#define INCL_DOS
#define INCL_WIN
#define INCL_GPI
#include <os2.h>
#include <stdlib.h>
#include <string.h>
#include "string.h"
#include "pmapp.h"
#include "pmframe.h"
#include "pmtxt.h"
#define UWM_PMTHREAD (WM_USER+200)
#define PMTHREAD_SBAR_X 0x01
#define PMTHREAD_SBAR_Y 0x02
#define LF_UNDERLINE 0x01
#define EOL_COLUMN (pmtxt::max_line_len + 1)
#define MAX(a,b) ((a) > (b) ? (a) : (b))
struct pmtxt_line_attr
{
// TODO: x
struct pmtxt_line_attr *next;
int column;
pmtxt::element el;
pmtxt_attr attr;
};
struct pmtxt_line
{
char *str;
pmtxt_line_attr *attr;
int len, alloc;
// TODO: Add flag bits (HAS_TABS, OUTPUT_PENDING, ...)
unsigned char flags;
};
pmtxt::pmtxt (pmapp *in_app, unsigned in_id, ULONG frame_flags,
const SWP *pswp, const char *fontnamesize)
: pmframe (in_app)
{
_fmutex_checked_create (&mutex, 0);
track = true;
default_font = true;
init_fontnamesize = fontnamesize;
initializing_font = false;
aptl_line = new POINTL[max_line_len+1];
pmthread_pending = 0;
x_off = 0; y_off = 0;
x_pos = 0; max_x_pos = 0;
output_stopped = false; output_pending = false;
cache_line = -1;
lines_used = 0; lines_alloc = 0;
lines_vector = NULL;
tab_x = NULL; tab_count = 0; tab_x_size = 0;
tab_repaint = false;
default_attr.fg_color = CLR_BLACK;
default_attr.bg_color = CLR_WHITE;
create ("pmtxt.client", in_id, frame_flags, pswp);
}
pmtxt::~pmtxt ()
{
delete_all ();
delete[] aptl_line;
delete[] tab_x;
// TODO: mutex
}
void pmtxt::delete_all ()
{
_fmutex_checked_request (&mutex, _FMR_IGNINT);
output_pending = lines_used != 0;
for (int i = 0; i < lines_alloc; ++i)
free_line (i);
delete[] lines_vector;
x_off = 0; y_off = 0;
x_pos = 0; max_x_pos = 0;
cache_line = -1;
lines_used = 0; lines_alloc = 0;
lines_vector = NULL;
tab_count = 0;
unlock ();
}
void pmtxt::free_line (int line)
{
delete[] lines_vector[line].str;
delete_attr (lines_vector[line].attr);
}
void pmtxt::init_line (int line)
{
lines_vector[line].str = NULL;
lines_vector[line].attr = NULL;
lines_vector[line].len = 0;
lines_vector[line].alloc = 0;
lines_vector[line].flags = 0;
}
void pmtxt::delete_attr (pmtxt_line_attr *a)
{
pmtxt_line_attr *next;
while (a != NULL)
{
next = a->next;
delete a;
a = next;
}
}
void pmtxt::more_lines (int new_count)
{
int n = new_count < 1024 ? new_count + 128 : new_count + 1024;
pmtxt_line *new_vec = new pmtxt_line[n];
for (int i = 0; i < lines_alloc; ++i)
new_vec[i] = lines_vector[i];
delete[] lines_vector;
lines_vector = new_vec;
for (int i = lines_alloc; i < n; ++i)
init_line (i);
lines_alloc = n;
}
static inline void query_hps_attr (HPS hps, pmtxt_attr &cache)
{
cache.fg_color = GpiQueryColor (hps);
cache.bg_color = GpiQueryBackColor (hps);
}
static inline void set_hps_attr (HPS hps, pmtxt_attr &cache,
const pmtxt_attr &attr)
{
if (attr.fg_color != cache.fg_color)
{
GpiSetColor (hps, attr.fg_color);
cache.fg_color = attr.fg_color;
}
if (attr.bg_color != cache.bg_color)
{
GpiSetBackColor (hps, attr.bg_color);
cache.bg_color = attr.bg_color;
}
}
static inline bool eq_attr (const pmtxt_attr &a1, const pmtxt_attr &a2)
{
return (a1.fg_color == a2.fg_color
&& a1.bg_color == a2.bg_color);
}
MRESULT pmtxt::wm_create (HWND hwnd, ULONG, MPARAM, MPARAM)
{
hwndHbar = WinWindowFromID (get_hwndFrame (), FID_HORZSCROLL);
hwndVbar = WinWindowFromID (get_hwndFrame (), FID_VERTSCROLL);
// Set the initial font
if (init_fontnamesize)
{
initializing_font = true;
WinSetPresParam (hwnd, PP_FONTNAMESIZE, strlen (init_fontnamesize) + 1,
(PVOID)init_fontnamesize);
initializing_font = false;
}
// Create a presentation space handle for asynchronous painting (and
// for retrieving the font metrics).
create_hpsClient ();
// Get the default line (rule) width
// TODO: Does this really yield the width in pixels?
LINEBUNDLE lbundle;
if (GpiQueryAttrs (hpsClient, PRIM_LINE, LBB_WIDTH, &lbundle)
== GPI_ALTERROR)
rule_width = 1;
else
rule_width = FIXEDINT (lbundle.fxWidth);
// Retrieve the current attributes of hpsClient.
query_hps_attr (hpsClient, hpsClient_attr);
return FALSE;
}
// Retrieve the font metrics of the default font.
void pmtxt::get_fontmetrics ()
{
FONTMETRICS fm;
GpiQueryFontMetrics (hpsClient, (LONG)sizeof (fm), &fm);
cxChar = (int)fm.lMaxCharInc;
cyChar = (int)fm.lMaxBaselineExt;
cyDesc = (int)fm.lMaxDescender;
fixed_pitch = (fm.fsType & FM_TYPE_FIXED);
}
void pmtxt::create_hpsClient ()
{
hpsClient = WinGetPS (get_hwndClient ());
setup_ps (hpsClient);
get_fontmetrics ();
}
void pmtxt::setup_ps (HPS hps)
{
GpiSetBackMix (hps, BM_OVERPAINT);
set_font (hps);
}
void pmtxt::set_font (HPS hps)
{
if (!default_font)
{
GpiCreateLogFont (hps, NULL, 1, &fattrs);
GpiSetCharSet (hps, 1);
}
}
void pmtxt::set_font (const pmtxt &s)
{
SIZEF charbox;
char fontnamesize[FACESIZE+10];
// TODO: dropped font (and don't forget to update the constructor
// of source_window which depends on the current behavior)
if (!s.default_font && GpiQueryCharBox (s.hpsClient, &charbox))
{
_fmutex_checked_request (&mutex, _FMR_IGNINT);
fattrs = s.fattrs;
default_font = false;
GpiCreateLogFont (hpsClient, NULL, 1, &fattrs);
GpiSetCharSet (hpsClient, 1);
GpiSetCharBox (hpsClient, &charbox);
get_fontmetrics ();
cache_line = -1;
unlock ();
clear_tabs ();
change_size ();
font_changed ();
repaint ();
}
else if (s.default_font
&& WinQueryPresParam (s.get_hwndClient(), PP_FONTNAMESIZE, 0,
NULL, sizeof (fontnamesize), fontnamesize,
0) != 0)
WinSetPresParam (get_hwndClient (), PP_FONTNAMESIZE,
strlen (fontnamesize) + 1, fontnamesize);
}
MRESULT pmtxt::wm_paint (HWND hwnd, ULONG, MPARAM, MPARAM)
{
pmtxt_attr hps_attr;
_fmutex_checked_request (&mutex, _FMR_IGNINT);
HPS hps = WinBeginPaint (hwnd, NULLHANDLE, NULL);
setup_ps (hps);
query_hps_attr (hps, hps_attr);
painting (true);
GpiErase (hps);
// TODO: examine update region to find lines to be displayed
int line = y_off;
// Compute the number of lines to display
int count = window_lines;
if (count > lines_used - line)
count = lines_used - line;
tab_repaint = false;
for (int i = 0; i < count; ++i)
paint (hps, hps_attr, line++, 0, false);
painting (false);
WinEndPaint (hps);
if (tab_repaint)
repaint ();
else
pmthread_pending |= PMTHREAD_SBAR_X | PMTHREAD_SBAR_Y;
unlock ();
return 0;
}
// Side effect: tab_repaint
void pmtxt::paint (HPS hps, pmtxt_attr &cur_attr, int line, int column,
bool paint_eol)
{
int end;
POINTL ptl;
if (!get_bbox_pos (hps, &ptl, line, column))
return;
if (ptl.y < 0 || ptl.y >= cyClient)
return;
ptl.y += cyDesc;
const pmtxt_line *pline = &lines_vector[line];
char *p = pline->str + column;
if (pline->attr == NULL)
{
// Optimization
set_hps_attr (hps, cur_attr, default_attr);
GpiCharStringAt (hps, &ptl, pline->len - column, (PCH)p);
if (paint_eol)
{
// TODO
pmtxt_line_attr la;
la.el = E_FILL;
la.attr = default_attr;
paint (hps, cur_attr, &la, (char *)NULL, -1);
}
}
else
{
tab_index = 0;
const pmtxt_line_attr *a = pline->attr;
while (a->column < column)
{
if (a->next == NULL || a->next->column > column)
break;
if (a->el == E_TAB && a->next != NULL
&& a->next->column != EOL_COLUMN)
tab_index += a->next->column - a->column;
a = a->next;
}
if (column < a->column)
{
set_hps_attr (hps, cur_attr, default_attr);
end = a->column == EOL_COLUMN ? pline->len : a->column;
GpiCharStringAt (hps, &ptl, end - column, (PCH)p);
p += end - column;
column = end;
}
else
GpiMove (hps, &ptl);
while (a != NULL && a->column != EOL_COLUMN)
{
if (a->next == NULL || a->next->column == EOL_COLUMN)
end = pline->len;
else
end = a->next->column;
if (column < end)
paint (hps, cur_attr, a, p, end - column);
p += end - column; column = end;
a = a->next;
}
// TODO: Don't always clear to the end of the line (we need
// to remember the previous max x
if (a != NULL) // a->column == EOL_COLUMN
paint (hps, cur_attr, a, p, -1);
else if (paint_eol)
{
// TODO
pmtxt_line_attr la;
la.el = E_FILL;
la.attr = default_attr;
paint (hps, cur_attr, &la, (char *)NULL, -1);
}
}
POINTL ptl_end;
GpiQueryCurrentPosition (hps, &ptl_end);
int x = (ptl_end.x - (-x_off * cxChar) + cxChar - 1) / cxChar;
if (x > max_x_pos)
max_x_pos = x;
if (pline->flags & LF_UNDERLINE)
{
RECTL rcli;
rcli.xLeft = 0; rcli.xRight = cxClient;
rcli.yBottom = ptl.y - cyDesc;
rcli.yTop = rcli.yBottom + 1;
WinFillRect (hps, &rcli, default_attr.fg_color);
}
}
void pmtxt::paint (HPS hps, pmtxt_attr &cur_attr, const pmtxt_line_attr *pla,
const char *p, int len)
{
POINTL ptl;
RECTL rcli;
int x, add, i;
switch (pla->el)
{
case E_FILL:
// TODO: Use GpiBox
GpiQueryCurrentPosition (hps, &ptl);
rcli.xLeft = ptl.x;
// TODO: Width specified in string (pixels)
rcli.xRight = len == -1 ? cxClient : rcli.xLeft + len * cxChar;
rcli.yBottom = ptl.y - cyDesc;
rcli.yTop = rcli.yBottom + cyChar;
WinFillRect (hps, &rcli, pla->attr.bg_color);
if (len != -1)
{
ptl.x = rcli.xRight;
GpiMove (hps, &ptl);
}
break;
case E_TEXT:
set_hps_attr (hps, cur_attr, pla->attr);
GpiCharString (hps, len, (PCH)p);
break;
case E_VRULE:
// TODO: Use GpiBox
GpiQueryCurrentPosition (hps, &ptl);
rcli.xLeft = ptl.x;
rcli.xRight = rcli.xLeft + rule_width;
rcli.yBottom = ptl.y - cyDesc;
rcli.yTop = rcli.yBottom + cyChar;
// Note: CLR_DEFAULT does not work (background vs. foreground)
WinFillRect (hps, &rcli, pla->attr.fg_color);
ptl.x += rule_width;
GpiMove (hps, &ptl);
break;
case E_TAB:
// TODO: Use GpiBox
GpiQueryCurrentPosition (hps, &ptl);
x = ptl.x + x_off * cxChar; add = 0;
while (len != 0)
{
if (x > tab_x[tab_index])
{
tab_repaint = true;
add += x - tab_x[tab_index];
for (i = tab_index; i < tab_count; ++i)
tab_x[i] += add;
}
x = tab_x[tab_index];
++tab_index; --len;
}
rcli.xLeft = ptl.x;
rcli.xRight = x - x_off * cxChar;
rcli.yBottom = ptl.y - cyDesc;
rcli.yTop = rcli.yBottom + cyChar;
WinFillRect (hps, &rcli, pla->attr.bg_color);
ptl.x = rcli.xRight;
GpiMove (hps, &ptl);
break;
default:
break;
}
}
void pmtxt::sync ()
{
if (output_pending)
repaint ();
}
// TODO: Cache current scrollbar status to avoid WinSendMsg if nothing changed
static void check_size (int new_value, int target, HWND sbar, int *off)
{
int m = target - new_value;
if (m > 0)
{
WinEnableWindow (sbar, TRUE);
if (*off > m)
*off = m;
WinSendMsg (sbar, SBM_SETSCROLLBAR,
MPFROMSHORT (*off), MPFROM2SHORT (0, MAX (0, (SHORT)m)));
WinSendMsg (sbar, SBM_SETTHUMBSIZE,
MPFROM2SHORT ((USHORT)new_value, (USHORT)target), 0);
}
else
{
WinEnableWindow (sbar, FALSE);
*off = 0;
}
}
void pmtxt::check_x_size ()
{
if (output_stopped)
pmthread_pending |= PMTHREAD_SBAR_X;
else if (same_tid ())
{
// Note that check_y_size() might be called recursively because
// check_size() calls WinSendMsg
unsigned rc = _fmutex_request (&mutex, _FMR_IGNINT | _FMR_NOWAIT);
if (rc != 0)
{
pmthread_pending |= PMTHREAD_SBAR_X;
// Avoid time window
rc = _fmutex_request (&mutex, _FMR_IGNINT | _FMR_NOWAIT);
}
if (rc == 0)
{
check_size (window_columns, max_x_pos, hwndHbar, &x_off);
unlock ();
}
}
else
pmthread_request (PMTHREAD_SBAR_X);
}
void pmtxt::check_y_size ()
{
if (output_stopped)
pmthread_pending |= PMTHREAD_SBAR_Y;
else if (same_tid ())
{
// Note that check_y_size() might be called recursively because
// check_size() calls WinSendMsg
unsigned rc = _fmutex_request (&mutex, _FMR_IGNINT | _FMR_NOWAIT);
if (rc != 0)
{
pmthread_pending |= PMTHREAD_SBAR_Y;
// Avoid time window
rc = _fmutex_request (&mutex, _FMR_IGNINT | _FMR_NOWAIT);
}
if (rc == 0)
{
check_size (window_lines, lines_used, hwndVbar, &y_off);
unlock ();
}
}
else
pmthread_request (PMTHREAD_SBAR_Y);
}
void pmtxt::painting (bool)
{
}
void pmtxt::button_event (int, int, int, int, int)
{
}
MRESULT pmtxt::button_event (HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2,
int button, int clicks)
{
if (WinQueryFocus (HWND_DESKTOP) == hwnd)
{
int line, column, tab;
_fmutex_checked_request (&mutex, _FMR_IGNINT);
line_column_from_x_y (hpsClient, &line, &column, &tab,
(USHORT)SHORT1FROMMP (mp1),
(USHORT)SHORT2FROMMP (mp1));
unlock ();
button_event (line, column, tab, button, clicks);
return (MRESULT)TRUE;
}
// Continue processing -- make window active.
return WinDefWindowProc (hwnd, msg, mp1, mp2);
}
MRESULT pmtxt::wm_size (HWND, ULONG, MPARAM, MPARAM mp2)
{
cxClient = SHORT1FROMMP (mp2);
cyClient = SHORT2FROMMP (mp2);
change_size ();
return 0;
}
void pmtxt::change_size ()
{
window_lines = (int)(cyClient / cyChar);
window_columns = (int)(cxClient / cxChar);
if (cyClient != window_lines * cyChar && !get_minimized ())
{
// I don't like calling WinSetWindowPos from WM_SIZE processing;
// however, there seems to be no other way which works with
// FCF_SHELLPOSITION (WM_ADJUSTWINDOWPOS does not work)
SWP swp;
WinQueryWindowPos (get_hwndClient (), &swp);
POINTL ptl;
ptl.x = swp.x;
ptl.y = swp.y + cyClient;
WinMapWindowPoints (get_hwndFrame (), HWND_DESKTOP, &ptl, 1);
RECTL rcl;
rcl.xLeft = ptl.x;
rcl.xRight = rcl.xLeft + cxClient;
rcl.yTop = ptl.y;
rcl.yBottom = rcl.yTop - window_lines * cyChar;
WinCalcFrameRect (get_hwndFrame (), &rcl, FALSE);
// This causes recursion on WM_SIZE
WinSetWindowPos (get_hwndFrame (), HWND_TOP, rcl.xLeft, rcl.yBottom,
rcl.xRight - rcl.xLeft, rcl.yTop - rcl.yBottom,
SWP_SIZE | SWP_MOVE);
}
check_x_size ();
check_y_size ();
}
MRESULT pmtxt::wm_button (HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
switch (msg)
{
case WM_BUTTON1DOWN:
return button_event (hwnd, msg, mp1, mp2, 1, 0);
case WM_BUTTON2DOWN:
return button_event (hwnd, msg, mp1, mp2, 2, 0);
case WM_BUTTON3DOWN:
return button_event (hwnd, msg, mp1, mp2, 3, 0);
case WM_BUTTON1CLICK:
return button_event (hwnd, msg, mp1, mp2, 1, 1);
case WM_BUTTON2CLICK:
return button_event (hwnd, msg, mp1, mp2, 2, 1);
case WM_BUTTON3CLICK:
return button_event (hwnd, msg, mp1, mp2, 3, 1);
case WM_BUTTON1DBLCLK:
return button_event (hwnd, msg, mp1, mp2, 1, 2);
case WM_BUTTON2DBLCLK:
return button_event (hwnd, msg, mp1, mp2, 2, 2);
case WM_BUTTON3DBLCLK:
return button_event (hwnd, msg, mp1, mp2, 3, 2);
}
return 0;
}
MRESULT pmtxt::wm_vscroll (HWND, ULONG, MPARAM, MPARAM mp2)
{
int n, m, diff;
m = lines_used - window_lines;
switch (SHORT2FROMMP (mp2))
{
case SB_LINEUP:
n = y_off - 1; break;
case SB_LINEDOWN:
n = y_off + 1; break;
case SB_PAGEUP:
n = y_off - window_lines; break;
case SB_PAGEDOWN:
n = y_off + window_lines; break;
case SB_SLIDERTRACK:
disable_output ();
if (!track)
return 0;
n = SHORT1FROMMP (mp2);
break;
case SB_SLIDERPOSITION:
enable_output ();
n = SHORT1FROMMP (mp2);
break;
default:
return 0;
}
if (n > m) n = m;
if (n < 0) n = 0;
diff = n - y_off;
if (diff > 0)
{
y_off = n;
scroll_up (diff, true);
}
else if (diff < 0)
{
y_off = n;
scroll_down (-diff);
}
if (SHORT2FROMMP (mp2) != SB_SLIDERTRACK)
WinSendMsg (hwndVbar, SBM_SETSCROLLBAR, MPFROMSHORT (y_off),
MPFROM2SHORT (0, (SHORT)m));
return 0;
}
MRESULT pmtxt::wm_hscroll (HWND, ULONG, MPARAM, MPARAM mp2)
{
int n, m;
m = max_x_pos - window_columns;
switch (SHORT2FROMMP (mp2))
{
case SB_LINELEFT:
n = x_off - 1; break;
case SB_LINERIGHT:
n = x_off + 1; break;
case SB_PAGELEFT:
n = x_off - window_columns; break;
case SB_PAGERIGHT:
n = x_off + window_columns; break;
case SB_SLIDERTRACK:
disable_output ();
if (!track)
return 0;
n = SHORT1FROMMP (mp2);
break;
case SB_SLIDERPOSITION:
enable_output ();
n = SHORT1FROMMP (mp2);
break;
default:
return 0;
}
if (n > m) n = m;
if (n < 0) n = 0;
if (n != x_off)
{
// TODO: use GpiBitBlt
x_pos += (x_off - n) * cxChar;
x_off = n;
repaint ();
}
if (SHORT2FROMMP (mp2) != SB_SLIDERTRACK)
WinSendMsg (hwndHbar, SBM_SETSCROLLBAR, MPFROMSHORT (x_off),
MPFROM2SHORT (0, (SHORT)m));
return 0;
}
MRESULT pmtxt::wm_presparamchanged (HWND hwnd, ULONG msg,
MPARAM mp1, MPARAM mp2)
{
parent::wm_presparamchanged (hwnd, msg, mp1, mp2);
if (LONGFROMMP (mp1) == PP_FONTNAMESIZE && !initializing_font)
{
_fmutex_checked_request (&mutex, _FMR_IGNINT);
default_font = true;
WinReleasePS (hpsClient);
create_hpsClient ();
query_hps_attr (hpsClient, hpsClient_attr);
cache_line = -1;
unlock ();
clear_tabs ();
change_size ();
repaint ();
}
return 0;
}
void pmtxt::font_changed ()
{
}
bool pmtxt::font_dialog ()
{
FONTMETRICS fm;
FONTDLG fd;
UCHAR family_name[FACESIZE+1];
LONG pel_per_inch;
SIZEF charbox;
_fmutex_checked_request (&mutex, _FMR_IGNINT);
HDC hdc = GpiQueryDevice (hpsClient);
bool ok = (GpiQueryFontMetrics (hpsClient, (LONG)sizeof (fm), &fm)
&& DevQueryCaps (hdc, CAPS_VERTICAL_FONT_RES, 1, &pel_per_inch)
&& GpiQueryCharBox (hpsClient, &charbox)
&& pel_per_inch != 0);
unlock ();
if (!ok)
{
WinAlarm (HWND_DESKTOP, WA_ERROR);
return false;
}
memcpy (family_name, fm.szFamilyname, FACESIZE);
family_name[FACESIZE] = 0;
memset (&fd, 0, sizeof (fd));
fd.cbSize = sizeof (fd);
fd.hpsScreen = hpsClient;
fd.hpsPrinter = NULLHANDLE;
fd.pszTitle = (PSZ)"Font";
fd.pszPreview = NULL;
fd.pszPtSizeList = NULL;
fd.pfnDlgProc = NULL;
fd.pszFamilyname = family_name;
if (fixed_pitch)
fd.fxPointSize = MAKEFIXED (fm.sNominalPointSize, 0) / 10;
else
fd.fxPointSize = (72 * charbox.cy + pel_per_inch / 2) / pel_per_inch;
fd.fl = (FNTS_HELPBUTTON | FNTS_RESETBUTTON | FNTS_CENTER
| FNTS_INITFROMFATTRS);
fd.flFlags = 0;
fd.flType = fm.fsType;
fd.flTypeMask = 0;
fd.flStyle = 0;
fd.flStyleMask = 0;
fd.clrFore = CLR_BLACK;
fd.clrBack = CLR_WHITE;
fd.ulUser = 0;
fd.lEmHeight = fm.lEmHeight;
fd.lXHeight = fm.lXHeight;
fd.lExternalLeading = fm.lExternalLeading;
fd.fAttrs.usRecordLength = sizeof (FATTRS);
fd.fAttrs.fsSelection = fm.fsSelection;
fd.fAttrs.lMatch = fm.lMatch;
memcpy (fd.fAttrs.szFacename, fm.szFacename, FACESIZE);
fd.fAttrs.idRegistry = fm.idRegistry;
fd.fAttrs.usCodePage = fm.usCodePage;
fd.fAttrs.lMaxBaselineExt = fm.lMaxBaselineExt;
fd.fAttrs.lAveCharWidth = fm.lAveCharWidth;
fd.fAttrs.fsType = fm.fsType;
fd.fAttrs.fsFontUse = 0; // TODO?
fd.sNominalPointSize = fm.sNominalPointSize;
fd.usWeight = fm.usWeightClass;
fd.usWidth = fm.usWidthClass;
fd.usFamilyBufLen = FACESIZE;
if (WinFontDlg (HWND_DESKTOP, get_hwndClient (), &fd) == NULLHANDLE)
WinAlarm (HWND_DESKTOP, WA_ERROR);
else if (fd.lReturn == DID_OK)
{
fd.fAttrs.fsFontUse = FATTR_FONTUSE_NOMIX;
charbox.cx = charbox.cy = (fd.fxPointSize * pel_per_inch + 72/2) / 72;
_fmutex_checked_request (&mutex, _FMR_IGNINT);
if (GpiCreateLogFont (hpsClient, NULL, 1, &fd.fAttrs) != FONT_MATCH
|| !GpiSetCharSet (hpsClient, 1)
|| !GpiSetCharBox (hpsClient, &charbox))
{
unlock ();
WinAlarm (HWND_DESKTOP, WA_ERROR);
}
else
{
fattrs = fd.fAttrs;
default_font = false;
get_fontmetrics ();
cache_line = -1;
unlock ();
clear_tabs ();
change_size ();
font_changed ();
repaint ();
return true;
}
}
return false;
}
void pmtxt::unlock ()
{
_fmutex_checked_release (&mutex);
pmthread_perform ();
}
void pmtxt::pmthread_request (unsigned char bits)
{
if (bits & ~pmthread_pending)
{
pmthread_pending |= bits;
WinPostMsg (get_hwndClient (), UWM_PMTHREAD, 0, 0);
}
}
void pmtxt::pmthread_perform ()
{
if (same_tid ())
{
// TODO: Atomic
unsigned char x = pmthread_pending;
pmthread_pending = 0;
if (x & PMTHREAD_SBAR_X)
check_x_size ();
if (x & PMTHREAD_SBAR_Y)
check_y_size ();
}
else if (pmthread_pending != 0)
WinPostMsg (get_hwndClient (), UWM_PMTHREAD, 0, 0);
}
bool pmtxt::wm_user (HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
if (parent::wm_user (hwnd, msg, mp1, mp2))
return true;
switch (msg)
{
case UWM_PMTHREAD:
pmthread_perform ();
return true;
default:
return false;
}
}
// Fill-in TRACKINFO structure for resizing or moving a window.
// This function is called from the frame window procedure.
MRESULT pmtxt::wm_querytrackinfo (HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
// First call the normal frame window procedure to set up the
// structure.
MRESULT mr = get_old_frame_msg () (hwnd, msg, mp1, mp2);
// If we're resizing the window, set the grid.
USHORT fs = SHORT1FROMMP (mp1);
if (((fs & TF_MOVE) != TF_MOVE)
&& ((fs & TF_MOVE) || (fs & TF_SETPOINTERPOS))
&& cxChar != 0 && cyChar != 0)
{
PTRACKINFO pti = (PTRACKINFO)mp2;
pti->fs |= TF_GRID;
if (fixed_pitch)
pti->cxGrid = pti->cxKeyboard = cxChar;
else
pti->cxGrid = pti->cxKeyboard = 1;
pti->cyGrid = pti->cyKeyboard = cyChar;
}
return mr;
}
void pmtxt::delete_lines (int line, int count, bool do_paint)
{
_fmutex_checked_request (&mutex, _FMR_IGNINT);
if (line >= 0 && line < lines_used && count >= 1)
{
if (line + count > lines_used)
count = lines_used - line;
if (cache_line >= line)
cache_line = -1;
int i;
for (i = 0; i < count; ++i)
free_line (line + i);
for (i = line; i < lines_used - count; ++i)
lines_vector[i] = lines_vector[i+count];
for (i = lines_used - count; i < lines_used; ++i)
init_line (i);
lines_used -= count;
int first = line - y_off;
if (first < 0) first = 0;
if (y_off > lines_used - window_lines)
{
y_off = lines_used - window_lines;
if (y_off < 0)
y_off = 0;
// TODO: Use GpiBitBlt?
if (do_paint && !output_stopped)
repaint ();
else
output_pending = true;
}
else if (first < window_lines)
{
if (!(do_paint && !output_stopped))
output_pending = true;
else if (first + count >= window_lines)
{
// No scrolling, just overwrite the last line(s)
// Clear first because paint() does nothing for lines
// beyond the current end of lines_vector[]
int n = window_lines - first;
RECTL rcli;
rcli.xLeft = 0; rcli.xRight = cxClient;
rcli.yBottom = 0; rcli.yTop = n * cyChar;
painting (true);
WinFillRect (hpsClient, &rcli, CLR_BACKGROUND);
// Repaint the last line(s)
for (i = 0; i < n; ++i)
paint (hpsClient, hpsClient_attr,
window_lines - n + i + y_off, 0, false);
painting (false);
}
else
{
RECTL rcli, rcl, rcl_update, rcl_dummy;
rcli.xLeft = 0; rcli.xRight = cxClient;
rcli.yBottom = 0; rcli.yTop = cyClient - first * cyChar;
rcl.xLeft = rcli.xLeft; rcl.xRight = rcli.xRight - 1;
rcl.yBottom = rcli.yBottom; rcl.yTop = rcli.yTop - 1;
if (!output_pending
&& GpiRectVisible (hpsClient, &rcl) == RVIS_VISIBLE
&& (!WinQueryUpdateRect (get_hwndClient (), &rcl_update)
|| !WinIntersectRect (get_hab (), &rcl_dummy,
&rcl_update, &rcli)))
{
// Scroll
POINTL aptl[3];
aptl[0].x = 0; aptl[0].y = count * cyChar;
aptl[1].x = cxClient; aptl[1].y = cyClient - first * cyChar;
aptl[2].x = 0; aptl[2].y = 0;
painting (true);
GpiBitBlt (hpsClient, hpsClient, 3, aptl, ROP_SRCCOPY,
BBO_IGNORE);
// Clear the last line(s)
rcli.xLeft = 0; rcli.xRight = cxClient;
rcli.yBottom = 0; rcli.yTop = count * cyChar;
WinFillRect (hpsClient, &rcli, CLR_BACKGROUND);
// Repaint the last line(s)
for (i = 0; i < count; ++i)
paint (hpsClient, hpsClient_attr,
window_lines - count + i + y_off, 0, false);
painting (false);
}
else
{
// TODO: Don't have to repaint all the lines
repaint ();
}
}
}
}
unlock ();
check_y_size ();
}
void pmtxt::insert_lines (int line, int count, bool do_paint)
{
_fmutex_checked_request (&mutex, _FMR_IGNINT);
if (line >= 0 && line <= lines_used && count >= 1)
{
if (lines_used + count > lines_alloc)
more_lines (lines_used + count);
if (cache_line >= line)
cache_line = -1;
if (line == lines_used)
lines_used += count;
else
{
int i;
for (i = 0; i < count; ++i)
free_line (lines_used + i);
for (i = lines_used - 1; i >= line; --i)
lines_vector[i+count] = lines_vector[i];
for (i = 0; i < count; ++i)
init_line (line + i);
lines_used += count;
int first = line - y_off;
if (first < 0) first = 0;
if (first < window_lines)
{
if (!(do_paint && !output_stopped))
output_pending = true;
else if (first + count >= window_lines)
{
// No scrolling, just clear the inserted line(s)
int n = window_lines - first;
RECTL rcli;
rcli.xLeft = 0; rcli.xRight = cxClient;
rcli.yBottom = 0; rcli.yTop = n * cyChar;
painting (true);
WinFillRect (hpsClient, &rcli, CLR_BACKGROUND);
painting (false);
}
else
{
RECTL rcli, rcl, rcl_update, rcl_dummy;
rcli.xLeft = 0; rcli.xRight = cxClient;
rcli.yBottom = 0; rcli.yTop = cyClient - first * cyChar;
rcl.xLeft = rcli.xLeft; rcl.xRight = rcli.xRight - 1;
rcl.yBottom = rcli.yBottom; rcl.yTop = rcli.yTop - 1;
if (!output_pending
&& GpiRectVisible (hpsClient, &rcl) == RVIS_VISIBLE
&& (!WinQueryUpdateRect (get_hwndClient (), &rcl_update)
|| !WinIntersectRect (get_hab (), &rcl_dummy,
&rcl_update, &rcli)))
{
// Scroll
POINTL aptl[3];
aptl[0].x = 0; aptl[0].y = 0;
aptl[1].x = cxClient;
aptl[1].y = (window_lines - first - count) * cyChar;
aptl[2].x = 0; aptl[2].y = count * cyChar;
painting (true);
GpiBitBlt (hpsClient, hpsClient, 3, aptl, ROP_SRCCOPY,
BBO_IGNORE);
// Clear the inserted line(s)
rcli.xLeft = 0; rcli.xRight = cxClient;
rcli.yTop = cyClient - first * cyChar;
rcli.yBottom = rcl.yTop - count * cyChar;
WinFillRect (hpsClient, &rcli, CLR_BACKGROUND);
painting (false);
}
else
{
// TODO: Don't have to repaint all the lines
repaint ();
}
}
}
}
}
unlock ();
check_y_size ();
}
void pmtxt::clear_lines (int line, int count, bool do_paint)
{
_fmutex_checked_request (&mutex, _FMR_IGNINT);
if (line >= 0 && line < lines_used && count >= 1)
{
if (line + count > lines_used)
count = lines_used - line;
if (cache_line >= line && cache_line < line + count)
cache_line = -1;
for (int i = 0; i < count; ++i)
{
lines_vector[line + i].len = 0;
delete_attr (lines_vector[line + i].attr);
lines_vector[line + i].attr = NULL;
}
int first = line - y_off;
if (first < window_lines && first + count > 0)
{
if (first < 0)
{
count += first;
first = 0;
}
if (first + count > window_lines)
count = window_lines - first;
if (do_paint && !output_stopped)
{
RECTL rcli;
rcli.xLeft = 0; rcli.xRight = cxClient;
rcli.yTop = cyClient - first * cyChar;
rcli.yBottom = rcli.yTop - count * cyChar;
painting (true);
WinFillRect (hpsClient, &rcli, CLR_BACKGROUND);
painting (false);
}
else
output_pending = true;
}
}
unlock ();
}
void pmtxt::truncate_line (int line, int count, bool do_paint)
{
_fmutex_checked_request (&mutex, _FMR_IGNINT);
if (line >= 0 && line < lines_used)
{
pmtxt_line *pline = &lines_vector[line];
if (pline->len > count)
{
pmtxt_line_attr **pa = &pline->attr;
while (*pa != NULL && (*pa)->column <= count)
pa = &(*pa)->next;
pmtxt_line_attr *a = *pa, *next;
while (a != NULL && a->column != EOL_COLUMN)
{
next = a->next;
delete a;
a = next;
}
*pa = a;
pline->len = count;
if (cache_line == line && cache_column > count)
cache_line = -1;
if (do_paint)
paint (hpsClient, hpsClient_attr, line, count, true);
}
}
unlock ();
}
// Return true iff the caller is responsible for painting the area
// becoming free
bool pmtxt::scroll_up (int n, bool do_paint)
{
RECTL rcl, rcli, rcl_update, rcl_dummy;
if (n >= window_lines || output_pending)
{
if (!do_paint)
cache_line = -1;
repaint ();
output_pending = false;
return false;
}
rcli.xLeft = 0; rcli.xRight = cxClient;
rcli.yBottom = 0; rcli.yTop = cyClient - n * cyChar;
rcl.xLeft = 0; rcl.xRight = cxClient - 1;
rcl.yBottom = 0; rcl.yTop = cyClient - n * cyChar - 1;
if (GpiRectVisible (hpsClient, &rcl) == RVIS_VISIBLE
&& (!WinQueryUpdateRect (get_hwndClient (), &rcl_update)
|| !WinIntersectRect (get_hab (), &rcl_dummy, &rcl_update, &rcli)))
{
// Source rectangle completely visible and not to be updated; scroll!
POINTL aptl[3];
aptl[0].x = 0; aptl[0].y = n * cyChar;
aptl[1].x = cxClient; aptl[1].y = cyClient;
aptl[2].x = 0; aptl[2].y = 0;
painting (true);
GpiBitBlt (hpsClient, hpsClient, 3, aptl, ROP_SRCCOPY, BBO_IGNORE);
rcli.xLeft = 0; rcli.xRight = cxClient;
rcli.yBottom = 0; rcli.yTop = n * cyChar;
if (do_paint)
{
painting (false);
repaint (&rcli);
return false;
}
else
{
WinFillRect (hpsClient, &rcli, CLR_BACKGROUND);
painting (false);
return true;
}
}
else
{
if (!do_paint)
cache_line = -1;
repaint ();
return false;
}
}
void pmtxt::scroll_down (int n)
{
RECTL rcl, rcli, rcl_update, rcl_dummy;
if (n >= window_lines || output_pending)
{
repaint ();
output_pending = false;
return;
}
rcli.xLeft = 0; rcli.xRight = cxClient;
rcli.yBottom = n * cyChar; rcli.yTop = cyClient;
rcl.xLeft = 0; rcl.xRight = cxClient - 1;
rcl.yBottom = n * cyChar; rcl.yTop = cyClient - 1;
if (GpiRectVisible (hpsClient, &rcl) == RVIS_VISIBLE
&& (!WinQueryUpdateRect (get_hwndClient (), &rcl_update)
|| !WinIntersectRect (get_hab (), &rcl_dummy, &rcl_update, &rcli)))
{
// Source rectangle completely visible and not to be updated; scroll!
POINTL aptl[3];
aptl[0].x = 0; aptl[0].y = 0;
aptl[1].x = cxClient; aptl[1].y = cyClient - n * cyChar;
aptl[2].x = 0; aptl[2].y = n * cyChar;
painting (true);
GpiBitBlt (hpsClient, hpsClient, 3, aptl, ROP_SRCCOPY, BBO_IGNORE);
painting (false);
rcli.xLeft = 0; rcli.xRight = cxClient;
rcli.yTop = cyClient; rcli.yBottom = cyClient - n * cyChar;
repaint (&rcli);
}
else
repaint ();
}
void pmtxt::disable_output ()
{
output_stopped = true;
}
void pmtxt::enable_output ()
{
output_stopped = false;
if (output_pending)
{
output_pending = false;
repaint ();
}
}
void pmtxt::show_line (int line, int threshold, int overshoot)
{
int n;
if (overshoot < threshold)
overshoot = threshold;
if (line - threshold < y_off)
{
line = line >= overshoot ? line - overshoot : 0;
n = y_off - line;
y_off = line;
scroll_down (n);
check_y_size ();
}
else if (line + threshold >= y_off + window_lines)
{
line = line + overshoot < lines_used ? line + overshoot : lines_used - 1;
n = line - (y_off + window_lines - 1);
y_off = line - (window_lines - 1);
scroll_up (n, true);
check_y_size ();
}
}
// TODO: E_TEXT only
int pmtxt::get_char (int line, int column)
{
if (line < 0 || line >= lines_used || column < 0)
return -1;
if (column >= lines_vector[line].len)
return -1;
return (unsigned char)lines_vector[line].str[column];
}
// TODO: E_TEXT only
bool pmtxt::get_string (int line, int column, char *str, size_t count)
{
if (line < 0 || line >= lines_used || column < 0)
return false;
if (column >= lines_vector[line].len)
count = 0;
else if (column + (int)count > lines_vector[line].len)
count = lines_vector[line].len - column;
memcpy (str, lines_vector[line].str + column, count);
str[count] = 0;
return true;
}
void pmtxt::clear_tabs ()
{
_fmutex_checked_request (&mutex, _FMR_IGNINT);
for (int i = 0; i < tab_count;++i)
tab_x[i] = 0;
unlock ();
}
bool pmtxt::get_bbox_pos (HPS hps, POINTL *pptl, int line, int column)
{
if (line >= lines_used)
return false;
int x = -1;
int line_len = lines_vector[line].len;
if (column == 0)
x = 0;
else if (line == cache_line && column == cache_column)
x = cache_x;
if (column > line_len)
return false;
if (x == -1)
{
POINTL ptl; ptl.x = 0; ptl.y = 0;
char *p = lines_vector[line].str;
const pmtxt_line_attr *a = lines_vector[line].attr;
// TODO: Use extra PS when adding fonts
// TODO: Start at cache_column if possible
if (a == NULL || column <= a->column)
{
if (fixed_pitch)
x = ptl.x + column * cxChar;
else
{
GpiQueryCharStringPosAt (hps, &ptl, 0, column, (PCH)p,
NULL, aptl_line);
x = aptl_line[column].x;
}
}
else
{
int cur_column = 0, t = 0, end, len;
x = 0;
if (a->column != 0)
{
if (fixed_pitch)
x += a->column * cxChar;
else
{
GpiQueryCharStringPosAt (hps, &ptl, 0, a->column,
(PCH)p, NULL, aptl_line);
x += aptl_line[a->column].x;
}
cur_column = a->column;
}
while (a != NULL && cur_column != column)
{
// TODO: Don't skip past font changes
while (a->next != NULL && a->next->el == a->el)
{
if (a->el == E_TAB && a->next->column != EOL_COLUMN)
t += a->next->column - a->column;
a = a->next;
}
if (a->column == EOL_COLUMN || a->next == NULL)
end = line_len;
else
end = a->next->column;
if (column < end)
len = column - cur_column;
else
len = end - cur_column;
switch (a->el)
{
case E_TEXT:
if (fixed_pitch)
x += len * cxChar;
else
{
GpiQueryCharStringPosAt (hps, &ptl, 0, len,
(PCH)p + cur_column, NULL,
aptl_line);
x += aptl_line[len].x;
}
break;
case E_FILL:
// TODO: Width specified in string (pixels)
x += len * cxChar;
break;
case E_VRULE:
x += rule_width;
break;
case E_TAB:
t += len;
x = tab_x[t-1];
break;
default:
break;
}
a = a->next;
cur_column += len;
}
}
cache_line = line; cache_column = column; cache_x = x;
}
pptl->x = x - x_off * cxChar;
pptl->y = cyClient - cyChar * (line + 1 - y_off);
return true;
}
// This is the reverse of get_bbox_pos()
// TODO: Use cache
void pmtxt::line_column_from_x_y (HPS hps, int *out_line, int *out_column,
int *out_tab, int x, int y)
{
int line = (cyClient - 1 - y) / cyChar + y_off;
if (line >= 0 && line < lines_used)
{
*out_line = line;
pmtxt_line *pline = &lines_vector[line];
POINTL ptl;
ptl.x = -x_off * cxChar; ptl.y = 0;
if (x >= ptl.x)
{
const pmtxt_line_attr *a = pline->attr;
pmtxt_line_attr temp;
if (a == NULL || a->column > 0)
{
temp.el = E_TEXT;
temp.next = (pmtxt_line_attr *)a; // Remove `const'
temp.column = 0;
a = &temp;
}
int tab_index = 0;
while (a != NULL && a->column != EOL_COLUMN)
{
int len;
if (a->next != NULL && a->next->column != EOL_COLUMN)
len = a->next->column - a->column;
else
len = pline->len - a->column;
if (a->el == E_TEXT)
{
if (fixed_pitch)
{
if (x < ptl.x + len * cxChar)
{
*out_column = a->column + (x - ptl.x) / cxChar;
*out_tab = tab_index;
return;
}
ptl.x += len * cxChar;
}
else
{
GpiQueryCharStringPosAt (hps, &ptl, 0, len,
(PCH)pline->str + a->column,
NULL, aptl_line);
for (int i = 0; i < len; ++i)
if (x < aptl_line[i+1].x)
{
*out_column = a->column + i;
*out_tab = tab_index;
return;
}
ptl.x = aptl_line[len].x;
}
}
else if (a->el == E_VRULE)
{
if (x < ptl.x + rule_width)
{
*out_column = a->column;
*out_tab = tab_index;
return;
}
ptl.x += rule_width;
}
else if (a->el == E_FILL)
{
if (x < ptl.x + len * cxChar)
{
*out_column = a->column + (x - ptl.x) / cxChar;
*out_tab = tab_index;
return;
}
ptl.x += len * cxChar;
}
else if (a->el == E_TAB)
{
if (x < tab_x[tab_index])
{
*out_column = a->column;
*out_tab = tab_index;
return;
}
ptl.x = tab_x[tab_index++] - x_off * cxChar;
}
a = a->next;
}
}
}
else
*out_line = -1;
*out_column = -1;
*out_tab = -1;
}
//
// Add text
//
// Note that this function is not a no-op with LEN==0: it may add
// an empty line at the end, for the cursor.
void pmtxt::put (int line, int column, size_t len, const char *s,
const pmtxt_attr &attr, element el, bool do_paint)
{
int lines_added;
if (line < 0 || column < 0)
return;
_fmutex_checked_request (&mutex, _FMR_IGNINT);
if (line >= lines_alloc)
more_lines (line + 1);
pmtxt_line *pline = &lines_vector[line];
int tlen = pline->len;
if (column > tlen)
{
unlock ();
return;
}
if (line >= lines_used)
{
lines_added = line + 1 - lines_used;
lines_used = line + 1;
}
else
lines_added = 0;
if (column + (int)len > max_line_len)
{
len = max_line_len - column;
if (len == 0)
{
unlock ();
return;
}
}
if (column + (int)len > pline->alloc)
{
int new_alloc = ((column + len + 39) / 40) * 40;
char *t = new char[new_alloc];
memcpy (t, pline->str, tlen);
delete[] pline->str;
pline->alloc = new_alloc;
pline->str = t;
}
char *target = pline->str;
memcpy (target + column, s, len);
if (len != 0)
{
if (column + (int)len > tlen)
pline->len = column + len;
change_attr (pline, column, column + (int)len, attr, el);
if (line == cache_line && column < cache_column)
cache_line = -1;
}
if (do_paint && !output_stopped)
{
if (lines_added != 0 && lines_used > window_lines)
{
if (y_off + lines_added == lines_used - window_lines)
{
y_off += lines_added;
bool paint_after_scroll = scroll_up (lines_added, false);
check_y_size ();
if (!paint_after_scroll)
{
unlock ();
return;
}
}
else
check_y_size ();
}
if (len != 0)
{
tab_repaint = false;
// TODO: Set do_paint to true only if required
painting (true);
paint (hpsClient, hpsClient_attr, line, column, true);
painting (false);
if (tab_repaint)
repaint ();
}
}
else
{
if (output_stopped && (lines_added || len != 0))
output_pending = true;
}
unlock ();
}
//
// Change attributes of existing text
//
void pmtxt::put (int line, int column, size_t len, const pmtxt_attr &attr,
bool do_paint)
{
if (line < 0 || line >= lines_used || column < 0)
return;
_fmutex_checked_request (&mutex, _FMR_IGNINT);
pmtxt_line *pline = &lines_vector[line];
int tlen = pline->len;
if (column + (int)len > tlen)
{
if (column >= tlen)
len = 0;
else
len = tlen - column;
}
if (len == 0)
{
unlock ();
return;
}
change_attr (pline, column, column + (int)len, attr, E_KEEP);
// TODO: Invalidate cache if font width changed
if (do_paint && !output_stopped)
{
tab_repaint = false;
painting (true);
paint (hpsClient, hpsClient_attr, line, column, true);
painting (false);
if (tab_repaint)
repaint ();
}
else
{
if (output_stopped)
output_pending = true;
}
unlock ();
}
void pmtxt::set_eol_attr (int line, const pmtxt_attr &attr, bool do_paint)
{
_fmutex_checked_request (&mutex, _FMR_IGNINT);
if (line >= 0 && line < lines_used)
{
pmtxt_line_attr **ppa;
for (ppa = &lines_vector[line].attr; *ppa != NULL; ppa = &(*ppa)->next)
if ((*ppa)->column == EOL_COLUMN)
break;
bool change = false;
if (*ppa == NULL)
{
if (!eq_attr (attr, default_attr))
{
pmtxt_line_attr *a = new pmtxt_line_attr;
a->next = NULL;
a->column = EOL_COLUMN;
a->el = E_FILL;
a->attr = attr;
*ppa = a;
change = true;
}
}
else
{
if (eq_attr (attr, default_attr))
{
pmtxt_line_attr *a = *ppa;
*ppa = NULL;
delete a;
change = true;
}
else if (!eq_attr (attr, (*ppa)->attr))
{
(*ppa)->attr = attr;
change = true;
}
}
if (change)
{
if (do_paint && !output_stopped)
{
POINTL ptl;
RECTL rcli;
if (!get_bbox_pos (hpsClient, &ptl, line,
lines_vector[line].len))
abort ();
rcli.xLeft = ptl.x; rcli.xRight = cxClient;
rcli.yBottom = ptl.y; rcli.yTop = ptl.y + cyChar;
painting (true);
WinFillRect (hpsClient, &rcli, attr.bg_color);
painting (false);
}
else
output_pending = true;
}
}
unlock ();
}
// TODO: underline off, underline to end of line, underline to width of window
// TODO: attribute
void pmtxt::underline (int line, bool on, bool do_paint)
{
_fmutex_checked_request (&mutex, _FMR_IGNINT);
if (line >= 0 && line < lines_used
&& (bool)(lines_vector[line].flags & LF_UNDERLINE) != on)
{
if (on)
lines_vector[line].flags |= LF_UNDERLINE;
else
lines_vector[line].flags &= ~LF_UNDERLINE;
if (do_paint && !output_stopped)
{
painting (true);
paint (hpsClient, hpsClient_attr, line, 0, true);
painting (false);
}
}
unlock ();
}
struct change_data
{
pmtxt_line_attr **add;
pmtxt_line_attr *last;
pmtxt_attr cur_attr;
pmtxt::element cur_el;
};
void change_at (change_data &c, pmtxt_line_attr *a, int column,
const pmtxt_attr &attr, pmtxt::element el)
{
if (column != EOL_COLUMN && eq_attr (attr, c.cur_attr) && el == c.cur_el)
{
if (a != NULL)
delete a;
}
else if (c.last != NULL && c.last->column == column)
{
c.last->attr = attr;
c.last->el = el;
c.cur_attr = attr;
c.cur_el = el;
if (a != NULL)
delete a;
}
else
{
if (a == NULL)
a = new pmtxt_line_attr;
a->next = NULL;
a->column = column;
a->attr = attr;
a->el = el;
c.cur_attr = attr;
c.cur_el = el;
c.last = a;
*c.add = a;
c.add = &a->next;
}
}
// TODO: Optimize to avoid copying/writing
void pmtxt::change_attr (pmtxt_line *p, int start, int end,
const pmtxt_attr &new_attr, element new_el)
{
pmtxt_line_attr *new_list, *a, *next;
pmtxt_attr prev_attr;
element prev_el;
change_data c;
new_list = NULL;
c.add = &new_list;
c.last = NULL;
c.cur_attr = default_attr;
c.cur_el = E_TEXT;
a = p->attr;
while (a != NULL && a->column < start)
{
next = a->next;
change_at (c, a, a->column, a->attr, a->el);
a = next;
}
prev_attr = c.cur_attr; prev_el = c.cur_el;
if (a == NULL)
change_at (c, NULL, start, new_attr,
new_el != E_KEEP ? new_el : c.cur_el);
else if (new_el == E_KEEP)
{
change_at (c, NULL, start, new_attr, c.cur_el);
while (a != NULL && a->column < end)
{
next = a->next;
change_at (c, a, a->column, new_attr, a->el);
a = next;
}
}
else
{
if (a->column == start)
{
prev_attr = a->attr; prev_el = a->el;
next = a->next;
change_at (c, a, start, new_attr, new_el);
a = next;
}
else
{
// TODO: if a->column < end, we can reuse the node
change_at (c, NULL, start, new_attr, new_el);
}
while (a != NULL && a->column < end)
{
next = a->next;
prev_attr = a->attr; prev_el = a->el;
delete a;
a = next;
}
}
if (a == NULL || a->column != end)
change_at (c, NULL, end, prev_attr, prev_el);
while (a != NULL)
{
next = a->next;
change_at (c, a, a->column, a->attr, a->el);
a = next;
}
p->attr = new_list;
int t = 0, i;
for (a = p->attr; a != NULL; a = a->next)
++t;
if (t > tab_x_size)
{
tab_x_size = t + 16;
int *new_x = new int[tab_x_size];
for (i = 0; i < tab_count; ++i)
new_x[i] = tab_x[i];
delete[] tab_x;
tab_x = new_x;
}
if (t > tab_count)
{
for (i = tab_count; i < t; ++i)
tab_x[i] = 0;
tab_count = t;
}
}