3660 lines
79 KiB
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)
|
||
|
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
|