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