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