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