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