This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
yap-6.3/LGPL/swi_console/console.c

3660 lines
79 KiB
C

/* $Id: console.c,v 1.1 2008-03-27 00:41:33 vsc Exp $
Part of SWI-Prolog
Author: Jan Wielemaker
E-mail: jan@swi.psy.uva.nl
WWW: http://www.swi-prolog.org
Copyright (C): 1985-2002, University of Amsterdam
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
This file defines a console for porting (unix) stream-based applications
to MS-Windows. It has been developed for SWI-Prolog. The main source is
part of SWI-Prolog.
The SWI-Prolog source is at http://www.swi-prolog.org
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Thread design:
<written as a mail to Lutz Wohlrab>
There are two threads. The Prolog engine runs in the main thread. The
other thread deals with the window. Basically, it processes events and
if anything is typed it puts it into a queue.
The main thread at some stage forks the display thread, running
window_loop(). This thread initialises the input and then sends
WM_RLC_READY to the main thread to indicate it is ready to accept data.
If data is to be written, Prolog calls rlc_write(), which posts the
WM_RLC_WRITE to the display thread, waiting on the termination. If data
is to be read, rlc_read() posts a WM_RLC_FLUSH, and then waits while
dispatching events, for the display-thread to fill the buffer and send
WM_RLC_INPUT (which is just sent to make GetMessage() in rlc_read()
return).
Towards an MT version on Windows
--------------------------------
If we want to move towards a multi-threaded version for MS-Windows, the
console code needs to be changed significantly, as we need to be able to
create multiple consoles to support thread_attach_console/0.
The most logical solution seems to be to reverse the thread-structure,
Prolog starting and running in the main-thread and creating a console
creates a new thread for this console. There are two ways to keep track
of the console to use. Cleanest might be to add an argument denoting the
allocated console and alternatively we could use thread-local data. We
can also combine the two: add an additional argument, but allow passing
NULL to use the default console for this thread.
Menus
-----
The current console provides a menu that can be extended from Prolog.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#ifdef O_DEBUG_HEAP
static void initHeapDebug(void);
#include <crtdbg.h>
#else
#define initHeapDebug()
#endif
#include <windows.h>
#include <tchar.h>
#ifndef WM_MOUSEWHEEL /* sometimes not defined */
#define WM_MOUSEWHEEL 0x020A
#endif
#ifndef WM_UNICHAR
#define WM_UNICHAR 0x109
#define UNICODE_NOCHAR 0xFFFF
#endif
#if (_MSC_VER < 1400) && !_WIN64
typedef DWORD DWORD_PTR;
#endif
#include <stdlib.h>
#include <io.h>
#include <string.h>
#include <malloc.h>
#define _MAKE_DLL 1
#undef _export
#include "console.h"
#include "menu.h"
#include "common.h"
#include <signal.h>
#include <ctype.h>
#include <stdio.h>
#ifndef isletter
#define isletter(c) (_istalpha(c) || (c) == '_')
#endif
#ifndef MAXPATHLEN
#define MAXPATHLEN 256
#endif
#ifndef CHAR_MAX
#define CHAR_MAX 256
#endif
#define MAXLINE 1024 /* max chars per line */
#define CMD_INITIAL 0
#define CMD_ESC 1
#define CMD_ANSI 2
#define GWL_DATA 0 /* offset for client data */
#define CHG_RESET 0 /* unchenged */
#define CHG_CHANGED 1 /* changed, but no clear */
#define CHG_CLEAR 2 /* clear */
#define CHG_CARET 4 /* caret has moved */
#define SEL_CHAR 0 /* character-unit selection */
#define SEL_WORD 1 /* word-unit selection */
#define SEL_LINE 2 /* line-unit selection */
#ifndef EOS
#define EOS 0
#endif
#define ESC 27 /* the escape character */
#define WM_RLC_INPUT WM_USER+10 /* Just somewhere ... */
#define WM_RLC_WRITE WM_USER+11 /* write data */
#define WM_RLC_FLUSH WM_USER+12 /* flush buffered data */
#define WM_RLC_READY WM_USER+13 /* Window thread is ready */
#define WM_RLC_CLOSEWIN WM_USER+14 /* Close the window */
/*#define WM_RLC_MENU WM_USER+15 Insert a menu (defined in menu.h) */
#define IMODE_RAW 1 /* char-by-char */
#define IMODE_COOKED 2 /* line-by-line */
#define NextLine(b, i) ((i) < (b)->height-1 ? (i)+1 : 0)
#define PrevLine(b, i) ((i) > 0 ? (i)-1 : (b)->height-1)
#define Bounds(v, mn, mx) ((v) < (mn) ? (mn) : (v) > (mx) ? (mx) : (v))
#define Control(x) ((x) - '@')
#define streq(s, q) (_tcscmp((s), (q)) == 0)
#include "console_i.h" /* internal package stuff */
#define OPT_SIZE 0x01
#define OPT_POSITION 0x02
/*******************************
* DATA *
*******************************/
RlcData _rlc_stdio = NULL; /* the main buffer */
static int _rlc_show; /* initial show */
static char _rlc_word_chars[CHAR_MAX]; /* word-characters (selection) */
static const TCHAR * _rlc_program; /* name of the program */
static HANDLE _rlc_hinstance; /* Global instance */
static HICON _rlc_hicon; /* Global icon */
/*******************************
* FUNCTIONS *
*******************************/
static LRESULT WINAPI rlc_wnd_proc(HWND win, UINT msg, WPARAM wP, LPARAM lP);
static void rlc_place_caret(RlcData b);
static void rlc_resize_pixel_units(RlcData b, int w, int h);
static RlcData rlc_make_buffer(int w, int h);
static int rlc_count_lines(RlcData b, int from, int to);
static void rlc_add_line(RlcData b);
static void rlc_open_line(RlcData b);
static void rlc_update_scrollbar(RlcData b);
static void rlc_paste(RlcData b);
static void rlc_init_text_dimensions(RlcData b, HFONT f);
static void rlc_save_font_options(HFONT f, rlc_console_attr *attr);
static void rlc_get_options(rlc_console_attr *attr);
static HKEY rlc_option_key(rlc_console_attr *attr, int create);
static void rlc_progbase(TCHAR *path, TCHAR *base);
static int rlc_add_queue(RlcData b, RlcQueue q, int chr);
static int rlc_add_lines(RlcData b, int here, int add);
static void rlc_start_selection(RlcData b, int x, int y);
static void rlc_extend_selection(RlcData b, int x, int y);
static void rlc_word_selection(RlcData b, int x, int y);
static void rlc_copy(RlcData b);
static void rlc_destroy(RlcData b);
static void rlc_request_redraw(RlcData b);
static void rlc_redraw(RlcData b);
static int rlc_breakargs(TCHAR *line, TCHAR **argv);
static void rlc_resize(RlcData b, int w, int h);
static void rlc_adjust_line(RlcData b, int line);
static int text_width(RlcData b, HDC hdc, const TCHAR *text, int len);
static void rlc_queryfont(RlcData b);
static void rlc_do_write(RlcData b, TCHAR *buf, int count);
static void rlc_reinit_line(RlcData b, int line);
static void rlc_free_line(RlcData b, int line);
static int rlc_between(RlcData b, int f, int t, int v);
static void free_user_data(RlcData b);
static RlcQueue rlc_make_queue(int size);
static void rlc_free_queue(RlcQueue q);
static int rlc_from_queue(RlcQueue q);
static int rlc_is_empty_queue(RlcQueue q);
static void rlc_empty_queue(RlcQueue q);
extern int main();
static RlcUpdateHook _rlc_update_hook;
static RlcTimerHook _rlc_timer_hook;
static RlcRenderHook _rlc_render_hook;
static RlcRenderAllHook _rlc_render_all_hook;
static RlcInterruptHook _rlc_interrupt_hook;
static RlcResizeHook _rlc_resize_hook;
static RlcMenuHook _rlc_menu_hook;
static RlcMessageHook _rlc_message_hook;
static int _rlc_copy_output_to_debug_output=0; /* != 0: copy to debugger */
static int emulate_three_buttons;
static HWND emu_hwnd; /* Emulating for this window */
static void _rlc_create_kill_window(RlcData b);
static DWORD WINAPI window_loop(LPVOID arg); /* console window proc */
#ifdef _DEBUG
#include <stdarg.h>
static void Dprintf(const TCHAR *fmt, ...);
static void Dprint_lines(RlcData b, int from, int to);
#define DEBUG(Code) Code
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
It might look a bit weird not to use <assert.h>, but for some reason it
looks as if the application thread continues if the asserting is trapped
using the normal assert()!? Just but a debugger breakpoint on
rlc_assert() and all functions normally.
rlc_check_assertions() is a (very) incomplete check that everything we
expect to be true about the data is indeed the case.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
void
rlc_assert(const TCHAR *msg)
{ MessageBox(NULL, msg, _T("Console assertion failed"), MB_OK|MB_TASKMODAL);
}
void
rlc_check_assertions(RlcData b)
{ int window_last = rlc_add_lines(b, b->window_start, b->window_size-1);
int y;
assert(b->last != b->first || b->first == 0);
assert(b->caret_x >= 0 && b->caret_x < b->width);
/* TBD: debug properly */
/*assert(rlc_between(b, b->window_start, window_last, b->caret_y));*/
for(y=0; y<b->height; y++)
{ TextLine tl = &b->lines[y];
assert(tl->size >= 0 && tl->size <= b->width);
}
}
#else
#define DEBUG(Code) ((void)0)
#define rlc_check_assertions(b)
#endif
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
rlc_long_name(TCHAR *buffer)
Translate a filename, possibly holding 8+3 abbreviated parts into
the `real' filename. I couldn't find a direct call for this. If
you have it, I'd be glad to receive a better implementation.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static void
rlc_long_name(TCHAR *file)
{ TCHAR buf[MAXPATHLEN];
TCHAR *i = file;
TCHAR *o = buf;
TCHAR *ok = buf;
int changed = 0;
while(*i)
{ int dirty = FALSE;
while(*i && *i != '\\')
{ if ( *i == '~' )
dirty++;
*o++ = *i++;
}
if ( dirty )
{ WIN32_FIND_DATA data;
HANDLE h;
*o = '\0';
if ( (h=FindFirstFile(buf, &data)) != INVALID_HANDLE_VALUE )
{ _tcscpy(ok, data.cFileName);
FindClose(h);
o = ok + _tcslen(ok);
changed++;
}
}
if ( *i )
*o++ = *i++;
ok = o;
}
if ( changed )
{ *o = '\0';
_tcscpy(file, buf);
}
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If %PLTERM_CLASS% is in the environment, this value is used as Windows
class identifier for the console window. This allows external programs
to start PLWIN.EXE and find the window it has started in order to embed
it.
In old versions this was fixed to "RlcConsole"
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static TCHAR *
rlc_window_class(HICON icon)
{ static TCHAR winclassname[32];
static WNDCLASS wndClass;
HINSTANCE instance = _rlc_hinstance;
if ( !winclassname[0] )
{ if ( !GetEnvironmentVariable(TEXT("PLTERM_CLASS"),
winclassname, sizeof(winclassname)) )
_stprintf(winclassname, TEXT("PlTerm-%d"), instance);
wndClass.lpszClassName = winclassname;
wndClass.style = CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS;
wndClass.lpfnWndProc = (LPVOID) rlc_wnd_proc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = sizeof(intptr_t);
wndClass.hInstance = instance;
if ( icon )
wndClass.hIcon = icon;
else
wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndClass.hCursor = LoadCursor(NULL, IDC_IBEAM);
wndClass.hbrBackground = (HBRUSH) NULL;
wndClass.lpszMenuName = NULL;
RegisterClass(&wndClass);
}
return winclassname;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
There are two ways to get the commandline. It is passed to WinMain as
8-bit string. This version does *not* include the command itself. It is
also available through GetCommandLine(), which does include the command
itself and returns LPTSTR (Unicode/ANSI). We assume the latter.
Nevertheless, for backward compatibility as well as easy to extract the
full pathname of the executable, we replace argv[0] with the intptr_t
filename version of the current module, so argv[0] is guaranteed to be a
full path refering to the .exe file.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
int
rlc_main(HANDLE hInstance, HANDLE hPrevInstance,
LPTSTR lpszCmdLine, int nCmdShow,
RlcMain mainfunc, HICON icon)
{ TCHAR * argv[100];
int argc;
TCHAR program[MAXPATHLEN];
TCHAR progbase[100];
RlcData b;
rlc_console_attr attr;
initHeapDebug();
_rlc_hinstance = hInstance;
_rlc_show = nCmdShow;
_rlc_hicon = icon;
GetModuleFileName(hInstance, program, sizeof(program));
rlc_long_name(program);
argc = rlc_breakargs(lpszCmdLine, argv);
argv[0] = program;
rlc_progbase(argv[0], progbase);
memset(&attr, 0, sizeof(attr));
_rlc_program = attr.title = progbase;
_rlc_stdio = b = rlc_create_console(&attr);
if ( mainfunc )
return (*mainfunc)(b, argc, argv);
else
return 0;
}
rlc_console
rlc_create_console(rlc_console_attr *attr)
{ RlcData b;
MSG msg;
const TCHAR *title;
rlc_get_options(attr);
if ( attr->title )
title = attr->title;
else
title = _T("Untitled");
b = rlc_make_buffer(attr->width, attr->savelines);
b->create_attributes = attr;
_tcscpy(b->current_title, title);
if ( attr->key )
{ b->regkey_name = _tcsdup(attr->key);
}
rlc_init_text_dimensions(b, NULL);
_rlc_create_kill_window(b);
DuplicateHandle(GetCurrentProcess(),
GetCurrentThread(),
GetCurrentProcess(),
&b->application_thread,
0,
FALSE,
DUPLICATE_SAME_ACCESS);
b->application_thread_id = GetCurrentThreadId();
b->console_thread = CreateThread(NULL, /* security */
2048, /* stack */
window_loop, b, /* proc+arg */
0, /* flags */
&b->console_thread_id); /* id */
/* wait till the window is created */
GetMessage(&msg, NULL, WM_RLC_READY, WM_RLC_READY);
b->create_attributes = NULL; /* release this data */
return b;
}
static void
rlc_create_window(RlcData b)
{ HWND hwnd;
rlc_console_attr *a = b->create_attributes;
RECT rect;
DWORD style = (WS_OVERLAPPEDWINDOW|WS_VSCROLL);
/* One would assume AdjustWindowRect() uses WS_VSCROLL to add the width of
the scrollbar. I think this isn't true, but maybe there is another reason
for getting 2 characters shorter each invocation ...
*/
rect.left = a->x;
rect.top = a->y;
rect.right = a->x + (a->width+2) * b->cw + GetSystemMetrics(SM_CXVSCROLL);
rect.bottom = a->y + a->height * b->ch;
AdjustWindowRect(&rect, style, TRUE);
hwnd = CreateWindow(rlc_window_class(_rlc_hicon), b->current_title,
style,
a->x, a->y,
rect.right - rect.left,
rect.bottom - rect.top,
NULL, NULL, _rlc_hinstance, NULL);
b->window = hwnd;
SetWindowLong(hwnd, GWL_DATA, (LONG) b);
SetScrollRange(hwnd, SB_VERT, 0, b->sb_lines, FALSE);
SetScrollPos(hwnd, SB_VERT, b->sb_start, TRUE);
b->queue = rlc_make_queue(256);
b->sb_lines = rlc_count_lines(b, b->first, b->last);
b->sb_start = rlc_count_lines(b, b->first, b->window_start);
b->foreground = GetSysColor(COLOR_WINDOWTEXT);
b->background = GetSysColor(COLOR_WINDOW);
b->sel_foreground = GetSysColor(COLOR_HIGHLIGHTTEXT);
b->sel_background = GetSysColor(COLOR_HIGHLIGHT);
if ( GetSystemMetrics(SM_CMOUSEBUTTONS) == 2 )
emulate_three_buttons = 120;
rlc_add_menu_bar(b->window);
ShowWindow(hwnd, _rlc_show);
UpdateWindow(hwnd);
}
int
rlc_iswin32s()
{ if( GetVersion() & 0x80000000 && (GetVersion() & 0xFF) ==3)
return TRUE;
else
return FALSE;
}
static void
rlc_progbase(TCHAR *path, TCHAR *base)
{ TCHAR *s;
TCHAR *e;
if ( !(s=_tcsrchr(path, '\\')) )
s = path; /* takes the filename part */
else
s++;
if ( !(e = _tcschr(s, '.')) )
_tcscpy(base, s);
else
{ _tcsncpy(base, s, e-s);
base[e-s] = '\0';
}
}
/*******************************
* HIDDEN WINDOW *
*******************************/
static LRESULT WINAPI
rlc_kill_wnd_proc(HWND hwnd, UINT message, UINT wParam, LONG lParam)
{ switch(message)
{ case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
static TCHAR *
rlc_kill_window_class()
{ static TCHAR winclassname[32];
static WNDCLASS wndClass;
HINSTANCE instance = _rlc_hinstance;
if ( !winclassname[0] )
{ _stprintf(winclassname, _T("Console-hidden-win%d"), instance);
wndClass.style = 0;
wndClass.lpfnWndProc = (LPVOID) rlc_kill_wnd_proc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = instance;
wndClass.hIcon = NULL;
wndClass.hCursor = NULL;
wndClass.hbrBackground = GetStockObject(WHITE_BRUSH);
wndClass.lpszMenuName = NULL;
wndClass.lpszClassName = winclassname;
RegisterClass(&wndClass);
}
return winclassname;
}
static void
_rlc_create_kill_window(RlcData b)
{ b->kill_window = CreateWindow(rlc_kill_window_class(),
_T("Console hidden window"),
0,
0, 0, 32, 32,
NULL, NULL, _rlc_hinstance, NULL);
}
/*******************************
* REGISTRY COMMUNICATION *
*******************************/
#define MAXREGSTRLEN 1024
static void
reg_save_int(HKEY key, const TCHAR *name, int value)
{ DWORD val = value;
if ( RegSetValueEx(key, name, 0,
REG_DWORD_LITTLE_ENDIAN,
(LPBYTE)&val, sizeof(val)) != ERROR_SUCCESS )
DEBUG(MessageBox(NULL, _T("Failed to save int setting"),
_T("Error"), MB_OK));
}
static void
reg_save_str(HKEY key, const TCHAR *name, TCHAR *value)
{ if ( RegSetValueEx(key, name, 0, REG_SZ,
(LPBYTE)value, (DWORD)(_tcslen(value)+1)*sizeof(TCHAR)) != ERROR_SUCCESS )
DEBUG(MessageBox(NULL, _T("Failed to save string setting"), _T("Error"), MB_OK));
}
static void
rlc_save_options(RlcData b)
{ HKEY key;
rlc_console_attr attr;
memset(&attr, 0, sizeof(attr));
attr.key = b->regkey_name;
if ( !(key = rlc_option_key(&attr, TRUE)) )
return;
reg_save_int(key, _T("SaveLines"), b->height);
if ( b->modified_options & OPT_SIZE )
{ reg_save_int(key, _T("Width"), b->width);
reg_save_int(key, _T("Height"), b->window_size);
}
if ( b->modified_options & OPT_POSITION )
{ reg_save_int(key, _T("X"), b->win_x);
reg_save_int(key, _T("Y"), b->win_y);
}
rlc_save_font_options(b->hfont, &attr);
if ( attr.face_name[0] )
{ reg_save_str(key, _T("FaceName"), attr.face_name);
reg_save_int(key, _T("FontFamily"), attr.font_family);
reg_save_int(key, _T("FontSize"), attr.font_size);
reg_save_int(key, _T("FontWeight"), attr.font_weight);
reg_save_int(key, _T("FontCharSet"), attr.font_char_set);
}
RegCloseKey(key);
}
static void
reg_get_int(HKEY key, const TCHAR *name, int mn, int def, int mx, int *value)
{ DWORD type;
BYTE data[8];
DWORD len = sizeof(data);
if ( *value )
return; /* use default */
if ( RegQueryValueEx(key, name, NULL, &type, data, &len) == ERROR_SUCCESS )
{ switch(type)
{ /*case REG_DWORD:*/ /* Same case !? */
case REG_DWORD_LITTLE_ENDIAN:
{ DWORD *valp = (DWORD *)data;
int v = *valp;
if ( mn < mx )
{ if ( v < mn )
v = mn;
else if ( v > mx )
v = mx;
}
*value = v;
}
}
} else
*value = def;
}
static void
reg_get_str(HKEY key, const TCHAR *name, TCHAR *value, int length)
{ DWORD type;
BYTE data[MAXREGSTRLEN*sizeof(TCHAR)];
DWORD len = sizeof(data);
if ( *value )
return; /* use default */
if ( RegQueryValueEx(key, name, NULL, &type, data, &len) == ERROR_SUCCESS )
{ switch(type)
{ case REG_SZ:
{ TCHAR *val = (TCHAR*)data;
_tcsncpy(value, val, length-1);
value[length-1] = '\0';
}
}
}
}
HKEY
reg_open_key(TCHAR **which, int create)
{ HKEY key = HKEY_CURRENT_USER;
DWORD disp;
LONG rval;
for( ; *which; which++)
{ HKEY tmp;
if ( which[1] )
{ if ( RegOpenKeyEx(key, which[0], 0L, KEY_READ, &tmp) == ERROR_SUCCESS )
{ key = tmp;
continue;
}
if ( !create )
return NULL;
}
rval = RegCreateKeyEx(key, which[0], 0, _T(""), 0,
KEY_ALL_ACCESS, NULL, &tmp, &disp);
RegCloseKey(key);
if ( rval == ERROR_SUCCESS )
key = tmp;
else
return NULL;
}
return key;
}
static HKEY
rlc_option_key(rlc_console_attr *attr, int create)
{ TCHAR Prog[256];
TCHAR *address[] = { _T("Software"),
RLC_VENDOR,
Prog,
_T("Console"),
(TCHAR *)attr->key, /* possible secondary key */
NULL
};
const TCHAR *s;
TCHAR *q;
for(s=_rlc_program, q=Prog; *s; s++, q++) /* capitalise the key */
{ *q = (s==_rlc_program ? _totupper(*s) : _totlower(*s));
}
*q = EOS;
return reg_open_key(address, create);
}
static void
rlc_get_options(rlc_console_attr *attr)
{ HKEY key;
if ( !(key = rlc_option_key(attr, FALSE)) )
{ if ( !attr->width ) attr->width = 80;
if ( !attr->height ) attr->height = 24;
if ( !attr->savelines ) attr->savelines = 200;
return;
}
{ int minx, miny, maxx, maxy;
RECT rect;
SystemParametersInfo(SPI_GETWORKAREA, 0, &rect, 0);
minx = rect.top;
miny = rect.left;
maxx = rect.right - 40;
maxy = rect.bottom - 40;
reg_get_int(key, _T("SaveLines"), 200, 200, 100000, &attr->savelines);
reg_get_int(key, _T("Width"), 20, 80, 300, &attr->width);
reg_get_int(key, _T("Height"), 5, 24, 100, &attr->height);
reg_get_int(key, _T("X"), minx, minx, maxx, &attr->x);
reg_get_int(key, _T("Y"), miny, miny, maxy, &attr->y);
}
reg_get_str(key, _T("FaceName"), attr->face_name,
sizeof(attr->face_name)/sizeof(TCHAR));
reg_get_int(key, _T("FontFamily"), 0, 0, 0, &attr->font_family);
reg_get_int(key, _T("FontSize"), 0, 0, 0, &attr->font_size);
reg_get_int(key, _T("FontWeight"), 0, 0, 0, &attr->font_weight);
reg_get_int(key, _T("FontCharSet"), 0, 0, 0, &attr->font_char_set);
RegCloseKey(key);
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Windows-'95 appears to quote names of files because files may hold
spaces. rlc_breakargs() will pass a quoted strings as one argument. If
it can't find the closing quote, it will tread the quote as a normal
character.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static int
rlc_breakargs(TCHAR *line, TCHAR **argv)
{ int argc = 0;
while(*line)
{ int q;
while(*line && _istspace(*line))
line++;
if ( (q = *line) == '"' || q == '\'' ) /* quoted arguments */
{ TCHAR *start = line+1;
TCHAR *end = start;
while( *end && *end != q )
end++;
if ( *end == q )
{ *end = '\0';
argv[argc++] = start;
line = end+1;
continue;
}
}
if ( *line )
{ argv[argc++] = line;
while(*line && !_istspace(*line))
line++;
if ( *line )
*line++ = '\0';
}
}
argv[argc] = NULL; /* add trailing NULL pointer to argv */
return argc;
}
/*******************************
* ATTRIBUTES *
*******************************/
COLORREF
rlc_color(rlc_console con, int which, COLORREF c)
{ HDC hdc;
COLORREF old;
RlcData b = rlc_get_data(con);
hdc = GetDC(NULL);
c = GetNearestColor(hdc, c);
ReleaseDC(NULL, hdc);
switch(which)
{ case RLC_WINDOW:
old = b->background;
b->background = c;
break;
case RLC_TEXT:
old = b->foreground;
b->foreground = c;
break;
case RLC_HIGHLIGHT:
old = b->sel_background;
b->sel_background = c;
break;
case RLC_HIGHLIGHTTEXT:
old = b->sel_foreground;
b->sel_foreground = c;
break;
default:
return (COLORREF)-1;
}
if ( b->window )
InvalidateRect(b->window, NULL, TRUE);
return old;
}
static int
rlc_kill(RlcData b)
{ DWORD_PTR result;
switch(b->closing++)
{ case 0:
b->queue->flags |= RLC_EOF;
PostThreadMessage(b->application_thread_id, WM_RLC_INPUT, 0, 0);
return TRUE;
case 1:
if ( _rlc_interrupt_hook )
{ (*_rlc_interrupt_hook)(b, SIGINT);
return TRUE;
}
default:
if ( !SendMessageTimeout(b->kill_window,
WM_DESTROY,
0, 0,
SMTO_ABORTIFHUNG,
5000,
&result) )
{ if ( b->window )
{ switch( MessageBox(b->window,
_T("Main task is not responding.")
_T("Click \"OK\" to terminate it"),
_T("Error"),
MB_OKCANCEL|MB_ICONEXCLAMATION|MB_APPLMODAL) )
{ case IDCANCEL:
return FALSE;
}
TerminateThread(b->application_thread, 1);
return TRUE;
}
}
}
return FALSE;
}
static void
rlc_interrupt(RlcData b)
{ if ( _rlc_interrupt_hook )
(*_rlc_interrupt_hook)((rlc_console)b, SIGINT);
else
raise(SIGINT);
}
static void
typed_char(RlcData b, int chr)
{ if ( chr == Control('C') )
rlc_interrupt(b);
else if ( chr == Control('V') || chr == Control('Y') )
rlc_paste(b);
else if ( b->queue )
rlc_add_queue(b, b->queue, chr);
}
/*******************************
* WINDOW PROCEDURE *
*******************************/
static void
rlc_destroy(RlcData b)
{ if ( b && b->window )
{ DestroyWindow(b->window);
b->window = NULL;
b->closing = 3;
}
}
static int
IsDownKey(code)
{ short mask = GetKeyState(code);
return mask & 0x8000;
}
static LRESULT WINAPI
rlc_wnd_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{ RlcData b = (RlcData) GetWindowLong(hwnd, GWL_DATA);
switch(message)
{ case WM_CREATE:
return 0;
case WM_SIZE:
if ( wParam != SIZE_MINIMIZED )
{ rlc_resize_pixel_units(b, LOWORD(lParam), HIWORD(lParam));
b->modified_options |= OPT_SIZE;
}
return 0;
case WM_MOVE:
{ WINDOWPLACEMENT placement;
placement.length = sizeof(placement);
GetWindowPlacement(hwnd, &placement);
if ( placement.showCmd == SW_SHOWNORMAL )
{ b->win_x = placement.rcNormalPosition.left;
b->win_y = placement.rcNormalPosition.top;
b->modified_options |= OPT_POSITION;
}
return 0;
}
case WM_SETFOCUS:
b->has_focus = TRUE;
CreateCaret(hwnd, NULL, b->fixedfont ? b->cw : 3, b->ch-1);
rlc_place_caret(b);
return 0;
case WM_KILLFOCUS:
b->has_focus = FALSE;
b->caret_is_shown = FALSE;
HideCaret(hwnd);
DestroyCaret();
return 0;
case WM_PAINT:
rlc_redraw(b);
return 0;
case WM_COMMAND:
{ UINT item = (UINT) LOWORD(wParam);
const TCHAR *name;
switch( item )
{ case IDM_PASTE:
rlc_paste(b);
return 0;
case IDM_COPY:
return 0; /* no op: already done */
case IDM_CUT:
break; /* TBD: cut */
case IDM_BREAK:
rlc_interrupt(b);
break;
case IDM_FONT:
rlc_queryfont(b);
return 0;
case IDM_EXIT:
if ( rlc_kill(b) )
return 0;
break;
}
if ( (name = lookupMenuId(item)) )
{ if ( _rlc_menu_hook )
{ (*_rlc_menu_hook)(b, name);
}
return 0;
}
break;
}
{ int chr;
case WM_KEYDOWN: /* up is sent only once */
{ switch((int) wParam)
{ case VK_DELETE: chr = 127; break;
case VK_LEFT: chr = Control('B'); break;
case VK_RIGHT: chr = Control('F'); break;
case VK_UP: chr = Control('P'); break;
case VK_DOWN: chr = Control('N'); break;
case VK_HOME: chr = Control('A'); break;
case VK_END: chr = Control('E'); break;
case VK_PRIOR: /* page up */
{ int maxdo = rlc_count_lines(b, b->first, b->window_start);
int pagdo = b->window_size - 1;
b->window_start = rlc_add_lines(b, b->window_start,
-min(maxdo, pagdo));
scrolledbykey:
rlc_update_scrollbar(b);
InvalidateRect(hwnd, NULL, FALSE);
return 0;
}
case VK_NEXT: /* page down */
{ int maxup = rlc_count_lines(b, b->window_start, b->last);
int pagup = b->window_size - 1;
b->window_start = rlc_add_lines(b, b->window_start,
min(maxup, pagup));
goto scrolledbykey;
}
default:
goto break2;
}
if ( chr > 0 )
{ if ( IsDownKey(VK_CONTROL) )
typed_char(b, ESC);
typed_char(b, chr);
return 0;
}
break2:
break;
}
case WM_UNICHAR:
chr = (int)wParam;
typed_char(b, chr);
return 0;
case WM_SYSCHAR: typed_char(b, ESC); /* Play escape-something */
case WM_CHAR: chr = (int)wParam;
typed_char(b, chr);
return 0;
}
/* selection handling */
case WM_MBUTTONDOWN:
middle_down:
return 0;
case WM_MBUTTONUP:
middle_up:
rlc_paste(b);
return 0;
case WM_LBUTTONDOWN:
{ POINTS pt;
if ( emulate_three_buttons )
{ MSG msg;
Sleep(emulate_three_buttons);
if ( PeekMessage(&msg, hwnd,
WM_RBUTTONDOWN, WM_RBUTTONDOWN, PM_REMOVE) )
{ emu_hwnd = hwnd;
goto middle_down;
}
}
pt = MAKEPOINTS(lParam);
rlc_start_selection(b, pt.x, pt.y);
return 0;
}
case WM_LBUTTONUP:
case WM_RBUTTONUP:
if ( emu_hwnd == hwnd )
{ if ( wParam & (MK_RBUTTON|MK_LBUTTON) )
goto middle_up;
else
{ emu_hwnd = 0;
return 0;
}
} else
{ rlc_copy(b);
return 0;
}
case WM_LBUTTONDBLCLK:
{ POINTS pt = MAKEPOINTS(lParam);
rlc_word_selection(b, pt.x, pt.y);
return 0;
}
case WM_RBUTTONDOWN:
{ POINTS pt;
if ( emulate_three_buttons )
{ MSG msg;
Sleep(emulate_three_buttons);
if ( PeekMessage(&msg, hwnd,
WM_LBUTTONDOWN, WM_LBUTTONDOWN, PM_REMOVE) )
{ emu_hwnd = hwnd;
goto middle_down;
}
}
pt = MAKEPOINTS(lParam);
rlc_extend_selection(b, pt.x, pt.y);
return 0;
}
case WM_MOUSEMOVE:
{ POINTS pt = MAKEPOINTS(lParam);
if ( (wParam & (MK_LBUTTON|MK_RBUTTON)) &&
(wParam & (MK_LBUTTON|MK_RBUTTON)) != (MK_LBUTTON|MK_RBUTTON) )
{ rlc_extend_selection(b, pt.x, pt.y);
return 0;
}
break;
}
case WM_MOUSEWHEEL:
{ short angle = (short)HIWORD(wParam);
if ( angle < 0 )
{ if ( b->window_start != b->last )
b->window_start = NextLine(b, b->window_start);
} else
{ if ( b->window_start != b->first )
b->window_start = PrevLine(b, b->window_start);
}
rlc_update_scrollbar(b);
InvalidateRect(hwnd, NULL, FALSE);
return 0;
}
/* scrolling */
case WM_VSCROLL:
{ switch( LOWORD(wParam) )
{ case SB_LINEUP:
if ( b->window_start != b->first )
b->window_start = PrevLine(b, b->window_start);
break;
case SB_LINEDOWN:
if ( b->window_start != b->last )
b->window_start = NextLine(b, b->window_start);
break;
case SB_PAGEUP:
{ int maxdo = rlc_count_lines(b, b->first, b->window_start);
int pagdo = b->window_size - 1;
b->window_start = rlc_add_lines(b, b->window_start,
-min(maxdo, pagdo));
break;
}
case SB_PAGEDOWN:
{ int maxup = rlc_count_lines(b, b->window_start, b->last);
int pagup = b->window_size - 1;
b->window_start = rlc_add_lines(b, b->window_start,
min(maxup, pagup));
break;
}
case SB_THUMBTRACK:
b->window_start = rlc_add_lines(b, b->first, HIWORD(wParam));
break;
}
rlc_update_scrollbar(b);
InvalidateRect(hwnd, NULL, FALSE);
return 0;
}
case WM_TIMER:
if ( _rlc_timer_hook && wParam >= RLC_APPTIMER_ID )
{ (*_rlc_timer_hook)((int) wParam);
return 0;
}
break;
case WM_RENDERALLFORMATS:
if ( _rlc_render_all_hook )
{ (*_rlc_render_all_hook)();
return 0;
}
break;
case WM_RENDERFORMAT:
if ( _rlc_render_hook && (*_rlc_render_hook)(wParam) )
return 0;
break;
case WM_ERASEBKGND:
{ HDC hdc = (HDC) wParam;
RECT rect;
HBRUSH hbrush;
COLORREF rgb = b->background;
hbrush = CreateSolidBrush(rgb);
GetClipBox(hdc, &rect);
FillRect(hdc, &rect, hbrush);
DeleteObject(hbrush);
return 1; /* non-zero: I've erased it */
}
case WM_SYSCOLORCHANGE:
b->foreground = GetSysColor(COLOR_WINDOWTEXT);
b->background = GetSysColor(COLOR_WINDOW);
b->sel_foreground = GetSysColor(COLOR_HIGHLIGHTTEXT);
b->sel_background = GetSysColor(COLOR_HIGHLIGHT);
return 0;
case WM_RLC_WRITE:
{ int count = (int)wParam;
TCHAR *buf = (TCHAR *)lParam;
if ( OQSIZE - b->output_queued > count )
{ _tcsncpy(&b->output_queue[b->output_queued], buf, count);
b->output_queued += count;
} else
{ if ( b->output_queued > 0 )
rlc_flush_output(b);
if ( count <= OQSIZE )
{ _tcsncpy(b->output_queue, buf, count);
b->output_queued = count;
} else
rlc_do_write(b, buf, count);
}
return 0;
}
case WM_RLC_FLUSH:
{ rlc_flush_output(b);
return 0;
}
case WM_RLC_MENU:
{ rlc_menu_action((rlc_console) b, (struct menu_data*)lParam);
return 0;
}
case WM_RLC_CLOSEWIN:
return 0;
case WM_CLOSE:
if ( rlc_kill(b) )
return 0;
break;
case WM_DESTROY:
b->window = NULL;
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
static int
rlc_get_message(MSG *msg, HWND hwnd, UINT low, UINT high)
{ int rc;
again:
if ( (rc=GetMessage(msg, hwnd, low, high)) )
{ if ( _rlc_message_hook &&
(*_rlc_message_hook)(msg->hwnd, msg->message,
msg->wParam, msg->lParam) )
goto again;
}
return rc;
}
static void
rlc_dispatch(RlcData b)
{ MSG msg;
if ( rlc_get_message(&msg, NULL, 0, 0) && msg.message != WM_RLC_CLOSEWIN )
{ /* DEBUG(Dprintf("Thread %x got message 0x%04x\n",
GetCurrentThreadId(), msg.message));
*/
TranslateMessage(&msg);
DispatchMessage(&msg);
rlc_flush_output(b);
return;
} else
{ DEBUG(Dprintf(_T("Thread %x got WM_RLC_CLOSEWIN\n"),
GetCurrentThreadId()));
b->queue->flags |= RLC_EOF;
}
}
void
rlc_yield()
{ MSG msg;
while ( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) )
{ TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
/*******************************
* CHARACTER TYPES *
*******************************/
static void
rlc_init_word_chars()
{ int i;
for(i=0; i<CHAR_MAX; i++)
_rlc_word_chars[i] = (isalnum(i) || i == '_') ? TRUE : FALSE;
}
void
rlc_word_char(int chr, int isword)
{ if ( chr > 0 && chr < CHAR_MAX )
_rlc_word_chars[chr] = isword;
}
int
rlc_is_word_char(int chr)
{ if ( chr > 0 && chr < CHAR_MAX )
return _rlc_word_chars[chr];
return _istalnum((wint_t)chr); /* only UNICODE version */
}
/*******************************
* SELECTION *
*******************************/
#define SelLT(l1, c1, l2, c2) ((l1) < (l2) || (l1) == (l2) && (c1) < (c2))
#define SelEQ(l1, c1, l2, c2) ((l1) == (l2) && (c1) == (c2))
static int
rlc_min(RlcData b, int x, int y)
{ if ( rlc_count_lines(b, b->first, x) < rlc_count_lines(b, b->first, y) )
return x;
return y;
}
static int
rlc_max(RlcData b, int x, int y)
{ if ( rlc_count_lines(b, b->first, x) > rlc_count_lines(b, b->first, y) )
return x;
return y;
}
static void
rlc_changed_line(RlcData b, int i, int mask)
{ b->lines[i].changed |= mask;
}
static void
rlc_set_selection(RlcData b, int sl, int sc, int el, int ec)
{ int sch = rlc_min(b, sl, b->sel_start_line);
int ech = rlc_max(b, el, b->sel_end_line);
int nel = NextLine(b, el);
int nsel= NextLine(b, b->sel_end_line);
int i;
int innow = FALSE;
int insoon = FALSE;
/* find the lines that changed */
for(i=sch; ; i = NextLine(b, i))
{ if ( i == sl )
{ insoon = TRUE;
if ( i == b->sel_start_line )
{ innow = TRUE;
if ( sc != b->sel_start_char ||
(i == el && i != b->sel_end_line) ||
(i == b->sel_end_line && i != el) )
rlc_changed_line(b, i, CHG_CHANGED);
} else
rlc_changed_line(b, i, CHG_CHANGED);
} else if ( i == b->sel_start_line )
{ innow = TRUE;
rlc_changed_line(b, i, CHG_CHANGED);
}
if ( i == b->sel_end_line )
{ if ( (i == el && ec != b->sel_end_char) || el != i )
rlc_changed_line(b, i, CHG_CHANGED);
}
if ( innow != insoon )
rlc_changed_line(b, i, CHG_CHANGED);
if ( i == nel )
{ insoon = FALSE;
if ( i == nsel )
innow = FALSE;
else
rlc_changed_line(b, i, CHG_CHANGED);
} else if ( i == nsel )
{ innow = FALSE;
rlc_changed_line(b, i, CHG_CHANGED);
}
if ( i == ech )
break;
}
/* update the attributes */
b->sel_start_line = sl;
b->sel_start_char = sc;
b->sel_end_line = el;
b->sel_end_char = ec;
/* ... and request a repaint */
rlc_request_redraw(b);
}
void
rlc_translate_mouse(RlcData b, int x, int y, int *line, int *chr)
{ int ln = b->window_start;
int n = b->window_size; /* # lines */
TextLine tl;
x-= b->cw; /* margin */
if ( !b->window )
return;
while( y > b->ch && ln != b->last && n-- > 0 )
{ ln = NextLine(b, ln);
y -= b->ch;
}
*line = ln;
tl = &b->lines[ln];
if ( b->fixedfont )
{ *chr = min(x/b->cw, tl->size);
} else if ( tl->size == 0 )
{ *chr = 0;
} else
{ TCHAR *s = tl->text;
HDC hdc = GetDC(b->window);
int f = 0;
int t = tl->size;
int m = (f+t)/2;
int i;
SelectObject(hdc, b->hfont);
for(i=10; --i > 0; m=(f+t)/2)
{ int w;
w = text_width(b, hdc, s, m);
if ( x > w )
{ int cw;
GetCharWidth32(hdc, s[m], s[m], &cw);
if ( x < w+cw )
{ *chr = m;
return;
}
f = m+1;
} else
{ t = m;
}
}
*chr = m;
}
}
static void
rlc_start_selection(RlcData b, int x, int y)
{ int l, c;
rlc_translate_mouse(b, x, y, &l, &c);
b->sel_unit = SEL_CHAR;
b->sel_org_line = l;
b->sel_org_char = c;
rlc_set_selection(b, l, c, l, c);
}
static void
rlc_end_selection(RlcData b, int x, int y)
{ int l, c;
rlc_translate_mouse(b, x, y, &l, &c);
if ( SelLT(l, c, b->sel_org_line, b->sel_org_char) )
rlc_set_selection(b, l, c, b->sel_org_line, b->sel_org_char);
else if ( SelLT(b->sel_org_line, b->sel_org_char, l, c) )
rlc_set_selection(b, b->sel_org_line, b->sel_org_char, l, c);
rlc_set_selection(b, l, c, l, c);
}
static int /* v >= f && v <= t */
rlc_between(RlcData b, int f, int t, int v)
{ int h = rlc_count_lines(b, b->first, v);
if ( h >= rlc_count_lines(b, b->first, f) &&
h <= rlc_count_lines(b, b->first, t) )
return TRUE;
return FALSE;
}
static void
rlc_word_selection(RlcData b, int x, int y)
{ int l, c;
rlc_translate_mouse(b, x, y, &l, &c);
if ( rlc_between(b, b->first, b->last, l) )
{ TextLine tl = &b->lines[l];
if ( c < tl->size && rlc_is_word_char(tl->text[c]) )
{ int f, t;
for(f=c; f>0 && rlc_is_word_char(tl->text[f-1]); f--)
;
for(t=c; t<tl->size && rlc_is_word_char(tl->text[t]); t++)
;
rlc_set_selection(b, l, f, l, t);
}
}
b->sel_unit = SEL_WORD;
}
static void
rlc_extend_selection(RlcData b, int x, int y)
{ int l, c;
rlc_translate_mouse(b, x, y, &l, &c);
if ( SelLT(l, c, b->sel_org_line, b->sel_org_char) )
{ if ( b->sel_unit == SEL_WORD )
{ if ( rlc_between(b, b->first, b->last, l) )
{ TextLine tl = &b->lines[l];
if ( c < tl->size && rlc_is_word_char(tl->text[c]) )
for(; c > 0 && rlc_is_word_char(tl->text[c-1]); c--)
;
}
} else if ( b->sel_unit == SEL_LINE )
c = 0;
rlc_set_selection(b, l, c, b->sel_end_line, b->sel_end_char);
} else if ( SelLT(b->sel_org_line, b->sel_org_char, l, c) )
{ if ( b->sel_unit == SEL_WORD )
{ if ( rlc_between(b, b->first, b->last, l) )
{ TextLine tl = &b->lines[l];
if ( c < tl->size && rlc_is_word_char(tl->text[c]) )
for(; c < tl->size && rlc_is_word_char(tl->text[c]); c++)
;
}
} else if ( b->sel_unit == SEL_LINE )
c = b->width;
rlc_set_selection(b, b->sel_start_line, b->sel_start_char, l, c);
}
}
static TCHAR *
rlc_read_from_window(RlcData b, int sl, int sc, int el, int ec)
{ int bufsize = 256;
TCHAR *buf;
int i = 0;
if ( el < sl || el == sl && ec < sc )
return NULL; /* invalid region */
if ( !(buf = rlc_malloc(bufsize * sizeof(TCHAR))) )
return NULL; /* not enough memory */
for( ; ; sc = 0, sl = NextLine(b, sl))
{ TextLine tl = &b->lines[sl];
if ( tl )
{ int e = (sl == el ? ec : tl->size);
if ( e > tl->size )
e = tl->size;
while(sc < e)
{ if ( i >= bufsize )
{ bufsize *= 2;
if ( !(buf = rlc_realloc(buf, bufsize * sizeof(TCHAR))) )
return NULL; /* not enough memory */
}
buf[i++] = tl->text[sc++];
}
}
if ( sl == el || sl == b->last )
{ buf[i++] = '\0';
return buf;
}
if ( tl && !tl->softreturn )
{ if ( i+1 >= bufsize )
{ bufsize *= 2;
if ( !(buf = rlc_realloc(buf, bufsize * sizeof(TCHAR))) )
return NULL; /* not enough memory */
}
buf[i++] = '\r'; /* Bill ... */
buf[i++] = '\n';
}
}
}
static TCHAR *
rlc_selection(RlcData b)
{ if ( SelEQ(b->sel_start_line, b->sel_start_char,
b->sel_end_line, b->sel_end_char) )
return NULL;
return rlc_read_from_window(b,
b->sel_start_line, b->sel_start_char,
b->sel_end_line, b->sel_end_char);
}
static void
rlc_copy(RlcData b)
{ TCHAR *sel = rlc_selection(b);
if ( sel && b->window )
{ size_t size = _tcslen(sel);
HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, (size + 1)*sizeof(TCHAR));
TCHAR far *data;
size_t i;
if ( !mem )
{ MessageBox(NULL, _T("Not enough memory to copy"), _T("Error"), MB_OK);
return;
}
data = GlobalLock(mem);
for(i=0; i<size; i++)
*data++ = sel[i];
*data = '\0';
GlobalUnlock(mem);
OpenClipboard(b->window);
EmptyClipboard();
#ifdef UNICODE
SetClipboardData(CF_UNICODETEXT, mem);
#else
SetClipboardData(CF_TEXT, mem);
#endif
CloseClipboard();
rlc_free(sel);
}
}
/*******************************
* REPAINT *
*******************************/
static void
rlc_place_caret(RlcData b)
{ if ( b->has_focus && b->window )
{ int line = rlc_count_lines(b, b->window_start, b->caret_y);
if ( line < b->window_size )
{ if ( b->fixedfont )
{ SetCaretPos((b->caret_x + 1) * b->cw, line * b->ch);
} else
{ HDC hdc = GetDC(b->window);
SIZE tsize;
TextLine tl = &b->lines[b->caret_y];
HFONT old;
old = SelectObject(hdc, b->hfont);
GetTextExtentPoint32(hdc, tl->text, b->caret_x, &tsize);
SelectObject(hdc, old);
ReleaseDC(b->window, hdc);
SetCaretPos(b->cw + tsize.cx, line * b->ch);
}
if ( !b->caret_is_shown )
{ ShowCaret(b->window);
b->caret_is_shown = TRUE;
return;
}
} else
{ if ( b->caret_is_shown == TRUE )
{ HideCaret(b->window);
b->caret_is_shown = FALSE;
}
}
}
b->caret_is_shown = FALSE;
}
static void
rlc_update_scrollbar(RlcData b)
{ if ( b->window )
{ int nsb_lines = rlc_count_lines(b, b->first, b->last);
int nsb_start = rlc_count_lines(b, b->first, b->window_start);
if ( nsb_lines != b->sb_lines ||
nsb_start != b->sb_start )
{ SetScrollRange(b->window, SB_VERT, 0, nsb_lines, FALSE);
SetScrollPos( b->window, SB_VERT, nsb_start, TRUE);
b->sb_lines = nsb_lines;
b->sb_start = nsb_start;
}
}
}
static void
rlc_redraw(RlcData b)
{ PAINTSTRUCT ps;
HDC hdc = BeginPaint(b->window, &ps);
int sl = max(0, ps.rcPaint.top/b->ch);
int el = min(b->window_size, ps.rcPaint.bottom/b->ch);
int l = rlc_add_lines(b, b->window_start, sl);
int pl = sl; /* physical line */
RECT rect;
HBRUSH bg;
int stockbg;
int insel = FALSE; /* selected lines? */
SelectObject(hdc, b->hfont);
SetTextColor(hdc, b->foreground);
SetBkColor(hdc, b->background);
if ( b->background == RGB(255, 255, 255) )
{ bg = GetStockObject(WHITE_BRUSH);
stockbg = TRUE;
} else
{ bg = CreateSolidBrush(b->background);
stockbg = FALSE;
}
if ( b->has_focus && b->caret_is_shown )
{ HideCaret(b->window);
b->caret_is_shown = FALSE;
}
if ( rlc_count_lines(b, b->first, b->sel_start_line) <
rlc_count_lines(b, b->first, l) &&
rlc_count_lines(b, b->first, b->sel_end_line) >=
rlc_count_lines(b, b->first, l) )
insel = TRUE;
if ( insel )
{ SetBkColor(hdc, b->sel_background);
SetTextColor(hdc, b->sel_foreground);
}
for(; pl <= el; l = NextLine(b, l), pl++)
{ TextLine tl = &b->lines[l];
TCHAR text[MAXLINE];
int ty = b->ch * pl;
int cx = b->cw;
if ( !tl->text )
{ int i;
TCHAR *t;
tl->size = 0;
for(i=0, t=text; i<b->width; i++)
*t++ = ' ';
} else
{ int i;
TCHAR *t, *s;
for(i=0, t=text, s=tl->text; i<tl->size; i++)
*t++ = *s++;
for(; i<b->width; i++)
*t++ = ' ';
}
rect.top = ty;
rect.bottom = rect.top + b->ch;
/* compute selection */
if ( l == b->sel_start_line )
{ int cf = b->sel_start_char;
int ce = (b->sel_end_line != b->sel_start_line ? b->width
: b->sel_end_char);
if ( cf > 0 )
{ TextOut(hdc, cx, ty, text, cf);
cx += text_width(b, hdc, text, cf);
}
SetBkColor(hdc, b->sel_background);
SetTextColor(hdc, b->sel_foreground);
TextOut(hdc, cx, ty, &text[cf], ce-cf);
cx += text_width(b, hdc, &text[cf], ce-cf);
if ( l == b->sel_end_line )
{ SetBkColor(hdc, b->background);
SetTextColor(hdc, b->foreground);
TextOut(hdc, cx, ty, &text[ce], b->width - ce);
cx += text_width(b, hdc, &text[ce], b->width - ce);
} else
insel = TRUE;
} else if ( l == b->sel_end_line ) /* end of selection */
{ int ce = b->sel_end_char;
insel = FALSE;
TextOut(hdc, cx, ty, text, ce);
cx += text_width(b, hdc, text, ce);
SetBkColor(hdc, b->background);
SetTextColor(hdc, b->foreground);
TextOut(hdc, cx, ty, &text[ce], b->width - ce);
cx += text_width(b, hdc, &text[ce], b->width - ce);
} else /* entire line in/out selection */
{ TextOut(hdc, cx, ty, text, b->width);
cx += text_width(b, hdc, text, b->width);
}
/* clear remainder of line */
if ( cx < b->width * (b->cw+1) )
{ rect.left = cx;
rect.right = b->width * (b->cw+1);
rect.top = b->ch * pl;
rect.bottom = rect.top + b->ch;
FillRect(hdc, &rect, bg);
}
tl->changed = CHG_RESET;
if ( l == b->last ) /* clear to end of window */
{ rect.left = b->cw;
rect.right = b->width * (b->cw+1);
rect.top = b->ch * (pl+1);
rect.bottom = b->ch * (el+1);
FillRect(hdc, &rect, bg);
break;
}
}
rlc_place_caret(b);
b->changed = CHG_RESET;
if ( !stockbg )
DeleteObject(bg);
EndPaint(b->window, &ps);
rlc_update_scrollbar(b);
}
static void
rlc_request_redraw(RlcData b)
{ if ( b->changed & CHG_CHANGED )
{ if ( b->window )
InvalidateRect(b->window, NULL, FALSE);
} else
{ int i = b->window_start;
int y = 0;
RECT rect;
int first = TRUE;
int clear = FALSE;
rect.left = b->cw;
rect.right = (b->width+1) * b->cw;
for(; y < b->window_size; y++, i = NextLine(b, i))
{ TextLine l = &b->lines[i];
if ( l->changed & CHG_CHANGED )
{ if ( first )
{ rect.top = y * b->ch;
rect.bottom = rect.top + b->ch;
first = FALSE;
} else
rect.bottom = (y+1) * b->ch;
if ( l->changed & CHG_CLEAR )
clear = TRUE;
}
if ( i == b->last )
break;
}
if ( !first && b->window )
InvalidateRect(b->window, &rect, FALSE); /*clear);*/
else if ( b->changed & CHG_CARET )
rlc_place_caret(b);
}
}
static void
rlc_normalise(RlcData b)
{ if ( rlc_count_lines(b, b->window_start, b->caret_y) >= b->window_size )
{ b->window_start = rlc_add_lines(b, b->caret_y, -(b->window_size-1));
b->changed |= CHG_CARET|CHG_CLEAR|CHG_CHANGED;
rlc_request_redraw(b);
}
}
static void
rlc_resize_pixel_units(RlcData b, int w, int h)
{ int nw = max(20, w/b->cw)-2; /* 1 character space for margins */
int nh = max(1, h/b->ch);
DEBUG(Dprintf(_T("rlc_resize_pixel_units(%p, %d, %d) (%dx%d)\n"),
b, w, h, nw, nh));
if ( b->width == nw && b->window_size == nh )
return; /* no real change */
rlc_resize(b, nw, nh);
if ( _rlc_resize_hook )
(*_rlc_resize_hook)(b->width, b->window_size);
else
{
#ifdef SIGWINCH
raise(SIGWINCH);
#endif
}
rlc_request_redraw(b);
}
/*******************************
* FONT *
*******************************/
static void
rlc_init_text_dimensions(RlcData b, HFONT font)
{ HDC hdc;
TEXTMETRIC tm;
if ( font )
{ b->hfont = font;
} else if ( b->create_attributes )
{ rlc_console_attr *a = b->create_attributes;
if ( !a->face_name[0] )
b->hfont = GetStockObject(ANSI_FIXED_FONT);
else
{ LOGFONT lfont;
memset(&lfont, 0, sizeof(lfont));
lfont.lfHeight = a->font_size;
lfont.lfWeight = a->font_weight;
lfont.lfPitchAndFamily = a->font_family;
lfont.lfCharSet = a->font_char_set;
_tcsncpy(lfont.lfFaceName, a->face_name, 31);
if ( !(b->hfont = CreateFontIndirect(&lfont)) )
b->hfont = GetStockObject(ANSI_FIXED_FONT);
}
} else
b->hfont = GetStockObject(ANSI_FIXED_FONT);
/* test for fixed?*/
hdc = GetDC(NULL);
SelectObject(hdc, b->hfont);
GetTextMetrics(hdc, &tm);
b->cw = tm.tmAveCharWidth;
b->cb = tm.tmHeight;
b->ch = tm.tmHeight + tm.tmExternalLeading;
b->fixedfont = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH ? FALSE : TRUE);
ReleaseDC(NULL, hdc);
if ( b->window )
{ RECT rect;
if ( b->has_focus == TRUE )
{ CreateCaret(b->window, NULL, b->fixedfont ? b->cw : 3, b->ch-1);
rlc_place_caret(b);
}
GetClientRect(b->window, &rect);
rlc_resize_pixel_units(b, rect.right - rect.left, rect.bottom - rect.top);
}
}
static int
text_width(RlcData b, HDC hdc, const TCHAR *text, int len)
{ if ( b->fixedfont )
{ return len * b->cw;
} else
{ SIZE size;
GetTextExtentPoint32(hdc, text, len, &size);
return size.cx;
}
}
static void
rlc_save_font_options(HFONT font, rlc_console_attr *attr)
{ if ( font == GetStockObject(ANSI_FIXED_FONT) )
{ attr->face_name[0] = '\0';
} else
{ LOGFONT lf;
if ( GetObject(font, sizeof(lf), &lf) )
{ memcpy(attr->face_name, lf.lfFaceName, sizeof(attr->face_name)-1);
attr->font_family = lf.lfPitchAndFamily;
attr->font_size = lf.lfHeight;
attr->font_weight = lf.lfWeight;
attr->font_char_set = lf.lfCharSet;
}
}
}
/*******************************
* FONT SELECTION *
*******************************/
static void
rlc_queryfont(RlcData b)
{ CHOOSEFONT cf;
LOGFONT lf;
memset(&cf, 0, sizeof(cf));
memset(&lf, 0, sizeof(lf));
lf.lfHeight = 16;
lf.lfWeight = FW_NORMAL;
lf.lfPitchAndFamily = FIXED_PITCH|FF_MODERN;
cf.lStructSize = sizeof(cf);
cf.hwndOwner = b->window;
cf.lpLogFont = &lf;
cf.Flags = CF_SCREENFONTS|
CF_NOVERTFONTS|
CF_NOSIMULATIONS|
CF_FORCEFONTEXIST|
CF_INITTOLOGFONTSTRUCT;
cf.nFontType = SCREEN_FONTTYPE;
if ( ChooseFont(&cf) )
{ HFONT f;
if ( (f = CreateFontIndirect(&lf)) )
{ rlc_init_text_dimensions(b, f);
InvalidateRect(b->window, NULL, TRUE);
}
}
}
/*******************************
* BUFFER INITIALISATION *
*******************************/
static RlcData
rlc_make_buffer(int w, int h)
{ RlcData b = rlc_malloc(sizeof(rlc_data));
int i;
memset(b, 0, sizeof(*b));
b->magic = RLC_MAGIC;
b->height = h;
b->width = w;
b->window_size = 25;
b->lines = rlc_malloc(sizeof(text_line) * h);
b->cmdstat = CMD_INITIAL;
b->changed = CHG_CARET|CHG_CHANGED|CHG_CLEAR;
b->imode = IMODE_COOKED; /* switch on first rlc_read() call */
b->imodeswitch = FALSE;
b->lhead = NULL;
b->ltail = NULL;
memset(b->lines, 0, sizeof(text_line) * h);
for(i=0; i<h; i++)
b->lines[i].adjusted = TRUE;
rlc_init_word_chars();
return b;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Copy all lines one `back' (i.e. towards older lines). If the oldest
(first) line is adjacent to the last, throw it away.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static void
rlc_shift_lines_down(RlcData b, int line)
{ int i = b->first;
int p = PrevLine(b, i);
if ( p != b->last ) /* move first (oldest line) */
{ b->first = p;
b->lines[p] = b->lines[i];
} else /* delete first (oldest) line */
rlc_free_line(b, b->first);
/* copy the lines */
for(p=i, i = NextLine(b, i); p != line; p=i, i = NextLine(b, i))
b->lines[p] = b->lines[i];
b->lines[line].text = NULL; /* make this one `free' */
b->lines[line].size = 0;
b->lines[line].adjusted = TRUE;
b->lines[line].softreturn = FALSE;
}
static void
rlc_shift_lines_up(RlcData b, int line)
{ int prev = PrevLine(b, line);
while(line != b->first)
{ b->lines[line] = b->lines[prev];
line = prev;
prev = PrevLine(b, prev);
}
rlc_reinit_line(b, b->first);
b->first = NextLine(b, b->first);
}
static void
rlc_resize(RlcData b, int w, int h)
{ int i;
if ( b->width == w && b->window_size == h )
return; /* no real change */
DEBUG(Dprintf(_T("Resizing %dx%d --> %dx%d\n"),
b->width, b->window_size, w, h));
b->window_size = h;
b->width = w;
for(i = b->first; /*i != b->last*/; i = NextLine(b, i))
{ TextLine tl = &b->lines[i];
if ( tl->text && tl->adjusted == FALSE )
rlc_adjust_line(b, i);
if ( tl->size > w )
{ if ( !tl->softreturn ) /* hard --> soft */
{ TextLine pl;
rlc_shift_lines_down(b, i);
DEBUG(Dprint_lines(b, b->first, b->first));
DEBUG(Dprintf(_T("b->first = %d, b->last = %d\n"), b->first, b->last));
pl = &b->lines[PrevLine(b, i)]; /* this is the moved line */
tl->text = rlc_malloc((pl->size - w)*sizeof(TCHAR));
memmove(tl->text, &pl->text[w], (pl->size - w)*sizeof(TCHAR));
DEBUG(Dprintf(_T("Copied %d chars from line %d to %d\n"),
pl->size - w, pl - b->lines, i));
tl->size = pl->size - w;
tl->adjusted = TRUE;
tl->softreturn = FALSE;
pl->softreturn = TRUE;
pl->text = rlc_realloc(pl->text, w * sizeof(TCHAR));
pl->size = w;
pl->adjusted = TRUE;
i = (int)(pl - b->lines);
DEBUG(Dprint_lines(b, b->first, b->last));
} else /* put in next line */
{ TextLine nl;
int move = tl->size - w;
if ( i == b->last )
rlc_add_line(b);
nl = &b->lines[NextLine(b, i)];
nl->text = rlc_realloc(nl->text, (nl->size + move)*sizeof(TCHAR));
memmove(&nl->text[move], nl->text, nl->size*sizeof(TCHAR));
memmove(nl->text, &tl->text[w], move*sizeof(TCHAR));
nl->size += move;
tl->size = w;
}
} else if ( tl->text && tl->softreturn && tl->size < w )
{ TextLine nl;
if ( i == b->last )
rlc_add_line(b);
nl = &b->lines[NextLine(b, i)];
nl->text = rlc_realloc(nl->text, (nl->size + tl->size)*sizeof(TCHAR));
memmove(&nl->text[tl->size], nl->text, nl->size*sizeof(TCHAR));
memmove(nl->text, tl->text, tl->size*sizeof(TCHAR));
nl->size += tl->size;
nl->adjusted = TRUE;
rlc_shift_lines_up(b, i);
}
if ( i == b->last )
break;
}
for(i = NextLine(b, i); i != b->first; i = NextLine(b, i))
rlc_free_line(b, i);
if ( rlc_count_lines(b, b->first, b->last) < h )
b->window_start = b->first;
else
b->window_start = rlc_add_lines(b, b->last, -(h-1));
b->caret_y = b->last;
b->caret_x = b->lines[b->last].size;
b->changed |= CHG_CARET|CHG_CHANGED|CHG_CLEAR;
rlc_check_assertions(b);
}
static void
rlc_reinit_line(RlcData b, int line)
{ TextLine tl = &b->lines[line];
tl->text = NULL;
tl->adjusted = FALSE;
tl->size = 0;
tl->softreturn = FALSE;
}
static void
rlc_free_line(RlcData b, int line)
{ TextLine tl = &b->lines[line];
if ( tl->text )
{ rlc_free(tl->text);
rlc_reinit_line(b, line);
}
}
static void
rlc_adjust_line(RlcData b, int line)
{ TextLine tl = &b->lines[line];
if ( tl->text && !tl->adjusted )
{ tl->text = rlc_realloc(tl->text, tl->size == 0
? sizeof(TCHAR)
: tl->size * sizeof(TCHAR));
tl->adjusted = TRUE;
}
}
static void
rlc_unadjust_line(RlcData b, int line)
{ TextLine tl = &b->lines[line];
if ( tl->text )
{ if ( tl->adjusted )
{ tl->text = rlc_realloc(tl->text, (b->width + 1)*sizeof(TCHAR));
tl->adjusted = FALSE;
}
} else
{ tl->text = rlc_malloc((b->width + 1)*sizeof(TCHAR));
tl->adjusted = FALSE;
tl->size = 0;
}
}
static void
rlc_open_line(RlcData b)
{ int i = b->last;
if ( i == b->sel_start_line )
rlc_set_selection(b, 0, 0, 0, 0); /* clear the selection */
if ( i == b->first )
{ rlc_free_line(b, b->first);
b->first = NextLine(b, b->first);
}
b->lines[i].text = rlc_malloc((b->width + 1)*sizeof(TCHAR));
b->lines[i].adjusted = FALSE;
b->lines[i].size = 0;
b->lines[i].softreturn = FALSE;
}
static void
rlc_add_line(RlcData b)
{ b->last = NextLine(b, b->last);
rlc_open_line(b);
}
/*******************************
* CALCULATIONS *
*******************************/
static int
rlc_count_lines(RlcData b, int from, int to)
{ if ( to >= from )
return to-from;
return to + b->height - from;
}
static int
rlc_add_lines(RlcData b, int here, int add)
{ here += add;
while ( here < 0 )
here += b->height;
while ( here >= b->height )
here -= b->height;
return here;
}
/*******************************
* ANSI SEQUENCE HANDLING *
*******************************/
static void
rlc_need_arg(RlcData b, int arg, int def)
{ if ( b->argc < arg )
{ b->argv[arg-1] = def;
b->argc = arg;
}
}
static void
rlc_caret_up(RlcData b, int arg)
{ while(arg-- > 0 && b->caret_y != b->first)
b->caret_y = PrevLine(b, b->caret_y);
b->changed |= CHG_CARET;
}
static void
rlc_caret_down(RlcData b, int arg)
{ while ( arg-- > 0 )
{ if ( b->caret_y == b->last )
rlc_add_line(b);
b->caret_y = NextLine(b, b->caret_y);
b->lines[b->caret_y].softreturn = FALSE; /* ? why not only on open? */
}
b->changed |= CHG_CARET;
/* scroll? */
if ( rlc_count_lines(b, b->window_start, b->caret_y) >= b->window_size )
{ b->window_start = rlc_add_lines(b, b->caret_y, -(b->window_size-1));
b->changed |= CHG_CHANGED|CHG_CLEAR;
}
rlc_check_assertions(b);
}
static void
rlc_caret_forward(RlcData b, int arg)
{ while(arg-- > 0)
{ if ( ++b->caret_x >= b->width )
{ b->lines[b->caret_y].softreturn = TRUE;
b->caret_x = 0;
rlc_caret_down(b, 1);
}
}
b->changed |= CHG_CARET;
}
static void
rlc_caret_backward(RlcData b, int arg)
{ while(arg-- > 0)
{ if ( b->caret_x-- == 0 )
{ rlc_caret_up(b, 1);
b->caret_x = b->width-1;
}
}
b->changed |= CHG_CARET;
}
static void
rlc_cariage_return(RlcData b)
{ b->caret_x = 0;
b->changed |= CHG_CARET;
}
static void
rlc_tab(RlcData b)
{ TextLine tl = &b->lines[b->caret_y];
do
{ rlc_caret_forward(b, 1);
} while( (b->caret_x % 8) != 0 );
if ( tl->size < b->caret_x )
{ rlc_unadjust_line(b, b->caret_y);
while ( tl->size < b->caret_x )
tl->text[tl->size++] = ' ';
}
b->changed |= CHG_CARET;
}
static void
rlc_set_caret(RlcData b, int x, int y)
{ int cy = rlc_count_lines(b, b->window_start, b->caret_y);
y = Bounds(y, 0, b->window_size);
if ( y < cy )
b->caret_y = rlc_add_lines(b, b->window_start, y);
else
rlc_caret_down(b, y-cy);
b->caret_x = Bounds(x, 0, b->width-1);
b->changed |= CHG_CARET;
}
static void
rlc_save_caret_position(RlcData b)
{ b->scaret_y = rlc_count_lines(b, b->window_start, b->caret_y);
b->scaret_x = b->caret_x;
}
static void
rlc_restore_caret_position(RlcData b)
{ rlc_set_caret(b, b->scaret_x, b->scaret_y);
}
static void
rlc_erase_display(RlcData b)
{ int i = b->window_start;
int last = rlc_add_lines(b, b->window_start, b->window_size);
do
{ b->lines[i].size = 0;
i = NextLine(b, i);
} while ( i != last );
b->changed |= CHG_CHANGED|CHG_CLEAR|CHG_CARET;
rlc_set_caret(b, 0, 0);
}
static void
rlc_erase_line(RlcData b)
{ TextLine tl = &b->lines[b->caret_y];
tl->size = b->caret_x;
tl->changed |= CHG_CHANGED|CHG_CLEAR;
}
static void
rlc_put(RlcData b, int chr)
{ TextLine tl = &b->lines[b->caret_y];
rlc_unadjust_line(b, b->caret_y);
while( tl->size < b->caret_x )
tl->text[tl->size++] = ' ';
tl->text[b->caret_x] = chr;
if ( tl->size <= b->caret_x )
tl->size = b->caret_x + 1;
tl->changed |= CHG_CHANGED;
rlc_caret_forward(b, 1);
}
#ifdef _DEBUG
#define CMD(c) {cmd = _T(#c); c;}
#else
#define CMD(c) {c;}
#endif
static void
rlc_putansi(RlcData b, int chr)
{
#ifdef _DEBUG
TCHAR *cmd;
#endif
switch(b->cmdstat)
{ case CMD_INITIAL:
switch(chr)
{ case '\b':
CMD(rlc_caret_backward(b, 1));
break;
case Control('G'):
MessageBeep(MB_ICONEXCLAMATION);
break;
case '\r':
CMD(rlc_cariage_return(b));
break;
case '\n':
CMD(rlc_caret_down(b, 1));
break;
case '\t':
CMD(rlc_tab(b));
break;
case 27: /* ESC */
b->cmdstat = CMD_ESC;
break;
default:
CMD(rlc_put(b, chr));
break;
}
break;
case CMD_ESC:
switch(chr)
{ case '[':
b->cmdstat = CMD_ANSI;
b->argc = 0;
b->argstat = 0; /* no arg */
break;
default:
b->cmdstat = CMD_INITIAL;
break;
}
break;
case CMD_ANSI: /* ESC [ */
if ( _istdigit((wint_t)chr) )
{ if ( !b->argstat )
{ b->argv[b->argc] = (chr - '0');
b->argstat = 1; /* positive */
} else
{ b->argv[b->argc] = b->argv[b->argc] * 10 + (chr - '0');
}
break;
}
if ( !b->argstat && chr == '-' )
{ b->argstat = -1; /* negative */
break;
}
if ( b->argstat )
{ b->argv[b->argc] *= b->argstat;
if ( b->argc < (ANSI_MAX_ARGC-1) )
b->argc++; /* silently discard more of them */
b->argstat = 0;
}
switch(chr)
{ case ';':
break; /* wait for more args */
case 'H':
case 'f':
rlc_need_arg(b, 1, 0);
rlc_need_arg(b, 2, 0);
CMD(rlc_set_caret(b, b->argv[0], b->argv[1]));
break;
case 'A':
rlc_need_arg(b, 1, 1);
CMD(rlc_caret_up(b, b->argv[0]));
break;
case 'B':
rlc_need_arg(b, 1, 1);
CMD(rlc_caret_down(b, b->argv[0]));
break;
case 'C':
rlc_need_arg(b, 1, 1);
CMD(rlc_caret_forward(b, b->argv[0]));
break;
case 'D':
rlc_need_arg(b, 1, 1);
CMD(rlc_caret_backward(b, b->argv[0]));
break;
case 's':
CMD(rlc_save_caret_position(b));
break;
case 'u':
CMD(rlc_restore_caret_position(b));
break;
case 'J':
if ( b->argv[0] == 2 )
CMD(rlc_erase_display(b));
break;
case 'K':
CMD(rlc_erase_line(b));
break;
}
b->cmdstat = CMD_INITIAL;
}
rlc_check_assertions(b);
}
/*******************************
* CUT/PASTE *
*******************************/
static void
rlc_paste(RlcData b)
{ HGLOBAL mem;
if ( b->window )
{ OpenClipboard(b->window);
if ( (mem = GetClipboardData(CF_UNICODETEXT)) )
{ wchar_t *data = GlobalLock(mem);
int i;
RlcQueue q = b->queue;
if ( q )
{ for(i=0; data[i]; i++)
{ rlc_add_queue(b, q, data[i]);
if ( data[i] == '\r' && data[i+1] == '\n' )
i++;
}
}
GlobalUnlock(mem);
} else if ( (mem = GetClipboardData(CF_TEXT)) )
{ char far *data = GlobalLock(mem);
int i;
RlcQueue q = b->queue;
if ( q )
{ for(i=0; data[i]; i++)
{ rlc_add_queue(b, q, data[i]);
if ( data[i] == '\r' && data[i+1] == '\n' )
i++;
}
}
GlobalUnlock(mem);
}
CloseClipboard();
}
}
/*******************************
* LINE-READ SUPPORT *
*******************************/
void
rlc_get_mark(rlc_console c, RlcMark m)
{ RlcData b = rlc_get_data(c);
m->mark_x = b->caret_x;
m->mark_y = b->caret_y;
}
void
rlc_goto_mark(rlc_console c, RlcMark m, const TCHAR *data, size_t offset)
{ RlcData b = rlc_get_data(c);
b->caret_x = m->mark_x;
b->caret_y = m->mark_y;
for( ; offset-- > 0; data++ )
{ switch(*data)
{ case '\t':
rlc_tab(b);
break;
case '\n':
b->caret_x = 0;
rlc_caret_down(b, 1);
break;
default:
rlc_caret_forward(b, 1);
}
}
}
void
rlc_erase_from_caret(rlc_console c)
{ RlcData b = rlc_get_data(c);
int i = b->caret_y;
int x = b->caret_x;
int last = rlc_add_lines(b, b->window_start, b->window_size);
do
{ TextLine tl = &b->lines[i];
if ( tl->size != x )
{ tl->size = x;
tl->changed |= CHG_CHANGED|CHG_CLEAR;
}
i = NextLine(b, i);
x = 0;
} while ( i != last );
}
void
rlc_putchar(rlc_console c, int chr)
{ RlcData b = rlc_get_data(c);
rlc_putansi(b, chr);
}
TCHAR *
rlc_read_screen(rlc_console c, RlcMark f, RlcMark t)
{ RlcData b = rlc_get_data(c);
TCHAR *buf;
buf = rlc_read_from_window(b, f->mark_y, f->mark_x, t->mark_y, t->mark_x);
return buf;
}
void
rlc_update(rlc_console c)
{ RlcData b = rlc_get_data(c);
if ( b->window )
{ rlc_normalise(b);
rlc_request_redraw(b);
UpdateWindow(b->window);
}
}
/*******************************
* UPDATE THREAD *
*******************************/
DWORD WINAPI
window_loop(LPVOID arg)
{ RlcData b = (RlcData) arg;
rlc_create_window(b);
/* if we do not do this, all windows */
/* created by Prolog (XPCE) will be */
/* in the background and inactive! */
if ( !AttachThreadInput(b->application_thread_id,
b->console_thread_id, TRUE) )
rlc_putansi(b, '!');
PostThreadMessage(b->application_thread_id, WM_RLC_READY, 0, 0);
while(!b->closing)
{ switch( b->imode )
{ case IMODE_COOKED:
{ TCHAR *line = read_line(b);
if ( line != RL_CANCELED_CHARP )
{ LQueued lq = rlc_malloc(sizeof(lqueued));
lq->next = NULL;
lq->line = line;
if ( b->ltail )
{ b->ltail->next = lq;
b->ltail = lq;
} else
{ b->lhead = b->ltail = lq;
/* awake main thread */
PostThreadMessage(b->application_thread_id, WM_RLC_INPUT, 0, 0);
}
}
break;
}
case IMODE_RAW:
{ MSG msg;
if ( rlc_get_message(&msg, NULL, 0, 0) )
{ TranslateMessage(&msg);
DispatchMessage(&msg);
rlc_flush_output(b);
} else
goto out;
if ( b->imodeswitch )
{ b->imodeswitch = FALSE;
}
}
}
}
if ( b->closing <= 2 )
{ MSG msg;
TCHAR *waiting = _T("\r\nWaiting for Prolog. ")
_T("Close again to force termination ..");
rlc_write(b, waiting, _tcslen(waiting));
while ( b->closing <= 2 && rlc_get_message(&msg, NULL, 0, 0) )
{ TranslateMessage(&msg);
DispatchMessage(&msg);
rlc_flush_output(b);
}
}
out:
{ DWORD appthread = b->application_thread_id;
rlc_destroy(b);
PostThreadMessage(appthread, WM_RLC_READY, 0, 0);
}
return 0;
}
/*******************************
* WATCOM/DOS I/O *
*******************************/
int
getch(rlc_console c)
{ RlcData b = rlc_get_data(c);
RlcQueue q = b->queue;
int fromcon = (GetCurrentThreadId() == b->console_thread_id);
while( rlc_is_empty_queue(q) )
{ if ( q->flags & RLC_EOF )
return EOF;
if ( !fromcon )
{ MSG msg;
if ( rlc_get_message(&msg, NULL, 0, 0) )
{ TranslateMessage(&msg);
DispatchMessage(&msg);
} else
return EOF;
} else
{ rlc_dispatch(b);
if ( b->imodeswitch )
{ b->imodeswitch = FALSE;
return IMODE_SWITCH_CHAR;
}
}
}
return rlc_from_queue(q);
}
int
getche(rlc_console c)
{ RlcData b = rlc_get_data(c);
int chr = getch(b);
rlc_putansi(b, chr);
return chr;
}
/*******************************
* GO32 FUNCTIONS *
*******************************/
int
getkey(rlc_console con)
{ int c;
RlcData b = rlc_get_data(con);
int fromcon = (GetCurrentThreadId() == b->console_thread_id);
if ( !fromcon && b->imode != IMODE_RAW )
{ int old = b->imode;
b->imode = IMODE_RAW;
b->imodeswitch = TRUE;
c = getch(b);
b->imode = old;
b->imodeswitch = TRUE;
} else
c = getch(b);
return c;
}
int
kbhit(rlc_console c)
{ RlcData b = rlc_get_data(c);
return !rlc_is_empty_queue(b->queue);
}
void
ScreenGetCursor(rlc_console c, int *row, int *col)
{ RlcData b = rlc_get_data(c);
*row = rlc_count_lines(b, b->window_start, b->caret_y) + 1;
*col = b->caret_x + 1;
}
void
ScreenSetCursor(rlc_console c, int row, int col)
{ RlcData b = rlc_get_data(c);
rlc_set_caret(b, col-1, row-1);
}
int
ScreenCols(rlc_console c)
{ RlcData b = rlc_get_data(c);
return b->width;
}
int
ScreenRows(rlc_console c)
{ RlcData b = rlc_get_data(c);
return b->window_size;
}
/*******************************
* QUEUE *
*******************************/
#define QN(q, i) ((i)+1 >= (q)->size ? 0 : (i)+1)
RlcQueue
rlc_make_queue(int size)
{ RlcQueue q;
if ( (q = rlc_malloc(sizeof(rlc_queue))) )
{ q->first = q->last = 0;
q->size = size;
q->flags = 0;
if ( (q->buffer = rlc_malloc(sizeof(TCHAR) * size)) )
return q;
}
return NULL; /* not enough memory */
}
void
rlc_free_queue(RlcQueue q)
{ if ( q )
{ if ( q->buffer )
rlc_free(q->buffer);
rlc_free(q);
}
}
static int
rlc_resize_queue(RlcQueue q, int size)
{ TCHAR *newbuf;
if ( (newbuf = rlc_malloc(size*sizeof(TCHAR))) )
{ TCHAR *o = newbuf;
int c;
while( (c=rlc_from_queue(q)) != -1 )
*o++ = c;
if ( q->buffer )
rlc_free(q->buffer);
q->buffer = newbuf;
q->first = 0;
q->last = (int)(o-newbuf);
q->size = size;
return TRUE;
}
return FALSE;
}
static int
rlc_add_queue(RlcData b, RlcQueue q, int chr)
{ int empty = (q->first == q->last);
while(q->size < 50000)
{ if ( QN(q, q->last) != q->first )
{ q->buffer[q->last] = chr;
q->last = QN(q, q->last);
if ( empty )
PostThreadMessage(b->application_thread_id, WM_RLC_INPUT, 0, 0);
return TRUE;
}
rlc_resize_queue(q, q->size*2);
}
return FALSE;
}
int
rlc_is_empty_queue(RlcQueue q)
{ if ( q->first == q->last )
return TRUE;
return FALSE;
}
void
rlc_empty_queue(RlcQueue q)
{ q->first = q->last = 0;
}
static int
rlc_from_queue(RlcQueue q)
{ if ( q->first != q->last )
{ int chr = q->buffer[q->first];
q->first = QN(q, q->first);
return chr;
}
return -1;
}
/*******************************
* BUFFERED I/O *
*******************************/
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
When using UNICODE, count is in bytes!
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
size_t
rlc_read(rlc_console c, TCHAR *buf, size_t count)
{ RlcData d = rlc_get_data(c);
size_t give;
MSG msg;
if ( d->closing )
return 0; /* signal EOF when closing */
PostThreadMessage(d->console_thread_id,
WM_RLC_FLUSH,
0, 0);
if ( _rlc_update_hook )
(*_rlc_update_hook)();
d->promptbuf[d->promptlen] = EOS;
_tcscpy(d->prompt, d->promptbuf);
if ( d->read_buffer.given >= d->read_buffer.length )
{ if ( d->read_buffer.line )
{ rlc_free(d->read_buffer.line);
d->read_buffer.line = NULL;
}
if ( d->imode != IMODE_COOKED )
{ d->imode = IMODE_COOKED;
d->imodeswitch = TRUE;
}
while(!d->lhead)
{ if ( rlc_get_message(&msg, NULL, 0, 0) )
{ TranslateMessage(&msg);
DispatchMessage(&msg);
} else
return -1;
}
{ LQueued lq = d->lhead;
d->read_buffer.line = lq->line;
if ( lq->next )
d->lhead = lq->next;
else
d->lhead = d->ltail = NULL;
rlc_free(lq);
}
d->read_buffer.length = _tcslen(d->read_buffer.line);
d->read_buffer.given = 0;
}
if ( d->read_buffer.length - d->read_buffer.given > count )
give = count;
else
give = d->read_buffer.length - d->read_buffer.given;
_tcsncpy(buf, d->read_buffer.line+d->read_buffer.given, give);
d->read_buffer.given += give;
return give;
}
static void
rlc_do_write(RlcData b, TCHAR *buf, int count)
{ if ( count > 0 )
{ int n = 0;
TCHAR *s = buf;
while(n++ < count)
{ int chr = *s++;
if ( chr == '\n' )
rlc_putansi(b, '\r');
rlc_putansi(b, chr);
}
rlc_normalise(b);
if ( b->window )
{ rlc_request_redraw(b);
UpdateWindow(b->window);
}
}
}
int
rlc_flush_output(rlc_console c)
{ RlcData b = rlc_get_data(c);
if ( !b )
return -1;
if ( b->output_queued )
{ rlc_do_write(b, b->output_queue, b->output_queued);
b->output_queued = 0;
}
return 0;
}
size_t
rlc_write(rlc_console c, TCHAR *buf, size_t count)
{ DWORD_PTR result;
TCHAR *e, *s;
RlcData b = rlc_get_data(c);
if ( !b )
return -1;
for(s=buf, e=&buf[count]; s<e; s++)
{ if ( *s == '\n' )
b->promptlen = 0;
else if ( b->promptlen < MAXPROMPT-1 )
b->promptbuf[b->promptlen++] = *s;
}
if ( b->window )
{ if ( SendMessageTimeout(b->window,
WM_RLC_WRITE,
(WPARAM)count,
(LPARAM)buf,
SMTO_NORMAL,
10000,
&result) )
{ PostMessage(b->window,
WM_RLC_FLUSH,
0, 0);
return count;
}
}
return -1; /* I/O error */
}
static void
free_rlc_data(RlcData b)
{ b->magic = 42; /* so next gets errors */
if ( b->lines )
{ int i;
for(i=0; i<b->height; i++)
{ if ( b->lines[i].text )
free(b->lines[i].text);
}
free(b->lines);
}
if ( b->read_buffer.line )
free(b->read_buffer.line);
free(b);
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
rlc_close() tries to gracefully get rid of the console thread. It does
so by posting WM_RLC_CLOSEWIN and then waiting for a WM_RLC_READY reply.
It waits for a maximum of 1.5 second, which should be fine as the
console thread should not have intptr_t-lasting activities.
If the timeout expires it hopes for the best. This was the old situation
and proved to be sound on Windows-NT, but not on 95 and '98.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
int
rlc_close(rlc_console c)
{ RlcData b = (RlcData)c;
MSG msg;
int i;
if ( b->magic != RLC_MAGIC )
return -1;
rlc_save_options(b);
b->closing = 3;
PostMessage(b->window, WM_RLC_CLOSEWIN, 0, 0);
/* wait for termination */
for(i=0; i<30; i++)
{ if ( PeekMessage(&msg, NULL, WM_RLC_READY, WM_RLC_READY, PM_REMOVE) )
break;
Sleep(50);
}
b->magic = 0;
free_user_data(c);
free_rlc_data(b);
return 0;
}
const TCHAR *
rlc_prompt(rlc_console c, const TCHAR *new)
{ RlcData b = rlc_get_data(c);
if ( b )
{ if ( new )
{ _tcsncpy(b->prompt, new, MAXPROMPT);
b->prompt[MAXPROMPT-1] = EOS;
}
return b->prompt;
}
return _T("");
}
void
rlc_clearprompt(rlc_console c)
{ RlcData b = rlc_get_data(c);
if ( b )
{ b->promptlen = 0;
b->prompt[0] = EOS;
}
}
/*******************************
* MISC STUFF *
*******************************/
static TCHAR current_title[RLC_TITLE_MAX];
void
rlc_title(rlc_console c, TCHAR *title, TCHAR *old, int size)
{ RlcData b = rlc_get_data(c);
if ( old )
memmove(old, b->current_title, size*sizeof(TCHAR));
if ( title )
{ if ( b->window )
SetWindowText(b->window, title);
memmove(b->current_title, title, RLC_TITLE_MAX*sizeof(TCHAR));
}
}
void
rlc_icon(rlc_console c, HICON icon)
{
#ifdef WIN64
SetClassLong(rlc_hwnd(c), GCLP_HICON, (LONG) icon);
#else
SetClassLong(rlc_hwnd(c), GCL_HICON, (LONG) icon);
#endif
}
int
rlc_window_pos(rlc_console c,
HWND hWndInsertAfter,
int x, int y, int w, int h,
UINT flags)
{ RlcData b = rlc_get_data(c);
if ( b )
{ w *= b->cw;
h *= b->ch;
SetWindowPos(b->window, hWndInsertAfter,
x, y, w, h,
flags);
return TRUE;
}
return FALSE;
}
HANDLE
rlc_hinstance()
{ return _rlc_hinstance;
}
HWND
rlc_hwnd(rlc_console c)
{ RlcData b = rlc_get_data(c);
return b ? b->window : (HWND)NULL;
}
/*******************************
* SETTING OPTIONS *
*******************************/
int
rlc_copy_output_to_debug_output(int new)
{ int old = _rlc_copy_output_to_debug_output;
_rlc_copy_output_to_debug_output = new;
return old;
}
RlcUpdateHook
rlc_update_hook(RlcUpdateHook new)
{ RlcUpdateHook old = _rlc_update_hook;
_rlc_update_hook = new;
return old;
}
RlcTimerHook
rlc_timer_hook(RlcTimerHook new)
{ RlcTimerHook old = _rlc_timer_hook;
_rlc_timer_hook = new;
return old;
}
RlcRenderHook
rlc_render_hook(RlcRenderHook new)
{ RlcRenderHook old = _rlc_render_hook;
_rlc_render_hook = new;
return old;
}
RlcRenderAllHook
rlc_render_all_hook(RlcRenderAllHook new)
{ RlcRenderAllHook old = _rlc_render_all_hook;
_rlc_render_all_hook = new;
return old;
}
RlcInterruptHook
rlc_interrupt_hook(RlcInterruptHook new)
{ RlcInterruptHook old = _rlc_interrupt_hook;
_rlc_interrupt_hook = new;
return old;
}
RlcResizeHook
rlc_resize_hook(RlcResizeHook new)
{ RlcResizeHook old = _rlc_resize_hook;
_rlc_resize_hook = new;
return old;
}
RlcMenuHook
rlc_menu_hook(RlcMenuHook new)
{ RlcMenuHook old = _rlc_menu_hook;
_rlc_menu_hook = new;
return old;
}
RlcMessageHook
rlc_message_hook(RlcMessageHook new)
{ RlcMessageHook old = _rlc_message_hook;
_rlc_message_hook = new;
return old;
}
int
rlc_set(rlc_console c, int what, uintptr_t data, RlcFreeDataHook hook)
{ RlcData b = rlc_get_data(c);
switch(what)
{ default:
if ( what >= RLC_VALUE(0) &&
what <= RLC_VALUE(MAX_USER_VALUES) )
{ b->values[what-RLC_VALUE(0)].data = data;
b->values[what-RLC_VALUE(0)].hook = hook;
return TRUE;
}
return FALSE;
}
}
int
rlc_get(rlc_console c, int what, uintptr_t *data)
{ RlcData b = (RlcData)c;
if ( !b )
return FALSE;
switch(what)
{ case RLC_APPLICATION_THREAD:
*data = (uintptr_t)b->application_thread;
return TRUE;
case RLC_APPLICATION_THREAD_ID:
*data = (uintptr_t)b->application_thread_id;
return TRUE;
default:
if ( what >= RLC_VALUE(0) &&
what <= RLC_VALUE(MAX_USER_VALUES) )
{ *data = b->values[what-RLC_VALUE(0)].data;
return TRUE;
}
return FALSE;
}
}
static void
free_user_data(RlcData b)
{ user_data *d = b->values;
int i;
for(i=0; i<MAX_USER_VALUES; i++, d++)
{ RlcFreeDataHook hook;
if ( (hook=d->hook) )
{ uintptr_t data = d->data;
d->hook = NULL;
d->data = 0L;
(*hook)(data);
}
}
}
/*******************************
* UTIL *
*******************************/
static void
noMemory()
{ MessageBox(NULL, _T("Not enough memory"), _T("Console"), MB_OK|MB_TASKMODAL);
ExitProcess(1);
}
void *
rlc_malloc(size_t size)
{ void *ptr = malloc(size);
if ( !ptr && size > 0 )
noMemory();
#ifdef _DEBUG
memset(ptr, 0xbf, size);
#endif
return ptr;
}
void *
rlc_realloc(void *ptr, size_t size)
{ void *ptr2 = realloc(ptr, size);
if ( !ptr2 && size > 0 )
noMemory();
return ptr2;
}
void
rlc_free(void *ptr)
{ free(ptr);
}
#ifndef initHeapDebug
/*******************************
* DEBUG *
*******************************/
static void
initHeapDebug(void)
{ int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
if ( !(tmpFlag & _CRTDBG_CHECK_ALWAYS_DF) )
{ /*MessageBox(NULL,
"setting malloc() debugging",
"SWI-Prolog console",
MB_OK|MB_TASKMODAL);*/
tmpFlag |= _CRTDBG_CHECK_ALWAYS_DF;
_CrtSetDbgFlag(tmpFlag);
} else
{
/*MessageBox(NULL,
"malloc() debugging lready set",
"SWI-Prolog console",
MB_OK|MB_TASKMODAL);*/
}
}
#endif /*initHeapDebug*/
#ifdef _DEBUG
static void
Dprintf(const TCHAR *fmt, ...)
{ TCHAR buf[1024];
va_list args;
va_start(args, fmt);
_vstprintf(buf, fmt, args);
va_end(args);
OutputDebugString(buf);
}
static void
Dprint_lines(RlcData b, int from, int to)
{ TCHAR buf[1024];
for( ; ; from = NextLine(b, from))
{ TextLine tl = &b->lines[from];
memcpy(buf, tl->text, tl->size);
buf[tl->size] = EOS;
Dprintf(_T("%03d: (0x%08x) \"%s\"\n"), from, tl->text, buf);
if ( from == to )
break;
}
}
#endif