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