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