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