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