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