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