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