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