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