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