TermView.cpp revision 9b46a2b25d3026bd60dd4011ee6244b7e591a9e5
1/*
2 * Copyright 2001-2010, Haiku, Inc.
3 * Copyright 2003-2004 Kian Duffy, myob@users.sourceforge.net
4 * Parts Copyright 1998-1999 Kazuho Okui and Takashi Murai.
5 * All rights reserved. Distributed under the terms of the MIT license.
6 *
7 * Authors:
8 *		Stefano Ceccherini <stefano.ceccherini@gmail.com>
9 *		Kian Duffy, myob@users.sourceforge.net
10 *		Y.Hayakawa, hida@sawada.riec.tohoku.ac.jp
11 *		Ingo Weinhold <ingo_weinhold@gmx.de>
12 *		Clemens Zeidler <haiku@Clemens-Zeidler.de>
13 */
14
15
16#include "TermView.h"
17
18#include <ctype.h>
19#include <signal.h>
20#include <stdlib.h>
21#include <string.h>
22#include <termios.h>
23
24#include <algorithm>
25#include <new>
26
27#include <Alert.h>
28#include <Application.h>
29#include <Beep.h>
30#include <Catalog.h>
31#include <Clipboard.h>
32#include <Debug.h>
33#include <Directory.h>
34#include <Dragger.h>
35#include <Input.h>
36#include <Locale.h>
37#include <MenuItem.h>
38#include <Message.h>
39#include <MessageRunner.h>
40#include <Node.h>
41#include <Path.h>
42#include <PopUpMenu.h>
43#include <PropertyInfo.h>
44#include <Region.h>
45#include <Roster.h>
46#include <ScrollBar.h>
47#include <ScrollView.h>
48#include <String.h>
49#include <StringView.h>
50#include <Window.h>
51
52#include "Encoding.h"
53#include "InlineInput.h"
54#include "Shell.h"
55#include "TermConst.h"
56#include "TerminalBuffer.h"
57#include "TerminalCharClassifier.h"
58#include "VTkeymap.h"
59
60
61// defined in VTKeyTbl.c
62extern int function_keycode_table[];
63extern char *function_key_char_table[];
64
65static rgb_color kTermColorTable[256] = {
66	{ 40,  40,  40, 0},	// black
67	{204,   0,   0, 0},	// red
68	{ 78, 154,   6, 0},	// green
69	{218, 168,   0, 0},	// yellow
70	{ 51, 102, 152, 0},	// blue
71	{115,  68, 123, 0},	// magenta
72	{  6, 152, 154, 0},	// cyan
73	{245, 245, 245, 0},	// white
74
75	{128, 128, 128, 0},	// black
76	{255,   0,   0, 0},	// red
77	{  0, 255,   0, 0},	// green
78	{255, 255,   0, 0},	// yellow
79	{  0,   0, 255, 0},	// blue
80	{255,   0, 255, 0},	// magenta
81	{  0, 255, 255, 0},	// cyan
82	{255, 255, 255, 0},	// white
83
84	{  0,   0,   0, 0},
85	{  0,   0,  51, 0},
86	{  0,   0, 102, 0},
87	{  0,   0, 153, 0},
88	{  0,   0, 204, 0},
89	{  0,   0, 255, 0},
90	{  0,  51,   0, 0},
91	{  0,  51,  51, 0},
92	{  0,  51, 102, 0},
93	{  0,  51, 153, 0},
94	{  0,  51, 204, 0},
95	{  0,  51, 255, 0},
96	{  0, 102,   0, 0},
97	{  0, 102,  51, 0},
98	{  0, 102, 102, 0},
99	{  0, 102, 153, 0},
100	{  0, 102, 204, 0},
101	{  0, 102, 255, 0},
102	{  0, 153,   0, 0},
103	{  0, 153,  51, 0},
104	{  0, 153, 102, 0},
105	{  0, 153, 153, 0},
106	{  0, 153, 204, 0},
107	{  0, 153, 255, 0},
108	{  0, 204,   0, 0},
109	{  0, 204,  51, 0},
110	{  0, 204, 102, 0},
111	{  0, 204, 153, 0},
112	{  0, 204, 204, 0},
113	{  0, 204, 255, 0},
114	{  0, 255,   0, 0},
115	{  0, 255,  51, 0},
116	{  0, 255, 102, 0},
117	{  0, 255, 153, 0},
118	{  0, 255, 204, 0},
119	{  0, 255, 255, 0},
120	{ 51,   0,   0, 0},
121	{ 51,   0,  51, 0},
122	{ 51,   0, 102, 0},
123	{ 51,   0, 153, 0},
124	{ 51,   0, 204, 0},
125	{ 51,   0, 255, 0},
126	{ 51,  51,   0, 0},
127	{ 51,  51,  51, 0},
128	{ 51,  51, 102, 0},
129	{ 51,  51, 153, 0},
130	{ 51,  51, 204, 0},
131	{ 51,  51, 255, 0},
132	{ 51, 102,   0, 0},
133	{ 51, 102,  51, 0},
134	{ 51, 102, 102, 0},
135	{ 51, 102, 153, 0},
136	{ 51, 102, 204, 0},
137	{ 51, 102, 255, 0},
138	{ 51, 153,   0, 0},
139	{ 51, 153,  51, 0},
140	{ 51, 153, 102, 0},
141	{ 51, 153, 153, 0},
142	{ 51, 153, 204, 0},
143	{ 51, 153, 255, 0},
144	{ 51, 204,   0, 0},
145	{ 51, 204,  51, 0},
146	{ 51, 204, 102, 0},
147	{ 51, 204, 153, 0},
148	{ 51, 204, 204, 0},
149	{ 51, 204, 255, 0},
150	{ 51, 255,   0, 0},
151	{ 51, 255,  51, 0},
152	{ 51, 255, 102, 0},
153	{ 51, 255, 153, 0},
154	{ 51, 255, 204, 0},
155	{ 51, 255, 255, 0},
156	{102,   0,   0, 0},
157	{102,   0,  51, 0},
158	{102,   0, 102, 0},
159	{102,   0, 153, 0},
160	{102,   0, 204, 0},
161	{102,   0, 255, 0},
162	{102,  51,   0, 0},
163	{102,  51,  51, 0},
164	{102,  51, 102, 0},
165	{102,  51, 153, 0},
166	{102,  51, 204, 0},
167	{102,  51, 255, 0},
168	{102, 102,   0, 0},
169	{102, 102,  51, 0},
170	{102, 102, 102, 0},
171	{102, 102, 153, 0},
172	{102, 102, 204, 0},
173	{102, 102, 255, 0},
174	{102, 153,   0, 0},
175	{102, 153,  51, 0},
176	{102, 153, 102, 0},
177	{102, 153, 153, 0},
178	{102, 153, 204, 0},
179	{102, 153, 255, 0},
180	{102, 204,   0, 0},
181	{102, 204,  51, 0},
182	{102, 204, 102, 0},
183	{102, 204, 153, 0},
184	{102, 204, 204, 0},
185	{102, 204, 255, 0},
186	{102, 255,   0, 0},
187	{102, 255,  51, 0},
188	{102, 255, 102, 0},
189	{102, 255, 153, 0},
190	{102, 255, 204, 0},
191	{102, 255, 255, 0},
192	{153,   0,   0, 0},
193	{153,   0,  51, 0},
194	{153,   0, 102, 0},
195	{153,   0, 153, 0},
196	{153,   0, 204, 0},
197	{153,   0, 255, 0},
198	{153,  51,   0, 0},
199	{153,  51,  51, 0},
200	{153,  51, 102, 0},
201	{153,  51, 153, 0},
202	{153,  51, 204, 0},
203	{153,  51, 255, 0},
204	{153, 102,   0, 0},
205	{153, 102,  51, 0},
206	{153, 102, 102, 0},
207	{153, 102, 153, 0},
208	{153, 102, 204, 0},
209	{153, 102, 255, 0},
210	{153, 153,   0, 0},
211	{153, 153,  51, 0},
212	{153, 153, 102, 0},
213	{153, 153, 153, 0},
214	{153, 153, 204, 0},
215	{153, 153, 255, 0},
216	{153, 204,   0, 0},
217	{153, 204,  51, 0},
218	{153, 204, 102, 0},
219	{153, 204, 153, 0},
220	{153, 204, 204, 0},
221	{153, 204, 255, 0},
222	{153, 255,   0, 0},
223	{153, 255,  51, 0},
224	{153, 255, 102, 0},
225	{153, 255, 153, 0},
226	{153, 255, 204, 0},
227	{153, 255, 255, 0},
228	{204,   0,   0, 0},
229	{204,   0,  51, 0},
230	{204,   0, 102, 0},
231	{204,   0, 153, 0},
232	{204,   0, 204, 0},
233	{204,   0, 255, 0},
234	{204,  51,   0, 0},
235	{204,  51,  51, 0},
236	{204,  51, 102, 0},
237	{204,  51, 153, 0},
238	{204,  51, 204, 0},
239	{204,  51, 255, 0},
240	{204, 102,   0, 0},
241	{204, 102,  51, 0},
242	{204, 102, 102, 0},
243	{204, 102, 153, 0},
244	{204, 102, 204, 0},
245	{204, 102, 255, 0},
246	{204, 153,   0, 0},
247	{204, 153,  51, 0},
248	{204, 153, 102, 0},
249	{204, 153, 153, 0},
250	{204, 153, 204, 0},
251	{204, 153, 255, 0},
252	{204, 204,   0, 0},
253	{204, 204,  51, 0},
254	{204, 204, 102, 0},
255	{204, 204, 153, 0},
256	{204, 204, 204, 0},
257	{204, 204, 255, 0},
258	{204, 255,   0, 0},
259	{204, 255,  51, 0},
260	{204, 255, 102, 0},
261	{204, 255, 153, 0},
262	{204, 255, 204, 0},
263	{204, 255, 255, 0},
264	{255,   0,   0, 0},
265	{255,   0,  51, 0},
266	{255,   0, 102, 0},
267	{255,   0, 153, 0},
268	{255,   0, 204, 0},
269	{255,   0, 255, 0},
270	{255,  51,   0, 0},
271	{255,  51,  51, 0},
272	{255,  51, 102, 0},
273	{255,  51, 153, 0},
274	{255,  51, 204, 0},
275	{255,  51, 255, 0},
276	{255, 102,   0, 0},
277	{255, 102,  51, 0},
278	{255, 102, 102, 0},
279	{255, 102, 153, 0},
280	{255, 102, 204, 0},
281	{255, 102, 255, 0},
282	{255, 153,   0, 0},
283	{255, 153,  51, 0},
284	{255, 153, 102, 0},
285	{255, 153, 153, 0},
286	{255, 153, 204, 0},
287	{255, 153, 255, 0},
288	{255, 204,   0, 0},
289	{255, 204,  51, 0},
290	{255, 204, 102, 0},
291	{255, 204, 153, 0},
292	{255, 204, 204, 0},
293	{255, 204, 255, 0},
294	{255, 255,   0, 0},
295	{255, 255,  51, 0},
296	{255, 255, 102, 0},
297	{255, 255, 153, 0},
298	{255, 255, 204, 0},
299	{255, 255, 255, 0},
300
301	{ 10,  10,  10, 0},
302	{ 20,  20,  20, 0},
303	{ 30,  30,  30, 0},
304	{ 40,  40,  40, 0},
305	{ 50,  50,  50, 0},
306	{ 60,  60,  60, 0},
307	{ 70,  70,  70, 0},
308	{ 80,  80,  80, 0},
309	{ 90,  90,  90, 0},
310	{100, 100, 100, 0},
311	{110, 110, 110, 0},
312	{120, 120, 120, 0},
313	{130, 130, 130, 0},
314	{140, 140, 140, 0},
315	{150, 150, 150, 0},
316	{160, 160, 160, 0},
317	{170, 170, 170, 0},
318	{180, 180, 180, 0},
319	{190, 190, 190, 0},
320	{200, 200, 200, 0},
321	{210, 210, 210, 0},
322	{220, 220, 220, 0},
323	{230, 230, 230, 0},
324	{240, 240, 240, 0},
325};
326
327#define ROWS_DEFAULT 25
328#define COLUMNS_DEFAULT 80
329
330// selection granularity
331enum {
332	SELECT_CHARS,
333	SELECT_WORDS,
334	SELECT_LINES
335};
336
337#undef B_TRANSLATE_CONTEXT
338#define B_TRANSLATE_CONTEXT "Terminal TermView"
339
340static property_info sPropList[] = {
341	{ "encoding",
342	{B_GET_PROPERTY, 0},
343	{B_DIRECT_SPECIFIER, 0},
344	"get terminal encoding"},
345	{ "encoding",
346	{B_SET_PROPERTY, 0},
347	{B_DIRECT_SPECIFIER, 0},
348	"set terminal encoding"},
349	{ "tty",
350	{B_GET_PROPERTY, 0},
351	{B_DIRECT_SPECIFIER, 0},
352	"get tty name."},
353	{ 0  }
354};
355
356
357static const uint32 kUpdateSigWinch = 'Rwin';
358static const uint32 kBlinkCursor = 'BlCr';
359static const uint32 kAutoScroll = 'AScr';
360
361static const bigtime_t kSyncUpdateGranularity = 100000;	// 0.1 s
362
363static const int32 kCursorBlinkIntervals = 3;
364static const int32 kCursorVisibleIntervals = 2;
365static const bigtime_t kCursorBlinkInterval = 500000;
366
367static const rgb_color kBlackColor = { 0, 0, 0, 255 };
368static const rgb_color kWhiteColor = { 255, 255, 255, 255 };
369
370static const char* kDefaultSpecialWordChars = ":@-./_~";
371static const char* kEscapeCharacters = " ~`#$&*()\\|[]{};'\"<>?!";
372
373// secondary mouse button drop
374const int32 kSecondaryMouseDropAction = 'SMDA';
375
376enum {
377	kInsert,
378	kChangeDirectory,
379	kLinkFiles,
380	kMoveFiles,
381	kCopyFiles
382};
383
384
385template<typename Type>
386static inline Type
387restrict_value(const Type& value, const Type& min, const Type& max)
388{
389	return value < min ? min : (value > max ? max : value);
390}
391
392
393class TermView::CharClassifier : public TerminalCharClassifier {
394public:
395	CharClassifier(const char* specialWordChars)
396		:
397		fSpecialWordChars(specialWordChars)
398	{
399	}
400
401	virtual int Classify(const char* character)
402	{
403		// TODO: Deal correctly with non-ASCII chars.
404		char c = *character;
405		if (UTF8Char::ByteCount(c) > 1)
406			return CHAR_TYPE_WORD_CHAR;
407
408		if (isspace(c))
409			return CHAR_TYPE_SPACE;
410		if (isalnum(c) || strchr(fSpecialWordChars, c) != NULL)
411			return CHAR_TYPE_WORD_CHAR;
412
413		return CHAR_TYPE_WORD_DELIMITER;
414	}
415
416private:
417	const char*	fSpecialWordChars;
418};
419
420
421//	#pragma mark -
422
423
424TermView::TermView(BRect frame, int32 argc, const char** argv, int32 historySize)
425	: BView(frame, "termview", B_FOLLOW_ALL,
426		B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE),
427	fColumns(COLUMNS_DEFAULT),
428	fRows(ROWS_DEFAULT),
429	fEncoding(M_UTF8),
430	fActive(false),
431	fScrBufSize(historySize),
432	fReportX10MouseEvent(false),
433	fReportNormalMouseEvent(false),
434	fReportButtonMouseEvent(false),
435	fReportAnyMouseEvent(false)
436{
437	status_t status = _InitObject(argc, argv);
438	if (status != B_OK)
439		throw status;
440	SetTermSize(frame);
441}
442
443
444TermView::TermView(int rows, int columns, int32 argc, const char** argv,
445		int32 historySize)
446	: BView(BRect(0, 0, 0, 0), "termview", B_FOLLOW_ALL,
447		B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE),
448	fColumns(columns),
449	fRows(rows),
450	fEncoding(M_UTF8),
451	fActive(false),
452	fScrBufSize(historySize),
453	fReportX10MouseEvent(false),
454	fReportNormalMouseEvent(false),
455	fReportButtonMouseEvent(false),
456	fReportAnyMouseEvent(false)
457{
458	status_t status = _InitObject(argc, argv);
459	if (status != B_OK)
460		throw status;
461
462	ResizeToPreferred();
463
464	// TODO: Don't show the dragger, since replicant capabilities
465	// don't work very well ATM.
466	/*
467	BRect rect(0, 0, 16, 16);
468	rect.OffsetTo(Bounds().right - rect.Width(),
469		Bounds().bottom - rect.Height());
470
471	SetFlags(Flags() | B_DRAW_ON_CHILDREN | B_FOLLOW_ALL);
472	AddChild(new BDragger(rect, this,
473		B_FOLLOW_RIGHT|B_FOLLOW_BOTTOM, B_WILL_DRAW));*/
474}
475
476
477TermView::TermView(BMessage* archive)
478	:
479	BView(archive),
480	fColumns(COLUMNS_DEFAULT),
481	fRows(ROWS_DEFAULT),
482	fEncoding(M_UTF8),
483	fActive(false),
484	fScrBufSize(1000),
485	fReportX10MouseEvent(false),
486	fReportNormalMouseEvent(false),
487	fReportButtonMouseEvent(false),
488	fReportAnyMouseEvent(false)
489{
490	BRect frame = Bounds();
491
492	if (archive->FindInt32("encoding", (int32*)&fEncoding) < B_OK)
493		fEncoding = M_UTF8;
494	if (archive->FindInt32("columns", (int32*)&fColumns) < B_OK)
495		fColumns = COLUMNS_DEFAULT;
496	if (archive->FindInt32("rows", (int32*)&fRows) < B_OK)
497		fRows = ROWS_DEFAULT;
498
499	int32 argc = 0;
500	if (archive->HasInt32("argc"))
501		archive->FindInt32("argc", &argc);
502
503	const char **argv = new const char*[argc];
504	for (int32 i = 0; i < argc; i++) {
505		archive->FindString("argv", i, (const char**)&argv[i]);
506	}
507
508	// TODO: Retrieve colors, history size, etc. from archive
509	status_t status = _InitObject(argc, argv);
510	if (status != B_OK)
511		throw status;
512
513	bool useRect = false;
514	if ((archive->FindBool("use_rect", &useRect) == B_OK) && useRect)
515		SetTermSize(frame);
516
517	delete[] argv;
518}
519
520
521/*!	Initializes the object for further use.
522	The members fRows, fColumns, fEncoding, and fScrBufSize must
523	already be initialized; they are not touched by this method.
524*/
525status_t
526TermView::_InitObject(int32 argc, const char** argv)
527{
528	SetFlags(Flags() | B_WILL_DRAW | B_FRAME_EVENTS
529		| B_FULL_UPDATE_ON_RESIZE/* | B_INPUT_METHOD_AWARE*/);
530
531	fShell = NULL;
532	fWinchRunner = NULL;
533	fCursorBlinkRunner = NULL;
534	fAutoScrollRunner = NULL;
535	fResizeRunner = NULL;
536	fResizeView = NULL;
537	fCharClassifier = NULL;
538	fFontWidth = 0;
539	fFontHeight = 0;
540	fFontAscent = 0;
541	fFrameResized = false;
542	fResizeViewDisableCount = 0;
543	fLastActivityTime = 0;
544	fCursorState = 0;
545	fCursorHeight = 0;
546	fCursor = TermPos(0, 0);
547	fTextBuffer = NULL;
548	fVisibleTextBuffer = NULL;
549	fScrollBar = NULL;
550	fInline = NULL;
551	fCursorForeColor = kWhiteColor;
552	fCursorBackColor = kBlackColor;
553	fSelectForeColor = kWhiteColor;
554	fSelectBackColor = kBlackColor;
555	fScrollOffset = 0;
556	fLastSyncTime = 0;
557	fScrolledSinceLastSync = 0;
558	fSyncRunner = NULL;
559	fConsiderClockedSync = false;
560	fSelStart = TermPos(-1, -1);
561	fSelEnd = TermPos(-1, -1);
562	fMouseTracking = false;
563	fCheckMouseTracking = false;
564	fPrevPos = TermPos(-1, - 1);
565	fReportX10MouseEvent = false;
566	fReportNormalMouseEvent = false;
567	fReportButtonMouseEvent = false;
568	fReportAnyMouseEvent = false;
569	fMouseClipboard = be_clipboard;
570
571	fTextBuffer = new(std::nothrow) TerminalBuffer;
572	if (fTextBuffer == NULL)
573		return B_NO_MEMORY;
574
575	fVisibleTextBuffer = new(std::nothrow) BasicTerminalBuffer;
576	if (fVisibleTextBuffer == NULL)
577		return B_NO_MEMORY;
578
579	// TODO: Make the special word chars user-settable!
580	fCharClassifier = new(std::nothrow) CharClassifier(
581		kDefaultSpecialWordChars);
582	if (fCharClassifier == NULL)
583		return B_NO_MEMORY;
584
585	status_t error = fTextBuffer->Init(fColumns, fRows, fScrBufSize);
586	if (error != B_OK)
587		return error;
588	fTextBuffer->SetEncoding(fEncoding);
589
590	error = fVisibleTextBuffer->Init(fColumns, fRows + 2, 0);
591	if (error != B_OK)
592		return error;
593
594	fShell = new (std::nothrow) Shell();
595	if (fShell == NULL)
596		return B_NO_MEMORY;
597
598	SetTermFont(be_fixed_font);
599
600	error = fShell->Open(fRows, fColumns,
601		EncodingAsShortString(fEncoding), argc, argv);
602
603	if (error < B_OK)
604		return error;
605
606	error = _AttachShell(fShell);
607	if (error < B_OK)
608		return error;
609
610	SetLowColor(kTermColorTable[8]);
611	SetViewColor(B_TRANSPARENT_32_BIT);
612
613	return B_OK;
614}
615
616
617TermView::~TermView()
618{
619	Shell* shell = fShell;
620		// _DetachShell sets fShell to NULL
621
622	_DetachShell();
623
624	delete fSyncRunner;
625	delete fAutoScrollRunner;
626	delete fCharClassifier;
627	delete fVisibleTextBuffer;
628	delete fTextBuffer;
629	delete shell;
630}
631
632
633bool
634TermView::IsShellBusy() const
635{
636	return fShell != NULL && fShell->HasActiveProcesses();
637}
638
639
640/* static */
641BArchivable *
642TermView::Instantiate(BMessage* data)
643{
644	if (validate_instantiation(data, "TermView")) {
645		TermView *view = new (std::nothrow) TermView(data);
646		return view;
647	}
648
649	return NULL;
650}
651
652
653status_t
654TermView::Archive(BMessage* data, bool deep) const
655{
656	status_t status = BView::Archive(data, deep);
657	if (status == B_OK)
658		status = data->AddString("add_on", TERM_SIGNATURE);
659	if (status == B_OK)
660		status = data->AddInt32("encoding", (int32)fEncoding);
661	if (status == B_OK)
662		status = data->AddInt32("columns", (int32)fColumns);
663	if (status == B_OK)
664		status = data->AddInt32("rows", (int32)fRows);
665
666	if (data->ReplaceString("class", "TermView") != B_OK)
667		data->AddString("class", "TermView");
668
669	return status;
670}
671
672
673inline int32
674TermView::_LineAt(float y)
675{
676	int32 location = int32(y + fScrollOffset);
677
678	// Make sure negative offsets are rounded towards the lower neighbor, too.
679	if (location < 0)
680		location -= fFontHeight - 1;
681
682	return location / fFontHeight;
683}
684
685
686inline float
687TermView::_LineOffset(int32 index)
688{
689	return index * fFontHeight - fScrollOffset;
690}
691
692
693// convert view coordinates to terminal text buffer position
694inline TermPos
695TermView::_ConvertToTerminal(const BPoint &p)
696{
697	return TermPos(p.x >= 0 ? (int32)p.x / fFontWidth : -1, _LineAt(p.y));
698}
699
700
701// convert terminal text buffer position to view coordinates
702inline BPoint
703TermView::_ConvertFromTerminal(const TermPos &pos)
704{
705	return BPoint(fFontWidth * pos.x, _LineOffset(pos.y));
706}
707
708
709inline void
710TermView::_InvalidateTextRect(int32 x1, int32 y1, int32 x2, int32 y2)
711{
712	BRect rect(x1 * fFontWidth, _LineOffset(y1),
713	    (x2 + 1) * fFontWidth - 1, _LineOffset(y2 + 1) - 1);
714//debug_printf("Invalidate((%f, %f) - (%f, %f))\n", rect.left, rect.top,
715//rect.right, rect.bottom);
716	Invalidate(rect);
717}
718
719
720void
721TermView::GetPreferredSize(float *width, float *height)
722{
723	if (width)
724		*width = fColumns * fFontWidth - 1;
725	if (height)
726		*height = fRows * fFontHeight - 1;
727}
728
729
730const char *
731TermView::TerminalName() const
732{
733	if (fShell == NULL)
734		return NULL;
735
736	return fShell->TTYName();
737}
738
739
740//! Get width and height for terminal font
741void
742TermView::GetFontSize(int* _width, int* _height)
743{
744	*_width = fFontWidth;
745	*_height = fFontHeight;
746}
747
748
749int
750TermView::Rows() const
751{
752	return fRows;
753}
754
755
756int
757TermView::Columns() const
758{
759	return fColumns;
760}
761
762
763//! Set number of rows and columns in terminal
764BRect
765TermView::SetTermSize(int rows, int columns)
766{
767//debug_printf("TermView::SetTermSize(%d, %d)\n", rows, columns);
768	if (rows > 0)
769		fRows = rows;
770	if (columns > 0)
771		fColumns = columns;
772
773	// To keep things simple, get rid of the selection first.
774	_Deselect();
775
776	{
777		BAutolock _(fTextBuffer);
778		if (fTextBuffer->ResizeTo(columns, rows) != B_OK
779			|| fVisibleTextBuffer->ResizeTo(columns, rows + 2, 0)
780				!= B_OK) {
781			return Bounds();
782		}
783	}
784
785//debug_printf("Invalidate()\n");
786	Invalidate();
787
788	if (fScrollBar != NULL) {
789		_UpdateScrollBarRange();
790		fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows);
791	}
792
793	BRect rect(0, 0, fColumns * fFontWidth, fRows * fFontHeight);
794
795	// synchronize the visible text buffer
796	{
797		BAutolock _(fTextBuffer);
798
799		_SynchronizeWithTextBuffer(0, -1);
800		int32 offset = _LineAt(0);
801		fVisibleTextBuffer->SynchronizeWith(fTextBuffer, offset, offset,
802			offset + rows + 2);
803	}
804
805	return rect;
806}
807
808
809void
810TermView::SetTermSize(BRect rect)
811{
812	int rows;
813	int columns;
814
815	GetTermSizeFromRect(rect, &rows, &columns);
816	SetTermSize(rows, columns);
817}
818
819
820void
821TermView::GetTermSizeFromRect(const BRect &rect, int *_rows,
822	int *_columns)
823{
824	int columns = (rect.IntegerWidth() + 1) / fFontWidth;
825	int rows = (rect.IntegerHeight() + 1) / fFontHeight;
826
827	if (_rows)
828		*_rows = rows;
829	if (_columns)
830		*_columns = columns;
831}
832
833
834void
835TermView::SetTextColor(rgb_color fore, rgb_color back)
836{
837	kTermColorTable[0] = back;
838	kTermColorTable[7] = fore;
839
840	SetLowColor(back);
841}
842
843
844void
845TermView::SetSelectColor(rgb_color fore, rgb_color back)
846{
847	fSelectForeColor = fore;
848	fSelectBackColor = back;
849}
850
851
852void
853TermView::SetCursorColor(rgb_color fore, rgb_color back)
854{
855	fCursorForeColor = fore;
856	fCursorBackColor = back;
857}
858
859
860int
861TermView::Encoding() const
862{
863	return fEncoding;
864}
865
866
867void
868TermView::SetEncoding(int encoding)
869{
870	// TODO: Shell::_Spawn() sets the "TTYPE" environment variable using
871	// the string value of encoding. But when this function is called and
872	// the encoding changes, the new value is never passed to Shell.
873	fEncoding = encoding;
874
875	BAutolock _(fTextBuffer);
876	fTextBuffer->SetEncoding(fEncoding);
877}
878
879
880void
881TermView::SetMouseClipboard(BClipboard *clipboard)
882{
883	fMouseClipboard = clipboard;
884}
885
886
887void
888TermView::GetTermFont(BFont *font) const
889{
890	if (font != NULL)
891		*font = fHalfFont;
892}
893
894
895//! Sets font for terminal
896void
897TermView::SetTermFont(const BFont *font)
898{
899	int halfWidth = 0;
900
901	fHalfFont = font;
902
903	fHalfFont.SetSpacing(B_FIXED_SPACING);
904
905	// calculate half font's max width
906	// Not Bounding, check only A-Z(For case of fHalfFont is KanjiFont. )
907	for (int c = 0x20 ; c <= 0x7e; c++){
908		char buf[4];
909		sprintf(buf, "%c", c);
910		int tmpWidth = (int)fHalfFont.StringWidth(buf);
911		if (tmpWidth > halfWidth)
912			halfWidth = tmpWidth;
913	}
914
915	fFontWidth = halfWidth;
916
917	font_height hh;
918	fHalfFont.GetHeight(&hh);
919
920	int font_ascent = (int)hh.ascent;
921	int font_descent =(int)hh.descent;
922	int font_leading =(int)hh.leading;
923
924	if (font_leading == 0)
925		font_leading = 1;
926
927	fFontAscent = font_ascent;
928	fFontHeight = font_ascent + font_descent + font_leading + 1;
929
930	fCursorHeight = fFontHeight;
931
932	_ScrollTo(0, false);
933	if (fScrollBar != NULL)
934		fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows);
935}
936
937
938void
939TermView::SetScrollBar(BScrollBar *scrollBar)
940{
941	fScrollBar = scrollBar;
942	if (fScrollBar != NULL)
943		fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows);
944}
945
946
947void
948TermView::SetTitle(const char *title)
949{
950	// TODO: Do something different in case we're a replicant,
951	// or in case we are inside a BTabView ?
952	if (Window())
953		Window()->SetTitle(title);
954}
955
956
957void
958TermView::Copy(BClipboard *clipboard)
959{
960	BAutolock _(fTextBuffer);
961
962	if (!_HasSelection())
963		return;
964
965	BString copyStr;
966	fTextBuffer->GetStringFromRegion(copyStr, fSelStart, fSelEnd);
967
968	if (clipboard->Lock()) {
969		BMessage *clipMsg = NULL;
970		clipboard->Clear();
971
972		if ((clipMsg = clipboard->Data()) != NULL) {
973			clipMsg->AddData("text/plain", B_MIME_TYPE, copyStr.String(),
974				copyStr.Length());
975			clipboard->Commit();
976		}
977		clipboard->Unlock();
978	}
979}
980
981
982void
983TermView::Paste(BClipboard *clipboard)
984{
985	if (clipboard->Lock()) {
986		BMessage *clipMsg = clipboard->Data();
987		const char* text;
988		ssize_t numBytes;
989		if (clipMsg->FindData("text/plain", B_MIME_TYPE,
990				(const void**)&text, &numBytes) == B_OK ) {
991			_WritePTY(text, numBytes);
992		}
993
994		clipboard->Unlock();
995
996		_ScrollTo(0, true);
997	}
998}
999
1000
1001void
1002TermView::SelectAll()
1003{
1004	BAutolock _(fTextBuffer);
1005
1006	_Select(TermPos(0, -fTextBuffer->HistorySize()),
1007		TermPos(0, fTextBuffer->Height()), false, true);
1008}
1009
1010
1011void
1012TermView::Clear()
1013{
1014	_Deselect();
1015
1016	{
1017		BAutolock _(fTextBuffer);
1018		fTextBuffer->Clear(true);
1019	}
1020	fVisibleTextBuffer->Clear(true);
1021
1022//debug_printf("Invalidate()\n");
1023	Invalidate();
1024
1025	_ScrollTo(0, false);
1026	if (fScrollBar) {
1027		fScrollBar->SetRange(0, 0);
1028		fScrollBar->SetProportion(1);
1029	}
1030}
1031
1032
1033//! Draw region
1034void
1035TermView::_InvalidateTextRange(TermPos start, TermPos end)
1036{
1037	if (end < start)
1038		std::swap(start, end);
1039
1040	if (start.y == end.y) {
1041		_InvalidateTextRect(start.x, start.y, end.x, end.y);
1042	} else {
1043		_InvalidateTextRect(start.x, start.y, fColumns, start.y);
1044
1045		if (end.y - start.y > 0)
1046			_InvalidateTextRect(0, start.y + 1, fColumns, end.y - 1);
1047
1048		_InvalidateTextRect(0, end.y, end.x, end.y);
1049	}
1050}
1051
1052
1053status_t
1054TermView::_AttachShell(Shell *shell)
1055{
1056	if (shell == NULL)
1057		return B_BAD_VALUE;
1058
1059	fShell = shell;
1060
1061	return fShell->AttachBuffer(TextBuffer());
1062}
1063
1064
1065void
1066TermView::_DetachShell()
1067{
1068	fShell->DetachBuffer();
1069	fShell = NULL;
1070}
1071
1072
1073void
1074TermView::_Activate()
1075{
1076	fActive = true;
1077
1078	if (fCursorBlinkRunner == NULL) {
1079		BMessage blinkMessage(kBlinkCursor);
1080		fCursorBlinkRunner = new (std::nothrow) BMessageRunner(
1081			BMessenger(this), &blinkMessage, kCursorBlinkInterval);
1082	}
1083}
1084
1085
1086void
1087TermView::_Deactivate()
1088{
1089	// make sure the cursor becomes visible
1090	fCursorState = 0;
1091	_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
1092	delete fCursorBlinkRunner;
1093	fCursorBlinkRunner = NULL;
1094
1095	fActive = false;
1096}
1097
1098
1099//! Draw part of a line in the given view.
1100void
1101TermView::_DrawLinePart(int32 x1, int32 y1, uint32 attr, char *buf,
1102	int32 width, bool mouse, bool cursor, BView *inView)
1103{
1104	rgb_color rgb_fore, rgb_back;
1105
1106	inView->SetFont(&fHalfFont);
1107
1108	// Set pen point
1109	int x2 = x1 + fFontWidth * width;
1110	int y2 = y1 + fFontHeight;
1111
1112	// color attribute
1113	int forecolor = IS_FORECOLOR(attr);
1114	int backcolor = IS_BACKCOLOR(attr);
1115	rgb_fore = kTermColorTable[forecolor];
1116	rgb_back = kTermColorTable[backcolor];
1117
1118	// Selection check.
1119	if (cursor) {
1120		rgb_fore = fCursorForeColor;
1121		rgb_back = fCursorBackColor;
1122	} else if (mouse) {
1123		rgb_fore = fSelectForeColor;
1124		rgb_back = fSelectBackColor;
1125	} else {
1126		// Reverse attribute(If selected area, don't reverse color).
1127		if (IS_INVERSE(attr)) {
1128			rgb_color rgb_tmp = rgb_fore;
1129			rgb_fore = rgb_back;
1130			rgb_back = rgb_tmp;
1131		}
1132	}
1133
1134	// Fill color at Background color and set low color.
1135	inView->SetHighColor(rgb_back);
1136	inView->FillRect(BRect(x1, y1, x2 - 1, y2 - 1));
1137	inView->SetLowColor(rgb_back);
1138
1139	inView->SetHighColor(rgb_fore);
1140
1141	// Draw character.
1142	inView->MovePenTo(x1, y1 + fFontAscent);
1143	inView->DrawString((char *) buf);
1144
1145	// bold attribute.
1146	if (IS_BOLD(attr)) {
1147		inView->MovePenTo(x1 + 1, y1 + fFontAscent);
1148
1149		inView->SetDrawingMode(B_OP_OVER);
1150		inView->DrawString((char *)buf);
1151		inView->SetDrawingMode(B_OP_COPY);
1152	}
1153
1154	// underline attribute
1155	if (IS_UNDER(attr)) {
1156		inView->MovePenTo(x1, y1 + fFontAscent);
1157		inView->StrokeLine(BPoint(x1 , y1 + fFontAscent),
1158			BPoint(x2 , y1 + fFontAscent));
1159	}
1160}
1161
1162
1163/*!	Caller must have locked fTextBuffer.
1164*/
1165void
1166TermView::_DrawCursor()
1167{
1168	BRect rect(fFontWidth * fCursor.x, _LineOffset(fCursor.y), 0, 0);
1169	rect.right = rect.left + fFontWidth - 1;
1170	rect.bottom = rect.top + fCursorHeight - 1;
1171	int32 firstVisible = _LineAt(0);
1172
1173	UTF8Char character;
1174	uint32 attr;
1175
1176	bool cursorVisible = _IsCursorVisible();
1177
1178	bool selected = _CheckSelectedRegion(TermPos(fCursor.x, fCursor.y));
1179	if (fVisibleTextBuffer->GetChar(fCursor.y - firstVisible, fCursor.x,
1180			character, attr) == A_CHAR) {
1181		int32 width;
1182		if (IS_WIDTH(attr))
1183			width = 2;
1184		else
1185			width = 1;
1186
1187		char buffer[5];
1188		int32 bytes = UTF8Char::ByteCount(character.bytes[0]);
1189		memcpy(buffer, character.bytes, bytes);
1190		buffer[bytes] = '\0';
1191
1192		_DrawLinePart(fCursor.x * fFontWidth, (int32)rect.top, attr, buffer,
1193			width, selected, cursorVisible, this);
1194	} else {
1195		if (selected)
1196			SetHighColor(fSelectBackColor);
1197		else
1198			SetHighColor(cursorVisible ? fCursorBackColor : kTermColorTable[IS_BACKCOLOR(attr)]);
1199
1200		FillRect(rect);
1201	}
1202}
1203
1204
1205bool
1206TermView::_IsCursorVisible() const
1207{
1208	return fCursorState < kCursorVisibleIntervals;
1209}
1210
1211
1212void
1213TermView::_BlinkCursor()
1214{
1215	bool wasVisible = _IsCursorVisible();
1216
1217	if (!wasVisible && fInline && fInline->IsActive())
1218		return;
1219
1220	bigtime_t now = system_time();
1221	if (Window()->IsActive() && now - fLastActivityTime >= kCursorBlinkInterval)
1222		fCursorState = (fCursorState + 1) % kCursorBlinkIntervals;
1223	else
1224		fCursorState = 0;
1225
1226	if (wasVisible != _IsCursorVisible())
1227		_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
1228}
1229
1230
1231void
1232TermView::_ActivateCursor(bool invalidate)
1233{
1234	fLastActivityTime = system_time();
1235	if (invalidate && fCursorState != 0)
1236		_BlinkCursor();
1237	else
1238		fCursorState = 0;
1239}
1240
1241
1242//! Update scroll bar range and knob size.
1243void
1244TermView::_UpdateScrollBarRange()
1245{
1246	if (fScrollBar == NULL)
1247		return;
1248
1249	int32 historySize;
1250	{
1251		BAutolock _(fTextBuffer);
1252		historySize = fTextBuffer->HistorySize();
1253	}
1254
1255	float viewHeight = fRows * fFontHeight;
1256	float historyHeight = (float)historySize * fFontHeight;
1257
1258//debug_printf("TermView::_UpdateScrollBarRange(): history: %ld, range: %f - 0\n",
1259//historySize, -historyHeight);
1260
1261	fScrollBar->SetRange(-historyHeight, 0);
1262	if (historySize > 0)
1263		fScrollBar->SetProportion(viewHeight / (viewHeight + historyHeight));
1264}
1265
1266
1267//!	Handler for SIGWINCH
1268void
1269TermView::_UpdateSIGWINCH()
1270{
1271	if (fFrameResized) {
1272		fShell->UpdateWindowSize(fRows, fColumns);
1273		fFrameResized = false;
1274	}
1275}
1276
1277
1278void
1279TermView::AttachedToWindow()
1280{
1281	fMouseButtons = 0;
1282
1283	// update the terminal size because it may have changed while the TermView
1284	// was detached from the window. On such conditions FrameResized was not
1285	// called when the resize occured
1286	int rows;
1287	int columns;
1288	GetTermSizeFromRect(Bounds(), &rows, &columns);
1289	SetTermSize(rows, columns);
1290	MakeFocus(true);
1291	if (fScrollBar) {
1292		fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows);
1293		_UpdateScrollBarRange();
1294	}
1295
1296	BMessenger thisMessenger(this);
1297
1298	BMessage message(kUpdateSigWinch);
1299	fWinchRunner = new (std::nothrow) BMessageRunner(thisMessenger,
1300		&message, 500000);
1301
1302	{
1303		BAutolock _(fTextBuffer);
1304		fTextBuffer->SetListener(thisMessenger);
1305		_SynchronizeWithTextBuffer(0, -1);
1306	}
1307
1308	be_clipboard->StartWatching(thisMessenger);
1309}
1310
1311
1312void
1313TermView::DetachedFromWindow()
1314{
1315	be_clipboard->StopWatching(BMessenger(this));
1316
1317	delete fWinchRunner;
1318	fWinchRunner = NULL;
1319
1320	delete fCursorBlinkRunner;
1321	fCursorBlinkRunner = NULL;
1322
1323	delete fResizeRunner;
1324	fResizeRunner = NULL;
1325
1326	{
1327		BAutolock _(fTextBuffer);
1328		fTextBuffer->UnsetListener();
1329	}
1330}
1331
1332
1333void
1334TermView::Draw(BRect updateRect)
1335{
1336	int32 x1 = (int32)updateRect.left / fFontWidth;
1337	int32 x2 = std::min((int)updateRect.right / fFontWidth, fColumns - 1);
1338
1339	int32 firstVisible = _LineAt(0);
1340	int32 y1 = _LineAt(updateRect.top);
1341	int32 y2 = std::min(_LineAt(updateRect.bottom), (int32)fRows - 1);
1342
1343	// draw the affected line parts
1344	if (x1 <= x2) {
1345		uint32 attr = 0;
1346
1347		for (int32 j = y1; j <= y2; j++) {
1348			int32 k = x1;
1349			char buf[fColumns * 4 + 1];
1350
1351			if (fVisibleTextBuffer->IsFullWidthChar(j - firstVisible, k))
1352				k--;
1353
1354			if (k < 0)
1355				k = 0;
1356
1357			for (int32 i = k; i <= x2;) {
1358				int32 lastColumn = x2;
1359				bool insideSelection = _CheckSelectedRegion(j, i, lastColumn);
1360					// This will clip lastColumn to the selection start or end
1361					// to ensure the selection is not drawn at the same time as
1362					// something else
1363				int32 count = fVisibleTextBuffer->GetString(j - firstVisible, i,
1364					lastColumn, buf, attr);
1365
1366// debug_printf("  fVisibleTextBuffer->GetString(%ld, %ld, %ld) -> (%ld, \"%.*s\"), selected: %d\n",
1367// j - firstVisible, i, lastColumn, count, (int)count, buf, insideSelection);
1368
1369				if (count == 0) {
1370					// No chars to draw : we just fill the rectangle with the
1371					// back color of the last char at the left
1372					BRect rect(fFontWidth * i, _LineOffset(j),
1373						fFontWidth * (lastColumn + 1) - 1, 0);
1374					rect.bottom = rect.top + fFontHeight - 1;
1375
1376					if (insideSelection) {
1377						// This area is selected, fill it with the select color
1378						SetHighColor(fSelectBackColor);
1379						FillRect(rect);
1380					} else {
1381						// We are not in the selection, so we have to try to
1382						// guess the color for this line from the last char
1383						// that was drawn in it.
1384						int t = 1;
1385						while (count == 0 && i - t >= 0) {
1386							count = fVisibleTextBuffer->GetString(
1387								j - firstVisible,
1388								i - t, lastColumn, buf, attr);
1389							t++;
1390						}
1391
1392						// If the line is completely empty, we use the default
1393						// back color.
1394						// TODO: It would be better to look at the line above,
1395						// or ensure each line is always initialized with an
1396						// attribute telling wat color to set.
1397						SetHighColor(count ? kTermColorTable[IS_BACKCOLOR(attr)]
1398							: kTermColorTable[0]);
1399						FillRect(rect);
1400					}
1401
1402					// Go on to the next block
1403					i = lastColumn + 1;
1404					continue;
1405				}
1406
1407				if (IS_WIDTH(attr))
1408					count = 2;
1409
1410				_DrawLinePart(fFontWidth * i, (int32)_LineOffset(j),
1411					attr, buf, count, insideSelection, false, this);
1412				i += count;
1413			}
1414		}
1415
1416		if (y2 == fRows - 1) {
1417			// There may be some empty space below the last line
1418			BRect rect(updateRect.left, _LineOffset(fRows),
1419				updateRect.right, 0);
1420			rect.bottom = rect.top + fFontHeight - 1;
1421			FillRect(rect);
1422		}
1423
1424	}
1425
1426	if (fInline && fInline->IsActive())
1427		_DrawInlineMethodString();
1428
1429	if (fCursor >= TermPos(x1, y1) && fCursor <= TermPos(x2, y2))
1430		_DrawCursor();
1431}
1432
1433
1434void
1435TermView::_DoPrint(BRect updateRect)
1436{
1437#if 0
1438	uint32 attr;
1439	uchar buf[1024];
1440
1441	const int numLines = (int)((updateRect.Height()) / fFontHeight);
1442
1443	int y1 = (int)(updateRect.top) / fFontHeight;
1444	y1 = y1 -(fScrBufSize - numLines * 2);
1445	if (y1 < 0)
1446		y1 = 0;
1447
1448	const int y2 = y1 + numLines -1;
1449
1450	const int x1 = (int)(updateRect.left) / fFontWidth;
1451	const int x2 = (int)(updateRect.right) / fFontWidth;
1452
1453	for (int j = y1; j <= y2; j++) {
1454		// If(x1, y1) Buffer is in string full width character,
1455		// alignment start position.
1456
1457		int k = x1;
1458		if (fTextBuffer->IsFullWidthChar(j, k))
1459			k--;
1460
1461		if (k < 0)
1462			k = 0;
1463
1464		for (int i = k; i <= x2;) {
1465			int count = fTextBuffer->GetString(j, i, x2, buf, &attr);
1466			if (count < 0) {
1467				i += abs(count);
1468				continue;
1469			}
1470
1471			_DrawLinePart(fFontWidth * i, fFontHeight * j,
1472				attr, buf, count, false, false, this);
1473			i += count;
1474		}
1475	}
1476#endif	// 0
1477}
1478
1479
1480void
1481TermView::WindowActivated(bool active)
1482{
1483	BView::WindowActivated(active);
1484	if (active && IsFocus()) {
1485		if (!fActive)
1486			_Activate();
1487	} else {
1488		if (fActive)
1489			_Deactivate();
1490	}
1491}
1492
1493
1494void
1495TermView::MakeFocus(bool focusState)
1496{
1497	BView::MakeFocus(focusState);
1498
1499	if (focusState && Window() && Window()->IsActive()) {
1500		if (!fActive)
1501			_Activate();
1502	} else {
1503		if (fActive)
1504			_Deactivate();
1505	}
1506}
1507
1508
1509void
1510TermView::KeyDown(const char *bytes, int32 numBytes)
1511{
1512	int32 key, mod, rawChar;
1513	BMessage *currentMessage = Looper()->CurrentMessage();
1514	if (currentMessage == NULL)
1515		return;
1516
1517	currentMessage->FindInt32("modifiers", &mod);
1518	currentMessage->FindInt32("key", &key);
1519	currentMessage->FindInt32("raw_char", &rawChar);
1520
1521	_ActivateCursor(true);
1522
1523	// handle multi-byte chars
1524	if (numBytes > 1) {
1525		if (fEncoding != M_UTF8) {
1526			char destBuffer[16];
1527			int32 destLen;
1528			long state = 0;
1529			convert_from_utf8(fEncoding, bytes, &numBytes, destBuffer,
1530				&destLen, &state, '?');
1531			_ScrollTo(0, true);
1532			fShell->Write(destBuffer, destLen);
1533			return;
1534		}
1535
1536		_ScrollTo(0, true);
1537		fShell->Write(bytes, numBytes);
1538		return;
1539	}
1540
1541	// Terminal filters RET, ENTER, F1...F12, and ARROW key code.
1542	const char *toWrite = NULL;
1543
1544	switch (*bytes) {
1545		case B_RETURN:
1546			if (rawChar == B_RETURN)
1547				toWrite = "\r";
1548			break;
1549
1550		case B_DELETE:
1551			toWrite = DELETE_KEY_CODE;
1552			break;
1553
1554		case B_BACKSPACE:
1555			// Translate only the actual backspace key to the backspace
1556			// code. CTRL-H shall just be echoed.
1557			if (!((mod & B_CONTROL_KEY) && rawChar == 'h'))
1558				toWrite = BACKSPACE_KEY_CODE;
1559			break;
1560
1561		case B_LEFT_ARROW:
1562			if (rawChar == B_LEFT_ARROW) {
1563				if (mod & B_SHIFT_KEY) {
1564					BMessage message(MSG_PREVIOUS_TAB);
1565					message.AddPointer("termView", this);
1566					Window()->PostMessage(&message);
1567					return;
1568				} else if ((mod & B_CONTROL_KEY) || (mod & B_COMMAND_KEY)) {
1569					toWrite = CTRL_LEFT_ARROW_KEY_CODE;
1570				} else
1571					toWrite = LEFT_ARROW_KEY_CODE;
1572			}
1573			break;
1574
1575		case B_RIGHT_ARROW:
1576			if (rawChar == B_RIGHT_ARROW) {
1577				if (mod & B_SHIFT_KEY) {
1578					BMessage message(MSG_NEXT_TAB);
1579					message.AddPointer("termView", this);
1580					Window()->PostMessage(&message);
1581					return;
1582				} else if ((mod & B_CONTROL_KEY) || (mod & B_COMMAND_KEY)) {
1583					toWrite = CTRL_RIGHT_ARROW_KEY_CODE;
1584				} else
1585					toWrite = RIGHT_ARROW_KEY_CODE;
1586			}
1587			break;
1588
1589		case B_UP_ARROW:
1590			if (mod & B_SHIFT_KEY) {
1591				_ScrollTo(fScrollOffset - fFontHeight, true);
1592				return;
1593			}
1594			if (rawChar == B_UP_ARROW) {
1595				if (mod & B_CONTROL_KEY)
1596					toWrite = CTRL_UP_ARROW_KEY_CODE;
1597				else
1598					toWrite = UP_ARROW_KEY_CODE;
1599			}
1600			break;
1601
1602		case B_DOWN_ARROW:
1603			if (mod & B_SHIFT_KEY) {
1604				_ScrollTo(fScrollOffset + fFontHeight, true);
1605				return;
1606			}
1607
1608			if (rawChar == B_DOWN_ARROW) {
1609				if (mod & B_CONTROL_KEY)
1610					toWrite = CTRL_DOWN_ARROW_KEY_CODE;
1611				else
1612					toWrite = DOWN_ARROW_KEY_CODE;
1613			}
1614			break;
1615
1616		case B_INSERT:
1617			if (rawChar == B_INSERT)
1618				toWrite = INSERT_KEY_CODE;
1619			break;
1620
1621		case B_HOME:
1622			if (rawChar == B_HOME)
1623				toWrite = HOME_KEY_CODE;
1624			break;
1625
1626		case B_END:
1627			if (rawChar == B_END)
1628				toWrite = END_KEY_CODE;
1629			break;
1630
1631		case B_PAGE_UP:
1632			if (mod & B_SHIFT_KEY) {
1633				_ScrollTo(fScrollOffset - fFontHeight  * fRows, true);
1634				return;
1635			}
1636			if (rawChar == B_PAGE_UP)
1637				toWrite = PAGE_UP_KEY_CODE;
1638			break;
1639
1640		case B_PAGE_DOWN:
1641			if (mod & B_SHIFT_KEY) {
1642				_ScrollTo(fScrollOffset + fFontHeight * fRows, true);
1643				return;
1644			}
1645			if (rawChar == B_PAGE_DOWN)
1646				toWrite = PAGE_DOWN_KEY_CODE;
1647			break;
1648
1649		case B_FUNCTION_KEY:
1650			for (int32 i = 0; i < 12; i++) {
1651				if (key == function_keycode_table[i]) {
1652					toWrite = function_key_char_table[i];
1653					break;
1654				}
1655			}
1656			break;
1657	}
1658
1659	// If the above code proposed an alternative string to write, we get it's
1660	// length. Otherwise we write exactly the bytes passed to this method.
1661	size_t toWriteLen;
1662	if (toWrite != NULL) {
1663		toWriteLen = strlen(toWrite);
1664	} else {
1665		toWrite = bytes;
1666		toWriteLen = numBytes;
1667	}
1668
1669	_ScrollTo(0, true);
1670	fShell->Write(toWrite, toWriteLen);
1671}
1672
1673
1674void
1675TermView::FrameResized(float width, float height)
1676{
1677//debug_printf("TermView::FrameResized(%f, %f)\n", width, height);
1678	int32 columns = ((int32)width + 1) / fFontWidth;
1679	int32 rows = ((int32)height + 1) / fFontHeight;
1680
1681	if (columns == fColumns && rows == fRows)
1682		return;
1683
1684	bool hasResizeView = fResizeRunner != NULL;
1685	if (!hasResizeView) {
1686		// show the current size in a view
1687		fResizeView = new BStringView(BRect(100, 100, 300, 140),
1688			B_TRANSLATE("size"), "");
1689		fResizeView->SetAlignment(B_ALIGN_CENTER);
1690		fResizeView->SetFont(be_bold_font);
1691
1692		BMessage message(MSG_REMOVE_RESIZE_VIEW_IF_NEEDED);
1693		fResizeRunner = new(std::nothrow) BMessageRunner(BMessenger(this),
1694			&message, 25000LL);
1695	}
1696
1697	BString text;
1698	text << columns << " x " << rows;
1699	fResizeView->SetText(text.String());
1700	fResizeView->GetPreferredSize(&width, &height);
1701	fResizeView->ResizeTo(width * 1.5, height * 1.5);
1702	fResizeView->MoveTo((Bounds().Width() - fResizeView->Bounds().Width()) / 2,
1703		(Bounds().Height()- fResizeView->Bounds().Height()) / 2);
1704	if (!hasResizeView && fResizeViewDisableCount < 1)
1705		AddChild(fResizeView);
1706
1707	if (fResizeViewDisableCount > 0)
1708		fResizeViewDisableCount--;
1709
1710	SetTermSize(rows, columns);
1711
1712	fFrameResized = true;
1713}
1714
1715
1716void
1717TermView::MessageReceived(BMessage *msg)
1718{
1719	entry_ref ref;
1720	const char *ctrl_l = "\x0c";
1721
1722	// first check for any dropped message
1723	if (msg->WasDropped() && (msg->what == B_SIMPLE_DATA
1724			|| msg->what == B_MIME_DATA)) {
1725		char *text;
1726		int32 numBytes;
1727		//rgb_color *color;
1728
1729		int32 i = 0;
1730
1731		if (msg->FindRef("refs", i++, &ref) == B_OK) {
1732			// first check if secondary mouse button is pressed
1733			int32 buttons = 0;
1734			msg->FindInt32("buttons", &buttons);
1735
1736			if (buttons == B_SECONDARY_MOUSE_BUTTON) {
1737				// start popup menu
1738				_SecondaryMouseButtonDropped(msg);
1739				return;
1740			}
1741
1742			_DoFileDrop(ref);
1743
1744			while (msg->FindRef("refs", i++, &ref) == B_OK) {
1745				_WritePTY(" ", 1);
1746				_DoFileDrop(ref);
1747			}
1748			return;
1749#if 0
1750		} else if (msg->FindData("RGBColor", B_RGB_COLOR_TYPE,
1751				(const void **)&color, &numBytes) == B_OK
1752				 && numBytes == sizeof(color)) {
1753			// TODO: handle color drop
1754			// maybe only on replicants ?
1755			return;
1756#endif
1757		} else if (msg->FindData("text/plain", B_MIME_TYPE,
1758			 	(const void **)&text, &numBytes) == B_OK) {
1759			_WritePTY(text, numBytes);
1760			return;
1761		}
1762	}
1763
1764	switch (msg->what){
1765		case B_ABOUT_REQUESTED:
1766			// (replicant) about box requested
1767			AboutRequested();
1768			break;
1769
1770		case B_SIMPLE_DATA:
1771		case B_REFS_RECEIVED:
1772		{
1773			// handle refs if they weren't dropped
1774			int32 i = 0;
1775			if (msg->FindRef("refs", i++, &ref) == B_OK) {
1776				_DoFileDrop(ref);
1777
1778				while (msg->FindRef("refs", i++, &ref) == B_OK) {
1779					_WritePTY(" ", 1);
1780					_DoFileDrop(ref);
1781				}
1782			} else
1783				BView::MessageReceived(msg);
1784			break;
1785		}
1786
1787		case B_COPY:
1788			Copy(be_clipboard);
1789			break;
1790
1791		case B_PASTE:
1792		{
1793			int32 code;
1794			if (msg->FindInt32("index", &code) == B_OK)
1795				Paste(be_clipboard);
1796			break;
1797		}
1798
1799		case B_CLIPBOARD_CHANGED:
1800			// This message originates from the system clipboard. Overwrite
1801			// the contents of the mouse clipboard with the ones from the
1802			// system clipboard, in case it contains text data.
1803			if (be_clipboard->Lock()) {
1804				if (fMouseClipboard->Lock()) {
1805					BMessage* clipMsgA = be_clipboard->Data();
1806					const char* text;
1807					ssize_t numBytes;
1808					if (clipMsgA->FindData("text/plain", B_MIME_TYPE,
1809							(const void**)&text, &numBytes) == B_OK ) {
1810						fMouseClipboard->Clear();
1811						BMessage* clipMsgB = fMouseClipboard->Data();
1812						clipMsgB->AddData("text/plain", B_MIME_TYPE,
1813							text, numBytes);
1814						fMouseClipboard->Commit();
1815					}
1816					fMouseClipboard->Unlock();
1817				}
1818				be_clipboard->Unlock();
1819			}
1820			break;
1821
1822		case B_SELECT_ALL:
1823			SelectAll();
1824			break;
1825
1826		case B_SET_PROPERTY:
1827		{
1828			int32 i;
1829			int32 encodingID;
1830			BMessage specifier;
1831			msg->GetCurrentSpecifier(&i, &specifier);
1832			if (!strcmp("encoding", specifier.FindString("property", i))){
1833				msg->FindInt32 ("data", &encodingID);
1834				SetEncoding(encodingID);
1835				msg->SendReply(B_REPLY);
1836			} else {
1837				BView::MessageReceived(msg);
1838			}
1839			break;
1840		}
1841
1842		case B_GET_PROPERTY:
1843		{
1844			int32 i;
1845			BMessage specifier;
1846			msg->GetCurrentSpecifier(&i, &specifier);
1847			if (!strcmp("encoding", specifier.FindString("property", i))){
1848				BMessage reply(B_REPLY);
1849				reply.AddInt32("result", Encoding());
1850				msg->SendReply(&reply);
1851			} else if (!strcmp("tty", specifier.FindString("property", i))) {
1852				BMessage reply(B_REPLY);
1853				reply.AddString("result", TerminalName());
1854				msg->SendReply(&reply);
1855			} else {
1856				BView::MessageReceived(msg);
1857			}
1858			break;
1859		}
1860
1861		case B_INPUT_METHOD_EVENT:
1862		{
1863			int32 opcode;
1864			if (msg->FindInt32("be:opcode", &opcode) == B_OK) {
1865				switch (opcode) {
1866					case B_INPUT_METHOD_STARTED:
1867					{
1868						BMessenger messenger;
1869						if (msg->FindMessenger("be:reply_to",
1870								&messenger) == B_OK) {
1871							fInline = new (std::nothrow)
1872								InlineInput(messenger);
1873						}
1874						break;
1875					}
1876
1877					case B_INPUT_METHOD_STOPPED:
1878						delete fInline;
1879						fInline = NULL;
1880						break;
1881
1882					case B_INPUT_METHOD_CHANGED:
1883						if (fInline != NULL)
1884							_HandleInputMethodChanged(msg);
1885						break;
1886
1887					case B_INPUT_METHOD_LOCATION_REQUEST:
1888						if (fInline != NULL)
1889							_HandleInputMethodLocationRequest();
1890						break;
1891
1892					default:
1893						break;
1894				}
1895			}
1896			break;
1897		}
1898
1899		case MENU_CLEAR_ALL:
1900			Clear();
1901			fShell->Write(ctrl_l, 1);
1902			break;
1903		case kBlinkCursor:
1904			_BlinkCursor();
1905			break;
1906		case kUpdateSigWinch:
1907			_UpdateSIGWINCH();
1908			break;
1909		case kAutoScroll:
1910			_AutoScrollUpdate();
1911			break;
1912		case kSecondaryMouseDropAction:
1913			_DoSecondaryMouseDropAction(msg);
1914			break;
1915		case MSG_TERMINAL_BUFFER_CHANGED:
1916		{
1917			BAutolock _(fTextBuffer);
1918			_SynchronizeWithTextBuffer(0, -1);
1919			break;
1920		}
1921		case MSG_SET_TERMNAL_TITLE:
1922		{
1923			const char* title;
1924			if (msg->FindString("title", &title) == B_OK)
1925				SetTitle(title);
1926			break;
1927		}
1928		case MSG_REPORT_MOUSE_EVENT:
1929		{
1930			bool report;
1931			if (msg->FindBool("reportX10MouseEvent", &report) == B_OK)
1932				fReportX10MouseEvent = report;
1933
1934			if (msg->FindBool("reportNormalMouseEvent", &report) == B_OK)
1935				fReportNormalMouseEvent = report;
1936
1937			if (msg->FindBool("reportButtonMouseEvent", &report) == B_OK)
1938				fReportButtonMouseEvent = report;
1939
1940			if (msg->FindBool("reportAnyMouseEvent", &report) == B_OK)
1941				fReportAnyMouseEvent = report;
1942			break;
1943		}
1944		case MSG_REMOVE_RESIZE_VIEW_IF_NEEDED:
1945		{
1946			BPoint point;
1947			uint32 buttons;
1948			GetMouse(&point, &buttons, false);
1949			if (buttons != 0)
1950				break;
1951
1952			if (fResizeView != NULL) {
1953				fResizeView->RemoveSelf();
1954				delete fResizeView;
1955				fResizeView = NULL;
1956			}
1957			delete fResizeRunner;
1958			fResizeRunner = NULL;
1959			break;
1960		}
1961
1962		case MSG_QUIT_TERMNAL:
1963		{
1964			int32 reason;
1965			if (msg->FindInt32("reason", &reason) != B_OK)
1966				reason = 0;
1967			NotifyQuit(reason);
1968			break;
1969		}
1970		default:
1971			BView::MessageReceived(msg);
1972			break;
1973	}
1974}
1975
1976
1977status_t
1978TermView::GetSupportedSuites(BMessage *message)
1979{
1980	BPropertyInfo propInfo(sPropList);
1981	message->AddString("suites", "suite/vnd.naan-termview");
1982	message->AddFlat("messages", &propInfo);
1983	return BView::GetSupportedSuites(message);
1984}
1985
1986
1987void
1988TermView::ScrollTo(BPoint where)
1989{
1990//debug_printf("TermView::ScrollTo(): %f -> %f\n", fScrollOffset, where.y);
1991	float diff = where.y - fScrollOffset;
1992	if (diff == 0)
1993		return;
1994
1995	float bottom = Bounds().bottom;
1996	int32 oldFirstLine = _LineAt(0);
1997	int32 oldLastLine = _LineAt(bottom);
1998	int32 newFirstLine = _LineAt(diff);
1999	int32 newLastLine = _LineAt(bottom + diff);
2000
2001	fScrollOffset = where.y;
2002
2003	// invalidate the current cursor position before scrolling
2004	_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
2005
2006	// scroll contents
2007	BRect destRect(Frame().OffsetToCopy(Bounds().LeftTop()));
2008	BRect sourceRect(destRect.OffsetByCopy(0, diff));
2009//debug_printf("CopyBits(((%f, %f) - (%f, %f)) -> (%f, %f) - (%f, %f))\n",
2010//sourceRect.left, sourceRect.top, sourceRect.right, sourceRect.bottom,
2011//destRect.left, destRect.top, destRect.right, destRect.bottom);
2012	CopyBits(sourceRect, destRect);
2013
2014	// sync visible text buffer with text buffer
2015	if (newFirstLine != oldFirstLine || newLastLine != oldLastLine) {
2016		if (newFirstLine != oldFirstLine)
2017{
2018//debug_printf("fVisibleTextBuffer->ScrollBy(%ld)\n", newFirstLine - oldFirstLine);
2019			fVisibleTextBuffer->ScrollBy(newFirstLine - oldFirstLine);
2020}
2021		BAutolock _(fTextBuffer);
2022		if (diff < 0)
2023			_SynchronizeWithTextBuffer(newFirstLine, oldFirstLine - 1);
2024		else
2025			_SynchronizeWithTextBuffer(oldLastLine + 1, newLastLine);
2026	}
2027}
2028
2029
2030void
2031TermView::TargetedByScrollView(BScrollView *scrollView)
2032{
2033	BView::TargetedByScrollView(scrollView);
2034
2035	SetScrollBar(scrollView ? scrollView->ScrollBar(B_VERTICAL) : NULL);
2036}
2037
2038
2039BHandler*
2040TermView::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier,
2041	int32 what, const char* property)
2042{
2043	BHandler* target = this;
2044	BPropertyInfo propInfo(sPropList);
2045	if (propInfo.FindMatch(message, index, specifier, what, property) < B_OK) {
2046		target = BView::ResolveSpecifier(message, index, specifier, what,
2047			property);
2048	}
2049
2050	return target;
2051}
2052
2053
2054void
2055TermView::_SecondaryMouseButtonDropped(BMessage* msg)
2056{
2057	// Launch menu to choose what is to do with the msg data
2058	BPoint point;
2059	if (msg->FindPoint("_drop_point_", &point) != B_OK)
2060		return;
2061
2062	BMessage* insertMessage = new BMessage(*msg);
2063	insertMessage->what = kSecondaryMouseDropAction;
2064	insertMessage->AddInt8("action", kInsert);
2065
2066	BMessage* cdMessage = new BMessage(*msg);
2067	cdMessage->what = kSecondaryMouseDropAction;
2068	cdMessage->AddInt8("action", kChangeDirectory);
2069
2070	BMessage* lnMessage = new BMessage(*msg);
2071	lnMessage->what = kSecondaryMouseDropAction;
2072	lnMessage->AddInt8("action", kLinkFiles);
2073
2074	BMessage* mvMessage = new BMessage(*msg);
2075	mvMessage->what = kSecondaryMouseDropAction;
2076	mvMessage->AddInt8("action", kMoveFiles);
2077
2078	BMessage* cpMessage = new BMessage(*msg);
2079	cpMessage->what = kSecondaryMouseDropAction;
2080	cpMessage->AddInt8("action", kCopyFiles);
2081
2082	BMenuItem* insertItem = new BMenuItem(
2083		B_TRANSLATE("Insert path"), insertMessage);
2084	BMenuItem* cdItem = new BMenuItem(
2085		B_TRANSLATE("Change directory"), cdMessage);
2086	BMenuItem* lnItem = new BMenuItem(
2087		B_TRANSLATE("Create link here"), lnMessage);
2088	BMenuItem* mvItem = new BMenuItem(B_TRANSLATE("Move here"), mvMessage);
2089	BMenuItem* cpItem = new BMenuItem(B_TRANSLATE("Copy here"), cpMessage);
2090	BMenuItem* chItem = new BMenuItem(B_TRANSLATE("Cancel"), NULL);
2091
2092	// if the refs point to different directorys disable the cd menu item
2093	bool differentDirs = false;
2094	BDirectory firstDir;
2095	entry_ref ref;
2096	int i = 0;
2097	while (msg->FindRef("refs", i++, &ref) == B_OK) {
2098		BNode node(&ref);
2099		BEntry entry(&ref);
2100		BDirectory dir;
2101		if (node.IsDirectory())
2102			dir.SetTo(&ref);
2103		else
2104			entry.GetParent(&dir);
2105
2106		if (i == 1) {
2107			node_ref nodeRef;
2108			dir.GetNodeRef(&nodeRef);
2109			firstDir.SetTo(&nodeRef);
2110		} else if (firstDir != dir) {
2111			differentDirs = true;
2112			break;
2113		}
2114	}
2115	if (differentDirs)
2116		cdItem->SetEnabled(false);
2117
2118	BPopUpMenu *menu = new BPopUpMenu(
2119		B_TRANSLATE("Secondary mouse button drop menu"));
2120	menu->SetAsyncAutoDestruct(true);
2121	menu->AddItem(insertItem);
2122	menu->AddSeparatorItem();
2123	menu->AddItem(cdItem);
2124	menu->AddItem(lnItem);
2125	menu->AddItem(mvItem);
2126	menu->AddItem(cpItem);
2127	menu->AddSeparatorItem();
2128	menu->AddItem(chItem);
2129	menu->SetTargetForItems(this);
2130	menu->Go(point, true, true, true);
2131}
2132
2133
2134void
2135TermView::_DoSecondaryMouseDropAction(BMessage* msg)
2136{
2137	int8 action = -1;
2138	msg->FindInt8("action", &action);
2139
2140	BString outString = "";
2141	BString itemString = "";
2142
2143	switch (action) {
2144		case kInsert:
2145			break;
2146		case kChangeDirectory:
2147			outString = "cd ";
2148			break;
2149		case kLinkFiles:
2150			outString = "ln -s ";
2151			break;
2152		case kMoveFiles:
2153			outString = "mv ";
2154			break;
2155		case kCopyFiles:
2156			outString = "cp ";
2157			break;
2158
2159		default:
2160			return;
2161	}
2162
2163	bool listContainsDirectory = false;
2164	entry_ref ref;
2165	int32 i = 0;
2166	while (msg->FindRef("refs", i++, &ref) == B_OK) {
2167		BEntry ent(&ref);
2168		BNode node(&ref);
2169		BPath path(&ent);
2170		BString string(path.Path());
2171
2172		if (node.IsDirectory())
2173			listContainsDirectory = true;
2174
2175		if (i > 1)
2176			itemString += " ";
2177
2178		if (action == kChangeDirectory) {
2179			if (!node.IsDirectory()) {
2180				int32 slash = string.FindLast("/");
2181				string.Truncate(slash);
2182			}
2183			string.CharacterEscape(kEscapeCharacters, '\\');
2184			itemString += string;
2185			break;
2186		}
2187		string.CharacterEscape(kEscapeCharacters, '\\');
2188		itemString += string;
2189	}
2190
2191	if (listContainsDirectory && action == kCopyFiles)
2192		outString += "-R ";
2193
2194	outString += itemString;
2195
2196	if (action == kLinkFiles || action == kMoveFiles || action == kCopyFiles)
2197		outString += " .";
2198
2199	if (action != kInsert)
2200		outString += "\n";
2201
2202	_WritePTY(outString.String(), outString.Length());
2203}
2204
2205
2206//! Gets dropped file full path and display it at cursor position.
2207void
2208TermView::_DoFileDrop(entry_ref& ref)
2209{
2210	BEntry ent(&ref);
2211	BPath path(&ent);
2212	BString string(path.Path());
2213
2214	string.CharacterEscape(kEscapeCharacters, '\\');
2215	_WritePTY(string.String(), string.Length());
2216}
2217
2218
2219/*!	Text buffer must already be locked.
2220*/
2221void
2222TermView::_SynchronizeWithTextBuffer(int32 visibleDirtyTop,
2223	int32 visibleDirtyBottom)
2224{
2225	TerminalBufferDirtyInfo& info = fTextBuffer->DirtyInfo();
2226	int32 linesScrolled = info.linesScrolled;
2227
2228//debug_printf("TermView::_SynchronizeWithTextBuffer(): dirty: %ld - %ld, "
2229//"scrolled: %ld, visible dirty: %ld - %ld\n", info.dirtyTop, info.dirtyBottom,
2230//info.linesScrolled, visibleDirtyTop, visibleDirtyBottom);
2231
2232	bigtime_t now = system_time();
2233	bigtime_t timeElapsed = now - fLastSyncTime;
2234	if (timeElapsed > 2 * kSyncUpdateGranularity) {
2235		// last sync was ages ago
2236		fLastSyncTime = now;
2237		fScrolledSinceLastSync = linesScrolled;
2238	}
2239
2240	if (fSyncRunner == NULL) {
2241		// We consider clocked syncing when more than a full screen height has
2242		// been scrolled in less than a sync update period. Once we're
2243		// actively considering it, the same condition will convince us to
2244		// actually do it.
2245		if (fScrolledSinceLastSync + linesScrolled <= fRows) {
2246			// Condition doesn't hold yet. Reset if time is up, or otherwise
2247			// keep counting.
2248			if (timeElapsed > kSyncUpdateGranularity) {
2249				fConsiderClockedSync = false;
2250				fLastSyncTime = now;
2251				fScrolledSinceLastSync = linesScrolled;
2252			} else
2253				fScrolledSinceLastSync += linesScrolled;
2254		} else if (fConsiderClockedSync) {
2255			// We are convinced -- create the sync runner.
2256			fLastSyncTime = now;
2257			fScrolledSinceLastSync = 0;
2258
2259			BMessage message(MSG_TERMINAL_BUFFER_CHANGED);
2260			fSyncRunner = new(std::nothrow) BMessageRunner(BMessenger(this),
2261				&message, kSyncUpdateGranularity);
2262			if (fSyncRunner != NULL && fSyncRunner->InitCheck() == B_OK)
2263				return;
2264
2265			delete fSyncRunner;
2266			fSyncRunner = NULL;
2267		} else {
2268			// Looks interesting so far. Reset the counts and consider clocked
2269			// syncing.
2270			fConsiderClockedSync = true;
2271			fLastSyncTime = now;
2272			fScrolledSinceLastSync = 0;
2273		}
2274	} else if (timeElapsed < kSyncUpdateGranularity) {
2275		// sync time not passed yet -- keep counting
2276		fScrolledSinceLastSync += linesScrolled;
2277		return;
2278	} else if (fScrolledSinceLastSync + linesScrolled <= fRows) {
2279		// time's up, but not enough happened
2280		delete fSyncRunner;
2281		fSyncRunner = NULL;
2282		fLastSyncTime = now;
2283		fScrolledSinceLastSync = linesScrolled;
2284	} else {
2285		// Things are still rolling, but the sync time's up.
2286		fLastSyncTime = now;
2287		fScrolledSinceLastSync = 0;
2288	}
2289
2290	// Simple case first -- complete invalidation.
2291	if (info.invalidateAll) {
2292		Invalidate();
2293		_UpdateScrollBarRange();
2294		_Deselect();
2295
2296		fCursor = fTextBuffer->Cursor();
2297		_ActivateCursor(false);
2298
2299		int32 offset = _LineAt(0);
2300		fVisibleTextBuffer->SynchronizeWith(fTextBuffer, offset, offset,
2301			offset + fTextBuffer->Height() + 2);
2302
2303		info.Reset();
2304		return;
2305	}
2306
2307	BRect bounds = Bounds();
2308	int32 firstVisible = _LineAt(0);
2309	int32 lastVisible = _LineAt(bounds.bottom);
2310	int32 historySize = fTextBuffer->HistorySize();
2311
2312	bool doScroll = false;
2313	if (linesScrolled > 0) {
2314		_UpdateScrollBarRange();
2315
2316		visibleDirtyTop -= linesScrolled;
2317		visibleDirtyBottom -= linesScrolled;
2318
2319		if (firstVisible < 0) {
2320			firstVisible -= linesScrolled;
2321			lastVisible -= linesScrolled;
2322
2323			float scrollOffset;
2324			if (firstVisible < -historySize) {
2325				firstVisible = -historySize;
2326				doScroll = true;
2327				scrollOffset = -historySize * fFontHeight;
2328				// We need to invalidate the lower linesScrolled lines of the
2329				// visible text buffer, since those will be scrolled up and
2330				// need to be replaced. We just use visibleDirty{Top,Bottom}
2331				// for that purpose. Unless invoked from ScrollTo() (i.e.
2332				// user-initiated scrolling) those are unused. In the unlikely
2333				// case that the user is scrolling at the same time we may
2334				// invalidate too many lines, since we have to extend the given
2335				// region.
2336				// Note that in the firstVisible == 0 case the new lines are
2337				// already in the dirty region, so they will be updated anyway.
2338				if (visibleDirtyTop <= visibleDirtyBottom) {
2339					if (lastVisible < visibleDirtyTop)
2340						visibleDirtyTop = lastVisible;
2341					if (visibleDirtyBottom < lastVisible + linesScrolled)
2342						visibleDirtyBottom = lastVisible + linesScrolled;
2343				} else {
2344					visibleDirtyTop = lastVisible + 1;
2345					visibleDirtyBottom = lastVisible + linesScrolled;
2346				}
2347			} else
2348				scrollOffset = fScrollOffset - linesScrolled * fFontHeight;
2349
2350			_ScrollTo(scrollOffset, false);
2351		} else
2352			doScroll = true;
2353
2354		if (doScroll && lastVisible >= firstVisible
2355			&& !(info.IsDirtyRegionValid() && firstVisible >= info.dirtyTop
2356				&& lastVisible <= info.dirtyBottom)) {
2357			// scroll manually
2358			float scrollBy = linesScrolled * fFontHeight;
2359			BRect destRect(Frame().OffsetToCopy(B_ORIGIN));
2360			BRect sourceRect(destRect.OffsetByCopy(0, scrollBy));
2361
2362			// invalidate the current cursor position before scrolling
2363			_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
2364
2365//debug_printf("CopyBits(((%f, %f) - (%f, %f)) -> (%f, %f) - (%f, %f))\n",
2366//sourceRect.left, sourceRect.top, sourceRect.right, sourceRect.bottom,
2367//destRect.left, destRect.top, destRect.right, destRect.bottom);
2368			CopyBits(sourceRect, destRect);
2369
2370			fVisibleTextBuffer->ScrollBy(linesScrolled);
2371		}
2372
2373		// move selection
2374		if (fSelStart != fSelEnd) {
2375			fSelStart.y -= linesScrolled;
2376			fSelEnd.y -= linesScrolled;
2377			fInitialSelectionStart.y -= linesScrolled;
2378			fInitialSelectionEnd.y -= linesScrolled;
2379
2380			if (fSelStart.y < -historySize)
2381				_Deselect();
2382		}
2383	}
2384
2385	// invalidate dirty region
2386	if (info.IsDirtyRegionValid()) {
2387		_InvalidateTextRect(0, info.dirtyTop, fTextBuffer->Width() - 1,
2388			info.dirtyBottom);
2389
2390		// clear the selection, if affected
2391		if (fSelStart != fSelEnd) {
2392			// TODO: We're clearing the selection more often than necessary --
2393			// to avoid that, we'd also need to track the x coordinates of the
2394			// dirty range.
2395			int32 selectionBottom = fSelEnd.x > 0 ? fSelEnd.y : fSelEnd.y - 1;
2396			if (fSelStart.y <= info.dirtyBottom
2397				&& info.dirtyTop <= selectionBottom) {
2398				_Deselect();
2399			}
2400		}
2401	}
2402
2403	if (visibleDirtyTop <= visibleDirtyBottom)
2404		info.ExtendDirtyRegion(visibleDirtyTop, visibleDirtyBottom);
2405
2406	if (linesScrolled != 0 || info.IsDirtyRegionValid()) {
2407		fVisibleTextBuffer->SynchronizeWith(fTextBuffer, firstVisible,
2408			info.dirtyTop, info.dirtyBottom);
2409	}
2410
2411	// invalidate cursor, if it changed
2412	TermPos cursor = fTextBuffer->Cursor();
2413	if (fCursor != cursor || linesScrolled != 0) {
2414		// Before we scrolled we did already invalidate the old cursor.
2415		if (!doScroll)
2416			_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
2417		fCursor = cursor;
2418		_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
2419		_ActivateCursor(false);
2420	}
2421
2422	info.Reset();
2423}
2424
2425
2426/*!	Write strings to PTY device. If encoding system isn't UTF8, change
2427	encoding to UTF8 before writing PTY.
2428*/
2429void
2430TermView::_WritePTY(const char* text, int32 numBytes)
2431{
2432	if (fEncoding != M_UTF8) {
2433		while (numBytes > 0) {
2434			char buffer[1024];
2435			int32 bufferSize = sizeof(buffer);
2436			int32 sourceSize = numBytes;
2437			int32 state = 0;
2438			if (convert_to_utf8(fEncoding, text, &sourceSize, buffer,
2439					&bufferSize, &state) != B_OK || bufferSize == 0) {
2440				break;
2441			}
2442
2443			fShell->Write(buffer, bufferSize);
2444			text += sourceSize;
2445			numBytes -= sourceSize;
2446		}
2447	} else {
2448		fShell->Write(text, numBytes);
2449	}
2450}
2451
2452
2453//! Returns the square of the actual pixel distance between both points
2454float
2455TermView::_MouseDistanceSinceLastClick(BPoint where)
2456{
2457	return (fLastClickPoint.x - where.x) * (fLastClickPoint.x - where.x)
2458		+ (fLastClickPoint.y - where.y) * (fLastClickPoint.y - where.y);
2459}
2460
2461
2462void
2463TermView::_SendMouseEvent(int32 buttons, int32 mode, int32 x, int32 y,
2464	bool motion)
2465{
2466	char xtermButtons;
2467	if (buttons == B_PRIMARY_MOUSE_BUTTON)
2468		xtermButtons = 32 + 0;
2469 	else if (buttons == B_SECONDARY_MOUSE_BUTTON)
2470		xtermButtons = 32 + 1;
2471	else if (buttons == B_TERTIARY_MOUSE_BUTTON)
2472		xtermButtons = 32 + 2;
2473	else
2474		xtermButtons = 32 + 3;
2475
2476	if (motion)
2477		xtermButtons += 32;
2478
2479	char xtermX = x + 1 + 32;
2480	char xtermY = y + 1 + 32;
2481
2482	char destBuffer[6];
2483	destBuffer[0] = '\033';
2484	destBuffer[1] = '[';
2485	destBuffer[2] = 'M';
2486	destBuffer[3] = xtermButtons;
2487	destBuffer[4] = xtermX;
2488	destBuffer[5] = xtermY;
2489	fShell->Write(destBuffer, 6);
2490}
2491
2492
2493void
2494TermView::MouseDown(BPoint where)
2495{
2496	if (!IsFocus())
2497		MakeFocus();
2498
2499	int32 buttons;
2500	int32 modifier;
2501	Window()->CurrentMessage()->FindInt32("buttons", &buttons);
2502	Window()->CurrentMessage()->FindInt32("modifiers", &modifier);
2503
2504	fMouseButtons = buttons;
2505
2506	if (fReportAnyMouseEvent || fReportButtonMouseEvent
2507		|| fReportNormalMouseEvent || fReportX10MouseEvent) {
2508  		TermPos clickPos = _ConvertToTerminal(where);
2509  		_SendMouseEvent(buttons, modifier, clickPos.x, clickPos.y, false);
2510		return;
2511	}
2512
2513	// paste button
2514	if ((buttons & (B_SECONDARY_MOUSE_BUTTON | B_TERTIARY_MOUSE_BUTTON)) != 0) {
2515		Paste(fMouseClipboard);
2516		fLastClickPoint = where;
2517		return;
2518	}
2519
2520	// Select Region
2521	if (buttons == B_PRIMARY_MOUSE_BUTTON) {
2522		int32 clicks;
2523		Window()->CurrentMessage()->FindInt32("clicks", &clicks);
2524
2525		if (_HasSelection()) {
2526			TermPos inPos = _ConvertToTerminal(where);
2527			if (_CheckSelectedRegion(inPos)) {
2528				if (modifier & B_CONTROL_KEY) {
2529					BPoint p;
2530					uint32 bt;
2531					do {
2532						GetMouse(&p, &bt);
2533
2534						if (bt == 0) {
2535							_Deselect();
2536							return;
2537						}
2538
2539						snooze(40000);
2540
2541					} while (abs((int)(where.x - p.x)) < 4
2542						&& abs((int)(where.y - p.y)) < 4);
2543
2544					InitiateDrag();
2545					return;
2546				}
2547			}
2548		}
2549
2550		// If mouse has moved too much, disable double/triple click.
2551		if (_MouseDistanceSinceLastClick(where) > 8)
2552			clicks = 1;
2553
2554		SetMouseEventMask(B_POINTER_EVENTS | B_KEYBOARD_EVENTS,
2555			B_NO_POINTER_HISTORY | B_LOCK_WINDOW_FOCUS);
2556
2557		TermPos clickPos = _ConvertToTerminal(where);
2558
2559		if (modifier & B_SHIFT_KEY) {
2560			fInitialSelectionStart = clickPos;
2561			fInitialSelectionEnd = clickPos;
2562			_ExtendSelection(fInitialSelectionStart, true, false);
2563		} else {
2564			_Deselect();
2565			fInitialSelectionStart = clickPos;
2566			fInitialSelectionEnd = clickPos;
2567		}
2568
2569		// If clicks larger than 3, reset mouse click counter.
2570		clicks = (clicks - 1) % 3 + 1;
2571
2572		switch (clicks) {
2573			case 1:
2574				fCheckMouseTracking = true;
2575				fSelectGranularity = SELECT_CHARS;
2576				break;
2577
2578			case 2:
2579				_SelectWord(where, (modifier & B_SHIFT_KEY) != 0, false);
2580				fMouseTracking = true;
2581				fSelectGranularity = SELECT_WORDS;
2582				break;
2583
2584			case 3:
2585				_SelectLine(where, (modifier & B_SHIFT_KEY) != 0, false);
2586				fMouseTracking = true;
2587				fSelectGranularity = SELECT_LINES;
2588				break;
2589		}
2590	}
2591	fLastClickPoint = where;
2592}
2593
2594
2595void
2596TermView::MouseMoved(BPoint where, uint32 transit, const BMessage *message)
2597{
2598	if (fReportAnyMouseEvent || fReportButtonMouseEvent) {
2599		int32 modifier;
2600		Window()->CurrentMessage()->FindInt32("modifiers", &modifier);
2601
2602  		TermPos clickPos = _ConvertToTerminal(where);
2603
2604  		if (fReportButtonMouseEvent) {
2605  			if (fPrevPos.x != clickPos.x || fPrevPos.y != clickPos.y) {
2606		  		_SendMouseEvent(fMouseButtons, modifier, clickPos.x, clickPos.y,
2607					true);
2608  			}
2609  			fPrevPos = clickPos;
2610  			return;
2611  		}
2612  		_SendMouseEvent(fMouseButtons, modifier, clickPos.x, clickPos.y, true);
2613		return;
2614	}
2615
2616	if (fCheckMouseTracking) {
2617		if (_MouseDistanceSinceLastClick(where) > 9)
2618			fMouseTracking = true;
2619	}
2620	if (!fMouseTracking)
2621		return;
2622
2623	bool doAutoScroll = false;
2624
2625	if (where.y < 0) {
2626		doAutoScroll = true;
2627		fAutoScrollSpeed = where.y;
2628		where.x = 0;
2629		where.y = 0;
2630	}
2631
2632	BRect bounds(Bounds());
2633	if (where.y > bounds.bottom) {
2634		doAutoScroll = true;
2635		fAutoScrollSpeed = where.y - bounds.bottom;
2636		where.x = bounds.right;
2637		where.y = bounds.bottom;
2638	}
2639
2640	if (doAutoScroll) {
2641		if (fAutoScrollRunner == NULL) {
2642			BMessage message(kAutoScroll);
2643			fAutoScrollRunner = new (std::nothrow) BMessageRunner(
2644				BMessenger(this), &message, 10000);
2645		}
2646	} else {
2647		delete fAutoScrollRunner;
2648		fAutoScrollRunner = NULL;
2649	}
2650
2651	switch (fSelectGranularity) {
2652		case SELECT_CHARS:
2653		{
2654			// If we just start selecting, we first select the initially
2655			// hit char, so that we get a proper initial selection -- the char
2656			// in question, which will thus always be selected, regardless of
2657			// whether selecting forward or backward.
2658			if (fInitialSelectionStart == fInitialSelectionEnd) {
2659				_Select(fInitialSelectionStart, fInitialSelectionEnd, true,
2660					true);
2661			}
2662
2663			_ExtendSelection(_ConvertToTerminal(where), true, true);
2664			break;
2665		}
2666		case SELECT_WORDS:
2667			_SelectWord(where, true, true);
2668			break;
2669		case SELECT_LINES:
2670			_SelectLine(where, true, true);
2671			break;
2672	}
2673}
2674
2675
2676void
2677TermView::MouseUp(BPoint where)
2678{
2679	fCheckMouseTracking = false;
2680	fMouseTracking = false;
2681
2682	if (fAutoScrollRunner != NULL) {
2683		delete fAutoScrollRunner;
2684		fAutoScrollRunner = NULL;
2685	}
2686
2687	// When releasing the first mouse button, we copy the selected text to the
2688	// clipboard.
2689	int32 buttons;
2690	Window()->CurrentMessage()->FindInt32("buttons", &buttons);
2691
2692	if (fReportAnyMouseEvent || fReportButtonMouseEvent
2693		|| fReportNormalMouseEvent) {
2694	  	TermPos clickPos = _ConvertToTerminal(where);
2695	  	_SendMouseEvent(0, 0, clickPos.x, clickPos.y, false);
2696	} else {
2697		if ((buttons & B_PRIMARY_MOUSE_BUTTON) == 0
2698			&& (fMouseButtons & B_PRIMARY_MOUSE_BUTTON) != 0) {
2699			Copy(fMouseClipboard);
2700		}
2701
2702	}
2703	fMouseButtons = buttons;
2704}
2705
2706
2707//! Select a range of text.
2708void
2709TermView::_Select(TermPos start, TermPos end, bool inclusive,
2710	bool setInitialSelection)
2711{
2712	BAutolock _(fTextBuffer);
2713
2714	_SynchronizeWithTextBuffer(0, -1);
2715
2716	if (end < start)
2717		std::swap(start, end);
2718
2719	if (inclusive)
2720		end.x++;
2721
2722//debug_printf("TermView::_Select(): (%ld, %ld) - (%ld, %ld)\n", start.x,
2723//start.y, end.x, end.y);
2724
2725	if (start.x < 0)
2726		start.x = 0;
2727	if (end.x >= fColumns)
2728		end.x = fColumns;
2729
2730	TermPos minPos(0, -fTextBuffer->HistorySize());
2731	TermPos maxPos(0, fTextBuffer->Height());
2732	start = restrict_value(start, minPos, maxPos);
2733	end = restrict_value(end, minPos, maxPos);
2734
2735	// if the end is past the end of the line, select the line break, too
2736	if (fTextBuffer->LineLength(end.y) < end.x
2737			&& end.y < fTextBuffer->Height()) {
2738		end.y++;
2739		end.x = 0;
2740	}
2741
2742	if (fTextBuffer->IsFullWidthChar(start.y, start.x)) {
2743		start.x--;
2744		if (start.x < 0)
2745			start.x = 0;
2746	}
2747
2748	if (fTextBuffer->IsFullWidthChar(end.y, end.x)) {
2749		end.x++;
2750		if (end.x >= fColumns)
2751			end.x = fColumns;
2752	}
2753
2754	if (fSelStart != fSelEnd)
2755		_InvalidateTextRange(fSelStart, fSelEnd);
2756
2757	fSelStart = start;
2758	fSelEnd = end;
2759
2760	if (setInitialSelection) {
2761		fInitialSelectionStart = fSelStart;
2762		fInitialSelectionEnd = fSelEnd;
2763	}
2764
2765	_InvalidateTextRange(fSelStart, fSelEnd);
2766}
2767
2768
2769//! Extend selection (shift + mouse click).
2770void
2771TermView::_ExtendSelection(TermPos pos, bool inclusive,
2772	bool useInitialSelection)
2773{
2774	if (!useInitialSelection && !_HasSelection())
2775		return;
2776
2777	TermPos start = fSelStart;
2778	TermPos end = fSelEnd;
2779
2780	if (useInitialSelection) {
2781		start = fInitialSelectionStart;
2782		end = fInitialSelectionEnd;
2783	}
2784
2785	if (inclusive) {
2786		if (pos >= start && pos >= end)
2787			pos.x++;
2788	}
2789
2790	if (pos < start)
2791		_Select(pos, end, false, !useInitialSelection);
2792	else if (pos > end)
2793		_Select(start, pos, false, !useInitialSelection);
2794	else if (useInitialSelection)
2795		_Select(start, end, false, false);
2796}
2797
2798
2799// clear the selection.
2800void
2801TermView::_Deselect()
2802{
2803//debug_printf("TermView::_Deselect(): has selection: %d\n", _HasSelection());
2804	if (!_HasSelection())
2805		return;
2806
2807	_InvalidateTextRange(fSelStart, fSelEnd);
2808
2809	fSelStart.SetTo(0, 0);
2810	fSelEnd.SetTo(0, 0);
2811	fInitialSelectionStart.SetTo(0, 0);
2812	fInitialSelectionEnd.SetTo(0, 0);
2813}
2814
2815
2816bool
2817TermView::_HasSelection() const
2818{
2819	return fSelStart != fSelEnd;
2820}
2821
2822
2823void
2824TermView::_SelectWord(BPoint where, bool extend, bool useInitialSelection)
2825{
2826	BAutolock _(fTextBuffer);
2827
2828	TermPos pos = _ConvertToTerminal(where);
2829	TermPos start, end;
2830	if (!fTextBuffer->FindWord(pos, fCharClassifier, true, start, end))
2831		return;
2832
2833	if (extend) {
2834		if (start < (useInitialSelection ? fInitialSelectionStart : fSelStart))
2835			_ExtendSelection(start, false, useInitialSelection);
2836		else if (end > (useInitialSelection ? fInitialSelectionEnd : fSelEnd))
2837			_ExtendSelection(end, false, useInitialSelection);
2838		else if (useInitialSelection)
2839			_Select(start, end, false, false);
2840	} else
2841		_Select(start, end, false, !useInitialSelection);
2842}
2843
2844
2845void
2846TermView::_SelectLine(BPoint where, bool extend, bool useInitialSelection)
2847{
2848	TermPos start = TermPos(0, _ConvertToTerminal(where).y);
2849	TermPos end = TermPos(0, start.y + 1);
2850
2851	if (extend) {
2852		if (start < (useInitialSelection ? fInitialSelectionStart : fSelStart))
2853			_ExtendSelection(start, false, useInitialSelection);
2854		else if (end > (useInitialSelection ? fInitialSelectionEnd : fSelEnd))
2855			_ExtendSelection(end, false, useInitialSelection);
2856		else if (useInitialSelection)
2857			_Select(start, end, false, false);
2858	} else
2859		_Select(start, end, false, !useInitialSelection);
2860}
2861
2862
2863void
2864TermView::_AutoScrollUpdate()
2865{
2866	if (fMouseTracking && fAutoScrollRunner != NULL && fScrollBar != NULL) {
2867		float value = fScrollBar->Value();
2868		_ScrollTo(value + fAutoScrollSpeed, true);
2869		if (fAutoScrollSpeed < 0) {
2870			_ExtendSelection(_ConvertToTerminal(BPoint(0, 0)), true, true);
2871		} else {
2872			_ExtendSelection(_ConvertToTerminal(Bounds().RightBottom()), true,
2873				true);
2874		}
2875	}
2876}
2877
2878
2879bool
2880TermView::_CheckSelectedRegion(const TermPos &pos) const
2881{
2882	return pos >= fSelStart && pos < fSelEnd;
2883}
2884
2885
2886bool
2887TermView::_CheckSelectedRegion(int32 row, int32 firstColumn,
2888	int32& lastColumn) const
2889{
2890	if (fSelStart == fSelEnd)
2891		return false;
2892
2893	if (row == fSelStart.y && firstColumn < fSelStart.x
2894			&& lastColumn >= fSelStart.x) {
2895		// region starts before the selection, but intersects with it
2896		lastColumn = fSelStart.x - 1;
2897		return false;
2898	}
2899
2900	if (row == fSelEnd.y && firstColumn < fSelEnd.x
2901			&& lastColumn >= fSelEnd.x) {
2902		// region starts in the selection, but exceeds the end
2903		lastColumn = fSelEnd.x - 1;
2904		return true;
2905	}
2906
2907	TermPos pos(firstColumn, row);
2908	return pos >= fSelStart && pos < fSelEnd;
2909}
2910
2911
2912void
2913TermView::GetFrameSize(float *width, float *height)
2914{
2915	int32 historySize;
2916	{
2917		BAutolock _(fTextBuffer);
2918		historySize = fTextBuffer->HistorySize();
2919	}
2920
2921	if (width != NULL)
2922		*width = fColumns * fFontWidth;
2923
2924	if (height != NULL)
2925		*height = (fRows + historySize) * fFontHeight;
2926}
2927
2928
2929// Find a string, and select it if found
2930bool
2931TermView::Find(const BString &str, bool forwardSearch, bool matchCase,
2932	bool matchWord)
2933{
2934	BAutolock _(fTextBuffer);
2935	_SynchronizeWithTextBuffer(0, -1);
2936
2937	TermPos start;
2938	if (_HasSelection()) {
2939		if (forwardSearch)
2940			start = fSelEnd;
2941		else
2942			start = fSelStart;
2943	} else {
2944		// search from the very beginning/end
2945		if (forwardSearch)
2946			start = TermPos(0, -fTextBuffer->HistorySize());
2947		else
2948			start = TermPos(0, fTextBuffer->Height());
2949	}
2950
2951	TermPos matchStart, matchEnd;
2952	if (!fTextBuffer->Find(str.String(), start, forwardSearch, matchCase,
2953			matchWord, matchStart, matchEnd)) {
2954		return false;
2955	}
2956
2957	_Select(matchStart, matchEnd, false, true);
2958	_ScrollToRange(fSelStart, fSelEnd);
2959
2960	return true;
2961}
2962
2963
2964//! Get the selected text and copy to str
2965void
2966TermView::GetSelection(BString &str)
2967{
2968	str.SetTo("");
2969	BAutolock _(fTextBuffer);
2970	fTextBuffer->GetStringFromRegion(str, fSelStart, fSelEnd);
2971}
2972
2973
2974void
2975TermView::NotifyQuit(int32 reason)
2976{
2977	// implemented in subclasses
2978}
2979
2980
2981void
2982TermView::CheckShellGone()
2983{
2984	if (!fShell)
2985		return;
2986
2987	// check, if the shell does still live
2988	pid_t pid = fShell->ProcessID();
2989	team_info info;
2990	if (get_team_info(pid, &info) == B_BAD_TEAM_ID) {
2991		// the shell is gone
2992		NotifyQuit(0);
2993	}
2994}
2995
2996
2997void
2998TermView::InitiateDrag()
2999{
3000	BAutolock _(fTextBuffer);
3001
3002	BString copyStr("");
3003	fTextBuffer->GetStringFromRegion(copyStr, fSelStart, fSelEnd);
3004
3005	BMessage message(B_MIME_DATA);
3006	message.AddData("text/plain", B_MIME_TYPE, copyStr.String(),
3007		copyStr.Length());
3008
3009	BPoint start = _ConvertFromTerminal(fSelStart);
3010	BPoint end = _ConvertFromTerminal(fSelEnd);
3011
3012	BRect rect;
3013	if (fSelStart.y == fSelEnd.y)
3014		rect.Set(start.x, start.y, end.x + fFontWidth, end.y + fFontHeight);
3015	else
3016		rect.Set(0, start.y, fColumns * fFontWidth, end.y + fFontHeight);
3017
3018	rect = rect & Bounds();
3019
3020	DragMessage(&message, rect);
3021}
3022
3023
3024/* static */
3025void
3026TermView::AboutRequested()
3027{
3028	BAlert *alert = new (std::nothrow) BAlert("about",
3029		B_TRANSLATE("Terminal\n\n"
3030			"written by Kazuho Okui and Takashi Murai\n"
3031			"updated by Kian Duffy and others\n\n"
3032			"Copyright " B_UTF8_COPYRIGHT "2003-2009, Haiku.\n"),
3033		B_TRANSLATE("OK"));
3034	if (alert != NULL)
3035		alert->Go();
3036}
3037
3038
3039void
3040TermView::_ScrollTo(float y, bool scrollGfx)
3041{
3042	if (!scrollGfx)
3043		fScrollOffset = y;
3044
3045	if (fScrollBar != NULL)
3046		fScrollBar->SetValue(y);
3047	else
3048		ScrollTo(BPoint(0, y));
3049}
3050
3051
3052void
3053TermView::_ScrollToRange(TermPos start, TermPos end)
3054{
3055	if (start > end)
3056		std::swap(start, end);
3057
3058	float startY = _LineOffset(start.y);
3059	float endY = _LineOffset(end.y) + fFontHeight - 1;
3060	float height = Bounds().Height();
3061
3062	if (endY - startY > height) {
3063		// The range is greater than the height. Scroll to the closest border.
3064
3065		// already as good as it gets?
3066		if (startY <= 0 && endY >= height)
3067			return;
3068
3069		if (startY > 0) {
3070			// scroll down to align the start with the top of the view
3071			_ScrollTo(fScrollOffset + startY, true);
3072		} else {
3073			// scroll up to align the end with the bottom of the view
3074			_ScrollTo(fScrollOffset + endY - height, true);
3075		}
3076	} else {
3077		// The range is smaller than the height.
3078
3079		// already visible?
3080		if (startY >= 0 && endY <= height)
3081			return;
3082
3083		if (startY < 0) {
3084			// scroll up to make the start visible
3085			_ScrollTo(fScrollOffset + startY, true);
3086		} else {
3087			// scroll down to make the end visible
3088			_ScrollTo(fScrollOffset + endY - height, true);
3089		}
3090	}
3091}
3092
3093
3094void
3095TermView::DisableResizeView(int32 disableCount)
3096{
3097	fResizeViewDisableCount += disableCount;
3098}
3099
3100
3101void
3102TermView::_DrawInlineMethodString()
3103{
3104	if (!fInline || !fInline->String())
3105		return;
3106
3107	const int32 numChars = BString(fInline->String()).CountChars();
3108
3109	BPoint startPoint = _ConvertFromTerminal(fCursor);
3110	BPoint endPoint = startPoint;
3111	endPoint.x += fFontWidth * numChars;
3112	endPoint.y += fFontHeight + 1;
3113
3114	BRect eraseRect(startPoint, endPoint);
3115
3116	PushState();
3117	SetHighColor(kTermColorTable[7]);
3118	FillRect(eraseRect);
3119	PopState();
3120
3121	BPoint loc = _ConvertFromTerminal(fCursor);
3122	loc.y += fFontHeight;
3123	SetFont(&fHalfFont);
3124	SetHighColor(kTermColorTable[0]);
3125	SetLowColor(kTermColorTable[7]);
3126	DrawString(fInline->String(), loc);
3127}
3128
3129
3130void
3131TermView::_HandleInputMethodChanged(BMessage *message)
3132{
3133	const char *string = NULL;
3134	if (message->FindString("be:string", &string) < B_OK || string == NULL)
3135		return;
3136
3137	_ActivateCursor(false);
3138
3139	if (IsFocus())
3140		be_app->ObscureCursor();
3141
3142	// If we find the "be:confirmed" boolean (and the boolean is true),
3143	// it means it's over for now, so the current InlineInput object
3144	// should become inactive. We will probably receive a
3145	// B_INPUT_METHOD_STOPPED message after this one.
3146	bool confirmed;
3147	if (message->FindBool("be:confirmed", &confirmed) != B_OK)
3148		confirmed = false;
3149
3150	fInline->SetString("");
3151
3152	Invalidate();
3153	// TODO: Debug only
3154	snooze(100000);
3155
3156	fInline->SetString(string);
3157	fInline->ResetClauses();
3158
3159	if (!confirmed && !fInline->IsActive())
3160		fInline->SetActive(true);
3161
3162	// Get the clauses, and pass them to the InlineInput object
3163	// TODO: Find out if what we did it's ok, currently we don't consider
3164	// clauses at all, while the bebook says we should; though the visual
3165	// effect we obtained seems correct. Weird.
3166	int32 clauseCount = 0;
3167	int32 clauseStart;
3168	int32 clauseEnd;
3169	while (message->FindInt32("be:clause_start", clauseCount, &clauseStart)
3170			== B_OK
3171		&& message->FindInt32("be:clause_end", clauseCount, &clauseEnd)
3172			== B_OK) {
3173		if (!fInline->AddClause(clauseStart, clauseEnd))
3174			break;
3175		clauseCount++;
3176	}
3177
3178	if (confirmed) {
3179		fInline->SetString("");
3180		_ActivateCursor(true);
3181
3182		// now we need to feed ourselves the individual characters as if the
3183		// user would have pressed them now - this lets KeyDown() pick out all
3184		// the special characters like B_BACKSPACE, cursor keys and the like:
3185		const char* currPos = string;
3186		const char* prevPos = currPos;
3187		while (*currPos != '\0') {
3188			if ((*currPos & 0xC0) == 0xC0) {
3189				// found the start of an UTF-8 char, we collect while it lasts
3190				++currPos;
3191				while ((*currPos & 0xC0) == 0x80)
3192					++currPos;
3193			} else if ((*currPos & 0xC0) == 0x80) {
3194				// illegal: character starts with utf-8 intermediate byte, skip it
3195				prevPos = ++currPos;
3196			} else {
3197				// single byte character/code, just feed that
3198				++currPos;
3199			}
3200			KeyDown(prevPos, currPos - prevPos);
3201			prevPos = currPos;
3202		}
3203
3204		Invalidate();
3205	} else {
3206		// temporarily show transient state of inline input
3207		int32 selectionStart = 0;
3208		int32 selectionEnd = 0;
3209		message->FindInt32("be:selection", 0, &selectionStart);
3210		message->FindInt32("be:selection", 1, &selectionEnd);
3211
3212		fInline->SetSelectionOffset(selectionStart);
3213		fInline->SetSelectionLength(selectionEnd - selectionStart);
3214		Invalidate();
3215	}
3216}
3217
3218
3219void
3220TermView::_HandleInputMethodLocationRequest()
3221{
3222	BMessage message(B_INPUT_METHOD_EVENT);
3223	message.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST);
3224
3225	BString string(fInline->String());
3226
3227	const int32 &limit = string.CountChars();
3228	BPoint where = _ConvertFromTerminal(fCursor);
3229	where.y += fFontHeight;
3230
3231	for (int32 i = 0; i < limit; i++) {
3232		// Add the location of the UTF8 characters
3233
3234		where.x += fFontWidth;
3235		ConvertToScreen(&where);
3236
3237		message.AddPoint("be:location_reply", where);
3238		message.AddFloat("be:height_reply", fFontHeight);
3239	}
3240
3241	fInline->Method()->SendMessage(&message);
3242}
3243
3244
3245
3246void
3247TermView::_CancelInputMethod()
3248{
3249	if (!fInline)
3250		return;
3251
3252	InlineInput *inlineInput = fInline;
3253	fInline = NULL;
3254
3255	if (inlineInput->IsActive() && Window()) {
3256		Invalidate();
3257
3258		BMessage message(B_INPUT_METHOD_EVENT);
3259		message.AddInt32("be:opcode", B_INPUT_METHOD_STOPPED);
3260		inlineInput->Method()->SendMessage(&message);
3261	}
3262
3263	delete inlineInput;
3264}
3265
3266