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