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