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