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