btrfs: Attempt to fix GCC2 build.
[haiku.git] / src / apps / terminal / TermView.cpp
blob6565dceb223b0c63a793d5a3c18dfc17a5162aed
1 /*
2 * Copyright 2001-2014, Haiku, Inc.
3 * Copyright 2003-2004 Kian Duffy, myob@users.sourceforge.net
4 * Parts Copyright 1998-1999 Kazuho Okui and Takashi Murai.
5 * All rights reserved. Distributed under the terms of the MIT license.
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 * Ingo Weinhold, ingo_weinhold@gmx.de
13 * Clemens Zeidler, haiku@Clemens-Zeidler.de
14 * Siarzhuk Zharski, zharik@gmx.li
18 #include "TermView.h"
20 #include <signal.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <termios.h>
25 #include <algorithm>
26 #include <new>
27 #include <vector>
29 #include <Alert.h>
30 #include <Application.h>
31 #include <Beep.h>
32 #include <Catalog.h>
33 #include <Clipboard.h>
34 #include <Debug.h>
35 #include <Directory.h>
36 #include <Dragger.h>
37 #include <Input.h>
38 #include <Locale.h>
39 #include <MenuItem.h>
40 #include <Message.h>
41 #include <MessageRunner.h>
42 #include <Node.h>
43 #include <Path.h>
44 #include <PopUpMenu.h>
45 #include <PropertyInfo.h>
46 #include <Region.h>
47 #include <Roster.h>
48 #include <ScrollBar.h>
49 #include <ScrollView.h>
50 #include <String.h>
51 #include <StringView.h>
52 #include <UTF8.h>
53 #include <Window.h>
55 #include "ActiveProcessInfo.h"
56 #include "Colors.h"
57 #include "InlineInput.h"
58 #include "PrefHandler.h"
59 #include "Shell.h"
60 #include "ShellParameters.h"
61 #include "TermApp.h"
62 #include "TermConst.h"
63 #include "TerminalBuffer.h"
64 #include "TerminalCharClassifier.h"
65 #include "TermViewStates.h"
66 #include "VTkeymap.h"
69 #define ROWS_DEFAULT 25
70 #define COLUMNS_DEFAULT 80
73 #undef B_TRANSLATION_CONTEXT
74 #define B_TRANSLATION_CONTEXT "Terminal TermView"
76 static property_info sPropList[] = {
77 { "encoding",
78 {B_GET_PROPERTY, 0},
79 {B_DIRECT_SPECIFIER, 0},
80 "get terminal encoding"},
81 { "encoding",
82 {B_SET_PROPERTY, 0},
83 {B_DIRECT_SPECIFIER, 0},
84 "set terminal encoding"},
85 { "tty",
86 {B_GET_PROPERTY, 0},
87 {B_DIRECT_SPECIFIER, 0},
88 "get tty name."},
89 { 0 }
93 static const uint32 kUpdateSigWinch = 'Rwin';
94 static const uint32 kBlinkCursor = 'BlCr';
96 static const bigtime_t kSyncUpdateGranularity = 100000; // 0.1 s
98 static const int32 kCursorBlinkIntervals = 3;
99 static const int32 kCursorVisibleIntervals = 2;
100 static const bigtime_t kCursorBlinkInterval = 500000;
102 static const rgb_color kBlackColor = { 0, 0, 0, 255 };
103 static const rgb_color kWhiteColor = { 255, 255, 255, 255 };
105 // secondary mouse button drop
106 const int32 kSecondaryMouseDropAction = 'SMDA';
108 enum {
109 kInsert,
110 kChangeDirectory,
111 kLinkFiles,
112 kMoveFiles,
113 kCopyFiles
117 template<typename Type>
118 static inline Type
119 restrict_value(const Type& value, const Type& min, const Type& max)
121 return value < min ? min : (value > max ? max : value);
125 template<typename Type>
126 static inline Type
127 saturated_add(Type a, Type b)
129 const Type max = (Type)(-1);
130 return (max - a >= b ? a + b : max);
134 // #pragma mark - TextBufferSyncLocker
137 class TermView::TextBufferSyncLocker {
138 public:
139 TextBufferSyncLocker(TermView* view)
141 fView(view)
143 fView->fTextBuffer->Lock();
146 ~TextBufferSyncLocker()
148 fView->fTextBuffer->Unlock();
150 if (fView->fVisibleTextBufferChanged)
151 fView->_VisibleTextBufferChanged();
154 private:
155 TermView* fView;
159 // #pragma mark - TermView
162 TermView::TermView(BRect frame, const ShellParameters& shellParameters,
163 int32 historySize)
165 BView(frame, "termview", B_FOLLOW_ALL,
166 B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE),
167 fListener(NULL),
168 fColumns(COLUMNS_DEFAULT),
169 fRows(ROWS_DEFAULT),
170 fEncoding(M_UTF8),
171 fActive(false),
172 fScrBufSize(historySize),
173 fReportX10MouseEvent(false),
174 fReportNormalMouseEvent(false),
175 fReportButtonMouseEvent(false),
176 fReportAnyMouseEvent(false)
178 status_t status = _InitObject(shellParameters);
179 if (status != B_OK)
180 throw status;
181 SetTermSize(frame);
185 TermView::TermView(int rows, int columns,
186 const ShellParameters& shellParameters, int32 historySize)
188 BView(BRect(0, 0, 0, 0), "termview", B_FOLLOW_ALL,
189 B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE),
190 fListener(NULL),
191 fColumns(columns),
192 fRows(rows),
193 fEncoding(M_UTF8),
194 fActive(false),
195 fScrBufSize(historySize),
196 fReportX10MouseEvent(false),
197 fReportNormalMouseEvent(false),
198 fReportButtonMouseEvent(false),
199 fReportAnyMouseEvent(false)
201 status_t status = _InitObject(shellParameters);
202 if (status != B_OK)
203 throw status;
205 ResizeToPreferred();
207 // TODO: Don't show the dragger, since replicant capabilities
208 // don't work very well ATM.
210 BRect rect(0, 0, 16, 16);
211 rect.OffsetTo(Bounds().right - rect.Width(),
212 Bounds().bottom - rect.Height());
214 SetFlags(Flags() | B_DRAW_ON_CHILDREN | B_FOLLOW_ALL);
215 AddChild(new BDragger(rect, this,
216 B_FOLLOW_RIGHT|B_FOLLOW_BOTTOM, B_WILL_DRAW));*/
220 TermView::TermView(BMessage* archive)
222 BView(archive),
223 fListener(NULL),
224 fColumns(COLUMNS_DEFAULT),
225 fRows(ROWS_DEFAULT),
226 fEncoding(M_UTF8),
227 fActive(false),
228 fScrBufSize(1000),
229 fReportX10MouseEvent(false),
230 fReportNormalMouseEvent(false),
231 fReportButtonMouseEvent(false),
232 fReportAnyMouseEvent(false)
234 BRect frame = Bounds();
236 if (archive->FindInt32("encoding", (int32*)&fEncoding) < B_OK)
237 fEncoding = M_UTF8;
238 if (archive->FindInt32("columns", (int32*)&fColumns) < B_OK)
239 fColumns = COLUMNS_DEFAULT;
240 if (archive->FindInt32("rows", (int32*)&fRows) < B_OK)
241 fRows = ROWS_DEFAULT;
243 int32 argc = 0;
244 if (archive->HasInt32("argc"))
245 archive->FindInt32("argc", &argc);
247 const char **argv = new const char*[argc];
248 for (int32 i = 0; i < argc; i++) {
249 archive->FindString("argv", i, (const char**)&argv[i]);
252 // TODO: Retrieve colors, history size, etc. from archive
253 status_t status = _InitObject(ShellParameters(argc, argv));
254 if (status != B_OK)
255 throw status;
257 bool useRect = false;
258 if ((archive->FindBool("use_rect", &useRect) == B_OK) && useRect)
259 SetTermSize(frame);
261 delete[] argv;
265 /*! Initializes the object for further use.
266 The members fRows, fColumns, fEncoding, and fScrBufSize must
267 already be initialized; they are not touched by this method.
269 status_t
270 TermView::_InitObject(const ShellParameters& shellParameters)
272 SetFlags(Flags() | B_WILL_DRAW | B_FRAME_EVENTS
273 | B_FULL_UPDATE_ON_RESIZE/* | B_INPUT_METHOD_AWARE*/);
275 fShell = NULL;
276 fWinchRunner = NULL;
277 fCursorBlinkRunner = NULL;
278 fAutoScrollRunner = NULL;
279 fResizeRunner = NULL;
280 fResizeView = NULL;
281 fCharClassifier = NULL;
282 fFontWidth = 0;
283 fFontHeight = 0;
284 fFontAscent = 0;
285 fEmulateBold = false;
286 fAllowBold = false;
287 fFrameResized = false;
288 fResizeViewDisableCount = 0;
289 fLastActivityTime = 0;
290 fCursorState = 0;
291 fCursorStyle = BLOCK_CURSOR;
292 fCursorBlinking = true;
293 fCursorHidden = false;
294 fCursor = TermPos(0, 0);
295 fTextBuffer = NULL;
296 fVisibleTextBuffer = NULL;
297 fVisibleTextBufferChanged = false;
298 fScrollBar = NULL;
299 fInline = NULL;
300 fSelectForeColor = kWhiteColor;
301 fSelectBackColor = kBlackColor;
302 fScrollOffset = 0;
303 fLastSyncTime = 0;
304 fScrolledSinceLastSync = 0;
305 fSyncRunner = NULL;
306 fConsiderClockedSync = false;
307 fSelection.SetHighlighter(this);
308 fSelection.SetRange(TermPos(0, 0), TermPos(0, 0));
309 fPrevPos = TermPos(-1, - 1);
310 fReportX10MouseEvent = false;
311 fReportNormalMouseEvent = false;
312 fReportButtonMouseEvent = false;
313 fReportAnyMouseEvent = false;
314 fMouseClipboard = be_clipboard;
315 fDefaultState = new(std::nothrow) DefaultState(this);
316 fSelectState = new(std::nothrow) SelectState(this);
317 fHyperLinkState = new(std::nothrow) HyperLinkState(this);
318 fHyperLinkMenuState = new(std::nothrow) HyperLinkMenuState(this);
319 fActiveState = NULL;
321 fTextBuffer = new(std::nothrow) TerminalBuffer;
322 if (fTextBuffer == NULL)
323 return B_NO_MEMORY;
325 fVisibleTextBuffer = new(std::nothrow) BasicTerminalBuffer;
326 if (fVisibleTextBuffer == NULL)
327 return B_NO_MEMORY;
329 // TODO: Make the special word chars user-settable!
330 fCharClassifier = new(std::nothrow) DefaultCharClassifier(
331 kDefaultAdditionalWordCharacters);
332 if (fCharClassifier == NULL)
333 return B_NO_MEMORY;
335 status_t error = fTextBuffer->Init(fColumns, fRows, fScrBufSize);
336 if (error != B_OK)
337 return error;
338 fTextBuffer->SetEncoding(fEncoding);
340 error = fVisibleTextBuffer->Init(fColumns, fRows + 2, 0);
341 if (error != B_OK)
342 return error;
344 fShell = new (std::nothrow) Shell();
345 if (fShell == NULL)
346 return B_NO_MEMORY;
348 SetTermFont(be_fixed_font);
350 // set the shell parameters' encoding
351 ShellParameters modifiedShellParameters(shellParameters);
352 modifiedShellParameters.SetEncoding(fEncoding);
354 error = fShell->Open(fRows, fColumns, modifiedShellParameters);
356 if (error < B_OK)
357 return error;
359 error = _AttachShell(fShell);
360 if (error < B_OK)
361 return error;
363 fHighlights.AddItem(&fSelection);
365 if (fDefaultState == NULL || fSelectState == NULL || fHyperLinkState == NULL
366 || fHyperLinkMenuState == NULL) {
367 return B_NO_MEMORY;
370 SetLowColor(fTextBackColor);
371 SetViewColor(B_TRANSPARENT_32_BIT);
373 _NextState(fDefaultState);
375 return B_OK;
379 TermView::~TermView()
381 Shell* shell = fShell;
382 // _DetachShell sets fShell to NULL
384 _DetachShell();
386 delete fDefaultState;
387 delete fSelectState;
388 delete fHyperLinkState;
389 delete fHyperLinkMenuState;
390 delete fSyncRunner;
391 delete fAutoScrollRunner;
392 delete fCharClassifier;
393 delete fVisibleTextBuffer;
394 delete fTextBuffer;
395 delete shell;
399 bool
400 TermView::IsShellBusy() const
402 return fShell != NULL && fShell->HasActiveProcesses();
406 bool
407 TermView::GetActiveProcessInfo(ActiveProcessInfo& _info) const
409 if (fShell == NULL) {
410 _info.Unset();
411 return false;
414 return fShell->GetActiveProcessInfo(_info);
418 bool
419 TermView::GetShellInfo(ShellInfo& _info) const
421 if (fShell == NULL) {
422 _info = ShellInfo();
423 return false;
426 _info = fShell->Info();
427 return true;
431 /* static */
432 BArchivable *
433 TermView::Instantiate(BMessage* data)
435 if (validate_instantiation(data, "TermView")) {
436 TermView *view = new (std::nothrow) TermView(data);
437 return view;
440 return NULL;
444 status_t
445 TermView::Archive(BMessage* data, bool deep) const
447 status_t status = BView::Archive(data, deep);
448 if (status == B_OK)
449 status = data->AddString("add_on", TERM_SIGNATURE);
450 if (status == B_OK)
451 status = data->AddInt32("encoding", (int32)fEncoding);
452 if (status == B_OK)
453 status = data->AddInt32("columns", (int32)fColumns);
454 if (status == B_OK)
455 status = data->AddInt32("rows", (int32)fRows);
457 if (data->ReplaceString("class", "TermView") != B_OK)
458 data->AddString("class", "TermView");
460 return status;
464 rgb_color
465 TermView::ForegroundColor()
467 return fSelectForeColor;
471 rgb_color
472 TermView::BackgroundColor()
474 return fSelectBackColor;
478 inline int32
479 TermView::_LineAt(float y)
481 int32 location = int32(y + fScrollOffset);
483 // Make sure negative offsets are rounded towards the lower neighbor, too.
484 if (location < 0)
485 location -= fFontHeight - 1;
487 return location / fFontHeight;
491 inline float
492 TermView::_LineOffset(int32 index)
494 return index * fFontHeight - fScrollOffset;
498 // convert view coordinates to terminal text buffer position
499 TermPos
500 TermView::_ConvertToTerminal(const BPoint &p)
502 return TermPos(p.x >= 0 ? (int32)p.x / fFontWidth : -1, _LineAt(p.y));
506 // convert terminal text buffer position to view coordinates
507 inline BPoint
508 TermView::_ConvertFromTerminal(const TermPos &pos)
510 return BPoint(fFontWidth * pos.x, _LineOffset(pos.y));
514 inline void
515 TermView::_InvalidateTextRect(int32 x1, int32 y1, int32 x2, int32 y2)
517 // assume the worst case with full-width characters - invalidate 2 cells
518 BRect rect(x1 * fFontWidth, _LineOffset(y1),
519 (x2 + 1) * fFontWidth * 2 - 1, _LineOffset(y2 + 1) - 1);
520 //debug_printf("Invalidate((%f, %f) - (%f, %f))\n", rect.left, rect.top,
521 //rect.right, rect.bottom);
522 Invalidate(rect);
526 void
527 TermView::GetPreferredSize(float *width, float *height)
529 if (width)
530 *width = fColumns * fFontWidth - 1;
531 if (height)
532 *height = fRows * fFontHeight - 1;
536 const char *
537 TermView::TerminalName() const
539 if (fShell == NULL)
540 return NULL;
542 return fShell->TTYName();
546 //! Get width and height for terminal font
547 void
548 TermView::GetFontSize(int* _width, int* _height)
550 *_width = fFontWidth;
551 *_height = fFontHeight;
556 TermView::Rows() const
558 return fRows;
563 TermView::Columns() const
565 return fColumns;
569 //! Set number of rows and columns in terminal
570 BRect
571 TermView::SetTermSize(int rows, int columns, bool notifyShell)
573 //debug_printf("TermView::SetTermSize(%d, %d)\n", rows, columns);
574 if (rows > 0)
575 fRows = rows;
576 if (columns > 0)
577 fColumns = columns;
579 // To keep things simple, get rid of the selection first.
580 _Deselect();
583 BAutolock _(fTextBuffer);
584 if (fTextBuffer->ResizeTo(columns, rows) != B_OK
585 || fVisibleTextBuffer->ResizeTo(columns, rows + 2, 0)
586 != B_OK) {
587 return Bounds();
591 //debug_printf("Invalidate()\n");
592 Invalidate();
594 if (fScrollBar != NULL) {
595 _UpdateScrollBarRange();
596 fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows);
599 BRect rect(0, 0, fColumns * fFontWidth, fRows * fFontHeight);
601 // synchronize the visible text buffer
603 TextBufferSyncLocker _(this);
605 _SynchronizeWithTextBuffer(0, -1);
606 int32 offset = _LineAt(0);
607 fVisibleTextBuffer->SynchronizeWith(fTextBuffer, offset, offset,
608 offset + rows + 2);
609 fVisibleTextBufferChanged = true;
612 if (notifyShell)
613 fFrameResized = true;
615 return rect;
619 void
620 TermView::SetTermSize(BRect rect, bool notifyShell)
622 int rows;
623 int columns;
625 GetTermSizeFromRect(rect, &rows, &columns);
626 SetTermSize(rows, columns, notifyShell);
630 void
631 TermView::GetTermSizeFromRect(const BRect &rect, int *_rows,
632 int *_columns)
634 int columns = (rect.IntegerWidth() + 1) / fFontWidth;
635 int rows = (rect.IntegerHeight() + 1) / fFontHeight;
637 if (_rows)
638 *_rows = rows;
639 if (_columns)
640 *_columns = columns;
644 void
645 TermView::SetTextColor(rgb_color fore, rgb_color back)
647 fTextBackColor = back;
648 fTextForeColor = fore;
650 SetLowColor(fTextBackColor);
654 void
655 TermView::SetCursorColor(rgb_color fore, rgb_color back)
657 fCursorForeColor = fore;
658 fCursorBackColor = back;
662 void
663 TermView::SetSelectColor(rgb_color fore, rgb_color back)
665 fSelectForeColor = fore;
666 fSelectBackColor = back;
670 void
671 TermView::SetTermColor(uint index, rgb_color color, bool dynamic)
673 if (!dynamic) {
674 if (index < kTermColorCount)
675 fTextBuffer->SetPaletteColor(index, color);
676 return;
679 switch (index) {
680 case 10:
681 fTextForeColor = color;
682 break;
683 case 11:
684 fTextBackColor = color;
685 SetLowColor(fTextBackColor);
686 break;
687 case 110:
688 fTextForeColor = PrefHandler::Default()->getRGB(
689 PREF_TEXT_FORE_COLOR);
690 break;
691 case 111:
692 fTextBackColor = PrefHandler::Default()->getRGB(
693 PREF_TEXT_BACK_COLOR);
694 SetLowColor(fTextBackColor);
695 break;
696 default:
697 break;
703 TermView::Encoding() const
705 return fEncoding;
709 void
710 TermView::SetEncoding(int encoding)
712 fEncoding = encoding;
714 if (fShell != NULL)
715 fShell->SetEncoding(fEncoding);
717 BAutolock _(fTextBuffer);
718 fTextBuffer->SetEncoding(fEncoding);
722 void
723 TermView::SetMouseClipboard(BClipboard *clipboard)
725 fMouseClipboard = clipboard;
729 void
730 TermView::GetTermFont(BFont *font) const
732 if (font != NULL)
733 *font = fHalfFont;
737 //! Sets font for terminal
738 void
739 TermView::SetTermFont(const BFont *font)
741 int halfWidth = 0;
743 fHalfFont = font;
744 fBoldFont = font;
745 uint16 face = fBoldFont.Face();
746 fBoldFont.SetFace(B_BOLD_FACE | (face & ~B_REGULAR_FACE));
748 fHalfFont.SetSpacing(B_FIXED_SPACING);
750 // calculate half font's max width
751 // Not Bounding, check only A-Z (For case of fHalfFont is KanjiFont.)
752 for (int c = 0x20; c <= 0x7e; c++) {
753 char buf[4];
754 sprintf(buf, "%c", c);
755 int tmpWidth = (int)fHalfFont.StringWidth(buf);
756 if (tmpWidth > halfWidth)
757 halfWidth = tmpWidth;
760 fFontWidth = halfWidth;
762 font_height hh;
763 fHalfFont.GetHeight(&hh);
765 int font_ascent = (int)hh.ascent;
766 int font_descent =(int)hh.descent;
767 int font_leading =(int)hh.leading;
769 if (font_leading == 0)
770 font_leading = 1;
772 fFontAscent = font_ascent;
773 fFontHeight = font_ascent + font_descent + font_leading + 1;
775 fCursorStyle = PrefHandler::Default() == NULL ? BLOCK_CURSOR
776 : PrefHandler::Default()->getCursor(PREF_CURSOR_STYLE);
777 fCursorBlinking = PrefHandler::Default()->getBool(PREF_BLINK_CURSOR);
779 fEmulateBold = PrefHandler::Default() == NULL ? false
780 : PrefHandler::Default()->getBool(PREF_EMULATE_BOLD);
782 fAllowBold = PrefHandler::Default() == NULL ? false
783 : PrefHandler::Default()->getBool(PREF_ALLOW_BOLD);
785 _ScrollTo(0, false);
786 if (fScrollBar != NULL)
787 fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows);
791 void
792 TermView::SetScrollBar(BScrollBar *scrollBar)
794 fScrollBar = scrollBar;
795 if (fScrollBar != NULL)
796 fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows);
800 void
801 TermView::Copy(BClipboard *clipboard)
803 BAutolock _(fTextBuffer);
805 if (!_HasSelection())
806 return;
808 BString copyStr;
809 fTextBuffer->GetStringFromRegion(copyStr, fSelection.Start(),
810 fSelection.End());
812 if (clipboard->Lock()) {
813 BMessage *clipMsg = NULL;
814 clipboard->Clear();
816 if ((clipMsg = clipboard->Data()) != NULL) {
817 clipMsg->AddData("text/plain", B_MIME_TYPE, copyStr.String(),
818 copyStr.Length());
819 clipboard->Commit();
821 clipboard->Unlock();
826 void
827 TermView::Paste(BClipboard *clipboard)
829 if (clipboard->Lock()) {
830 BMessage *clipMsg = clipboard->Data();
831 const char* text;
832 ssize_t numBytes;
833 if (clipMsg->FindData("text/plain", B_MIME_TYPE,
834 (const void**)&text, &numBytes) == B_OK ) {
835 _WritePTY(text, numBytes);
838 clipboard->Unlock();
840 _ScrollTo(0, true);
845 void
846 TermView::SelectAll()
848 BAutolock _(fTextBuffer);
850 _Select(TermPos(0, -fTextBuffer->HistorySize()),
851 TermPos(0, fTextBuffer->Height()), false, true);
855 void
856 TermView::Clear()
858 _Deselect();
861 BAutolock _(fTextBuffer);
862 fTextBuffer->Clear(true);
864 fVisibleTextBuffer->Clear(true);
866 //debug_printf("Invalidate()\n");
867 Invalidate();
869 _ScrollTo(0, false);
870 if (fScrollBar) {
871 fScrollBar->SetRange(0, 0);
872 fScrollBar->SetProportion(1);
877 //! Draw region
878 void
879 TermView::_InvalidateTextRange(TermPos start, TermPos end)
881 if (end < start)
882 std::swap(start, end);
884 if (start.y == end.y) {
885 _InvalidateTextRect(start.x, start.y, end.x, end.y);
886 } else {
887 _InvalidateTextRect(start.x, start.y, fColumns, start.y);
889 if (end.y - start.y > 0)
890 _InvalidateTextRect(0, start.y + 1, fColumns, end.y - 1);
892 _InvalidateTextRect(0, end.y, end.x, end.y);
897 status_t
898 TermView::_AttachShell(Shell *shell)
900 if (shell == NULL)
901 return B_BAD_VALUE;
903 fShell = shell;
905 return fShell->AttachBuffer(TextBuffer());
909 void
910 TermView::_DetachShell()
912 fShell->DetachBuffer();
913 fShell = NULL;
917 void
918 TermView::_SwitchCursorBlinking(bool blinkingOn)
920 if (blinkingOn) {
921 if (fCursorBlinkRunner == NULL) {
922 BMessage blinkMessage(kBlinkCursor);
923 fCursorBlinkRunner = new (std::nothrow) BMessageRunner(
924 BMessenger(this), &blinkMessage, kCursorBlinkInterval);
926 } else {
927 // make sure the cursor becomes visible
928 fCursorState = 0;
929 _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
930 delete fCursorBlinkRunner;
931 fCursorBlinkRunner = NULL;
936 void
937 TermView::_Activate()
939 fActive = true;
940 _SwitchCursorBlinking(fCursorBlinking);
944 void
945 TermView::_Deactivate()
947 // make sure the cursor becomes visible
948 fCursorState = 0;
949 _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
951 _SwitchCursorBlinking(false);
953 fActive = false;
957 //! Draw part of a line in the given view.
958 void
959 TermView::_DrawLinePart(int32 x1, int32 y1, uint32 attr, char *buf,
960 int32 width, Highlight* highlight, bool cursor, BView *inView)
962 if (highlight != NULL)
963 attr = highlight->Highlighter()->AdjustTextAttributes(attr);
965 inView->SetFont(IS_BOLD(attr) && !fEmulateBold && fAllowBold
966 ? &fBoldFont : &fHalfFont);
968 // Set pen point
969 int x2 = x1 + fFontWidth * width;
970 int y2 = y1 + fFontHeight;
972 rgb_color rgb_fore = fTextForeColor;
973 rgb_color rgb_back = fTextBackColor;
975 // color attribute
976 int forecolor = IS_FORECOLOR(attr);
977 int backcolor = IS_BACKCOLOR(attr);
979 if (IS_FORESET(attr))
980 rgb_fore = fTextBuffer->PaletteColor(forecolor);
981 if (IS_BACKSET(attr))
982 rgb_back = fTextBuffer->PaletteColor(backcolor);
984 // Selection check.
985 if (cursor) {
986 rgb_fore = fCursorForeColor;
987 rgb_back = fCursorBackColor;
988 } else if (highlight != NULL) {
989 rgb_fore = highlight->Highlighter()->ForegroundColor();
990 rgb_back = highlight->Highlighter()->BackgroundColor();
991 } else {
992 // Reverse attribute(If selected area, don't reverse color).
993 if (IS_INVERSE(attr)) {
994 rgb_color rgb_tmp = rgb_fore;
995 rgb_fore = rgb_back;
996 rgb_back = rgb_tmp;
1000 // Fill color at Background color and set low color.
1001 inView->SetHighColor(rgb_back);
1002 inView->FillRect(BRect(x1, y1, x2 - 1, y2 - 1));
1003 inView->SetLowColor(rgb_back);
1004 inView->SetHighColor(rgb_fore);
1006 // Draw character.
1007 if (IS_BOLD(attr)) {
1008 if (fEmulateBold) {
1009 inView->MovePenTo(x1 - 1, y1 + fFontAscent - 1);
1010 inView->DrawString((char *)buf);
1011 inView->SetDrawingMode(B_OP_BLEND);
1012 } else {
1013 rgb_color bright = rgb_fore;
1015 bright.red = saturated_add<uint8>(bright.red, 64);
1016 bright.green = saturated_add<uint8>(bright.green, 64);
1017 bright.blue = saturated_add<uint8>(bright.blue, 64);
1019 inView->SetHighColor(bright);
1023 inView->MovePenTo(x1, y1 + fFontAscent);
1024 inView->DrawString((char *)buf);
1025 inView->SetDrawingMode(B_OP_COPY);
1027 // underline attribute
1028 if (IS_UNDER(attr)) {
1029 inView->MovePenTo(x1, y1 + fFontAscent);
1030 inView->StrokeLine(BPoint(x1 , y1 + fFontAscent),
1031 BPoint(x2 , y1 + fFontAscent));
1036 /*! Caller must have locked fTextBuffer.
1038 void
1039 TermView::_DrawCursor()
1041 BRect rect(fFontWidth * fCursor.x, _LineOffset(fCursor.y), 0, 0);
1042 rect.right = rect.left + fFontWidth - 1;
1043 rect.bottom = rect.top + fFontHeight - 1;
1044 int32 firstVisible = _LineAt(0);
1046 UTF8Char character;
1047 uint32 attr = 0;
1049 bool cursorVisible = _IsCursorVisible();
1051 if (cursorVisible) {
1052 switch (fCursorStyle) {
1053 case UNDERLINE_CURSOR:
1054 rect.top = rect.bottom - 2;
1055 break;
1056 case IBEAM_CURSOR:
1057 rect.right = rect.left + 1;
1058 break;
1059 case BLOCK_CURSOR:
1060 default:
1061 break;
1065 Highlight* highlight = _CheckHighlightRegion(TermPos(fCursor.x, fCursor.y));
1066 if (fVisibleTextBuffer->GetChar(fCursor.y - firstVisible, fCursor.x,
1067 character, attr) == A_CHAR
1068 && (fCursorStyle == BLOCK_CURSOR || !cursorVisible)) {
1070 int32 width = IS_WIDTH(attr) ? FULL_WIDTH : HALF_WIDTH;
1071 char buffer[5];
1072 int32 bytes = UTF8Char::ByteCount(character.bytes[0]);
1073 memcpy(buffer, character.bytes, bytes);
1074 buffer[bytes] = '\0';
1076 _DrawLinePart(fCursor.x * fFontWidth, (int32)rect.top, attr, buffer,
1077 width, highlight, cursorVisible, this);
1078 } else {
1079 if (highlight != NULL)
1080 SetHighColor(highlight->Highlighter()->BackgroundColor());
1081 else if (cursorVisible)
1082 SetHighColor(fCursorBackColor );
1083 else {
1084 uint32 count = 0;
1085 rgb_color rgb_back = fTextBackColor;
1086 if (fTextBuffer->IsAlternateScreenActive())
1087 // alternate screen uses cell attributes beyond the line ends
1088 fTextBuffer->GetCellAttributes(
1089 fCursor.y, fCursor.x, attr, count);
1090 else
1091 attr = fVisibleTextBuffer->GetLineColor(
1092 fCursor.y - firstVisible);
1094 if (IS_BACKSET(attr))
1095 rgb_back = fTextBuffer->PaletteColor(IS_BACKCOLOR(attr));
1096 SetHighColor(rgb_back);
1099 if (IS_WIDTH(attr) && fCursorStyle != IBEAM_CURSOR)
1100 rect.right += fFontWidth;
1102 FillRect(rect);
1107 bool
1108 TermView::_IsCursorVisible() const
1110 return !fCursorHidden && fCursorState < kCursorVisibleIntervals;
1114 void
1115 TermView::_BlinkCursor()
1117 bool wasVisible = _IsCursorVisible();
1119 if (!wasVisible && fInline && fInline->IsActive())
1120 return;
1122 bigtime_t now = system_time();
1123 if (Window()->IsActive() && now - fLastActivityTime >= kCursorBlinkInterval)
1124 fCursorState = (fCursorState + 1) % kCursorBlinkIntervals;
1125 else
1126 fCursorState = 0;
1128 if (wasVisible != _IsCursorVisible())
1129 _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
1133 void
1134 TermView::_ActivateCursor(bool invalidate)
1136 fLastActivityTime = system_time();
1137 if (invalidate && fCursorState != 0)
1138 _BlinkCursor();
1139 else
1140 fCursorState = 0;
1144 //! Update scroll bar range and knob size.
1145 void
1146 TermView::_UpdateScrollBarRange()
1148 if (fScrollBar == NULL)
1149 return;
1151 int32 historySize;
1153 BAutolock _(fTextBuffer);
1154 historySize = fTextBuffer->HistorySize();
1157 float viewHeight = fRows * fFontHeight;
1158 float historyHeight = (float)historySize * fFontHeight;
1160 //debug_printf("TermView::_UpdateScrollBarRange(): history: %ld, range: %f - 0\n",
1161 //historySize, -historyHeight);
1163 fScrollBar->SetRange(-historyHeight, 0);
1164 if (historySize > 0)
1165 fScrollBar->SetProportion(viewHeight / (viewHeight + historyHeight));
1169 //! Handler for SIGWINCH
1170 void
1171 TermView::_UpdateSIGWINCH()
1173 if (fFrameResized) {
1174 fShell->UpdateWindowSize(fRows, fColumns);
1175 fFrameResized = false;
1180 void
1181 TermView::AttachedToWindow()
1183 fMouseButtons = 0;
1185 _UpdateModifiers();
1187 // update the terminal size because it may have changed while the TermView
1188 // was detached from the window. On such conditions FrameResized was not
1189 // called when the resize occured
1190 SetTermSize(Bounds(), true);
1191 MakeFocus(true);
1192 if (fScrollBar) {
1193 fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows);
1194 _UpdateScrollBarRange();
1197 BMessenger thisMessenger(this);
1199 BMessage message(kUpdateSigWinch);
1200 fWinchRunner = new (std::nothrow) BMessageRunner(thisMessenger,
1201 &message, 500000);
1204 TextBufferSyncLocker _(this);
1205 fTextBuffer->SetListener(thisMessenger);
1206 _SynchronizeWithTextBuffer(0, -1);
1209 be_clipboard->StartWatching(thisMessenger);
1213 void
1214 TermView::DetachedFromWindow()
1216 be_clipboard->StopWatching(BMessenger(this));
1218 _NextState(fDefaultState);
1220 delete fWinchRunner;
1221 fWinchRunner = NULL;
1223 delete fCursorBlinkRunner;
1224 fCursorBlinkRunner = NULL;
1226 delete fResizeRunner;
1227 fResizeRunner = NULL;
1230 BAutolock _(fTextBuffer);
1231 fTextBuffer->UnsetListener();
1236 void
1237 TermView::Draw(BRect updateRect)
1239 int32 x1 = (int32)updateRect.left / fFontWidth;
1240 int32 x2 = std::min((int)updateRect.right / fFontWidth, fColumns - 1);
1242 int32 firstVisible = _LineAt(0);
1243 int32 y1 = _LineAt(updateRect.top);
1244 int32 y2 = std::min(_LineAt(updateRect.bottom), (int32)fRows - 1);
1246 // clear the area to the right of the line ends
1247 if (y1 <= y2) {
1248 float clearLeft = fColumns * fFontWidth;
1249 if (clearLeft <= updateRect.right) {
1250 BRect rect(clearLeft, updateRect.top, updateRect.right,
1251 updateRect.bottom);
1252 SetHighColor(fTextBackColor);
1253 FillRect(rect);
1257 // clear the area below the last line
1258 if (y2 == fRows - 1) {
1259 float clearTop = _LineOffset(fRows);
1260 if (clearTop <= updateRect.bottom) {
1261 BRect rect(updateRect.left, clearTop, updateRect.right,
1262 updateRect.bottom);
1263 SetHighColor(fTextBackColor);
1264 FillRect(rect);
1268 // draw the affected line parts
1269 if (x1 <= x2) {
1270 uint32 attr = 0;
1272 for (int32 j = y1; j <= y2; j++) {
1273 int32 k = x1;
1274 char buf[fColumns * 4 + 1];
1276 if (fVisibleTextBuffer->IsFullWidthChar(j - firstVisible, k))
1277 k--;
1279 if (k < 0)
1280 k = 0;
1282 for (int32 i = k; i <= x2;) {
1283 int32 lastColumn = x2;
1284 Highlight* highlight = _CheckHighlightRegion(j, i, lastColumn);
1285 // This will clip lastColumn to the selection start or end
1286 // to ensure the selection is not drawn at the same time as
1287 // something else
1288 int32 count = fVisibleTextBuffer->GetString(j - firstVisible, i,
1289 lastColumn, buf, attr);
1291 // debug_printf(" fVisibleTextBuffer->GetString(%ld, %ld, %ld) -> (%ld, \"%.*s\"), highlight: %p\n",
1292 // j - firstVisible, i, lastColumn, count, (int)count, buf, highlight);
1294 if (count == 0) {
1295 // No chars to draw : we just fill the rectangle with the
1296 // back color of the last char at the left
1297 int nextColumn = lastColumn + 1;
1298 BRect rect(fFontWidth * i, _LineOffset(j),
1299 fFontWidth * nextColumn - 1, 0);
1300 rect.bottom = rect.top + fFontHeight - 1;
1302 rgb_color rgb_back = highlight != NULL
1303 ? highlight->Highlighter()->BackgroundColor()
1304 : fTextBackColor;
1306 if (fTextBuffer->IsAlternateScreenActive()) {
1307 // alternate screen uses cell attributes
1308 // beyond the line ends
1309 uint32 count = 0;
1310 fTextBuffer->GetCellAttributes(j, i, attr, count);
1311 rect.right = rect.left + fFontWidth * count - 1;
1312 nextColumn = i + count;
1313 } else
1314 attr = fVisibleTextBuffer->GetLineColor(j - firstVisible);
1316 if (IS_BACKSET(attr)) {
1317 int backcolor = IS_BACKCOLOR(attr);
1318 rgb_back = fTextBuffer->PaletteColor(backcolor);
1321 SetHighColor(rgb_back);
1322 rgb_back = HighColor();
1323 FillRect(rect);
1325 // Go on to the next block
1326 i = nextColumn;
1327 continue;
1330 // Note: full-width characters GetString()-ed always
1331 // with count 1, so this hardcoding is safe. From the other
1332 // side - drawing the whole string with one call render the
1333 // characters not aligned to cells grid - that looks much more
1334 // inaccurate for full-width strings than for half-width ones.
1335 if (IS_WIDTH(attr))
1336 count = FULL_WIDTH;
1338 _DrawLinePart(fFontWidth * i, (int32)_LineOffset(j),
1339 attr, buf, count, highlight, false, this);
1340 i += count;
1345 if (fInline && fInline->IsActive())
1346 _DrawInlineMethodString();
1348 if (fCursor >= TermPos(x1, y1) && fCursor <= TermPos(x2, y2))
1349 _DrawCursor();
1353 void
1354 TermView::_DoPrint(BRect updateRect)
1356 #if 0
1357 uint32 attr;
1358 uchar buf[1024];
1360 const int numLines = (int)((updateRect.Height()) / fFontHeight);
1362 int y1 = (int)(updateRect.top) / fFontHeight;
1363 y1 = y1 -(fScrBufSize - numLines * 2);
1364 if (y1 < 0)
1365 y1 = 0;
1367 const int y2 = y1 + numLines -1;
1369 const int x1 = (int)(updateRect.left) / fFontWidth;
1370 const int x2 = (int)(updateRect.right) / fFontWidth;
1372 for (int j = y1; j <= y2; j++) {
1373 // If(x1, y1) Buffer is in string full width character,
1374 // alignment start position.
1376 int k = x1;
1377 if (fTextBuffer->IsFullWidthChar(j, k))
1378 k--;
1380 if (k < 0)
1381 k = 0;
1383 for (int i = k; i <= x2;) {
1384 int count = fTextBuffer->GetString(j, i, x2, buf, &attr);
1385 if (count < 0) {
1386 i += abs(count);
1387 continue;
1390 _DrawLinePart(fFontWidth * i, fFontHeight * j,
1391 attr, buf, count, false, false, this);
1392 i += count;
1395 #endif // 0
1399 void
1400 TermView::WindowActivated(bool active)
1402 BView::WindowActivated(active);
1403 if (active && IsFocus()) {
1404 if (!fActive)
1405 _Activate();
1406 } else {
1407 if (fActive)
1408 _Deactivate();
1411 _UpdateModifiers();
1413 fActiveState->WindowActivated(active);
1417 void
1418 TermView::MakeFocus(bool focusState)
1420 BView::MakeFocus(focusState);
1422 if (focusState && Window() && Window()->IsActive()) {
1423 if (!fActive)
1424 _Activate();
1425 } else {
1426 if (fActive)
1427 _Deactivate();
1432 void
1433 TermView::KeyDown(const char *bytes, int32 numBytes)
1435 _UpdateModifiers();
1437 fActiveState->KeyDown(bytes, numBytes);
1441 void
1442 TermView::FrameResized(float width, float height)
1444 //debug_printf("TermView::FrameResized(%f, %f)\n", width, height);
1445 int32 columns = ((int32)width + 1) / fFontWidth;
1446 int32 rows = ((int32)height + 1) / fFontHeight;
1448 if (columns == fColumns && rows == fRows)
1449 return;
1451 bool hasResizeView = fResizeRunner != NULL;
1452 if (!hasResizeView) {
1453 // show the current size in a view
1454 fResizeView = new BStringView(BRect(100, 100, 300, 140), "size", "");
1455 fResizeView->SetAlignment(B_ALIGN_CENTER);
1456 fResizeView->SetFont(be_bold_font);
1457 fResizeView->SetViewColor(fTextBackColor);
1458 fResizeView->SetLowColor(fTextBackColor);
1459 fResizeView->SetHighColor(fTextForeColor);
1461 BMessage message(MSG_REMOVE_RESIZE_VIEW_IF_NEEDED);
1462 fResizeRunner = new(std::nothrow) BMessageRunner(BMessenger(this),
1463 &message, 25000LL);
1466 BString text;
1467 text << columns << " x " << rows;
1468 fResizeView->SetText(text.String());
1469 fResizeView->GetPreferredSize(&width, &height);
1470 fResizeView->ResizeTo(width * 1.5, height * 1.5);
1471 fResizeView->MoveTo((Bounds().Width() - fResizeView->Bounds().Width()) / 2,
1472 (Bounds().Height()- fResizeView->Bounds().Height()) / 2);
1473 if (!hasResizeView && fResizeViewDisableCount < 1)
1474 AddChild(fResizeView);
1476 if (fResizeViewDisableCount > 0)
1477 fResizeViewDisableCount--;
1479 SetTermSize(rows, columns, true);
1483 void
1484 TermView::MessageReceived(BMessage *msg)
1486 if (fActiveState->MessageReceived(msg))
1487 return;
1489 entry_ref ref;
1490 const char *ctrl_l = "\x0c";
1492 // first check for any dropped message
1493 if (msg->WasDropped() && (msg->what == B_SIMPLE_DATA
1494 || msg->what == B_MIME_DATA)) {
1495 char *text;
1496 ssize_t numBytes;
1497 //rgb_color *color;
1499 int32 i = 0;
1501 if (msg->FindRef("refs", i++, &ref) == B_OK) {
1502 // first check if secondary mouse button is pressed
1503 int32 buttons = 0;
1504 msg->FindInt32("buttons", &buttons);
1506 if (buttons == B_SECONDARY_MOUSE_BUTTON) {
1507 // start popup menu
1508 _SecondaryMouseButtonDropped(msg);
1509 return;
1512 _DoFileDrop(ref);
1514 while (msg->FindRef("refs", i++, &ref) == B_OK) {
1515 _WritePTY(" ", 1);
1516 _DoFileDrop(ref);
1518 return;
1519 #if 0
1520 } else if (msg->FindData("RGBColor", B_RGB_COLOR_TYPE,
1521 (const void **)&color, &numBytes) == B_OK
1522 && numBytes == sizeof(color)) {
1523 // TODO: handle color drop
1524 // maybe only on replicants ?
1525 return;
1526 #endif
1527 } else if (msg->FindData("text/plain", B_MIME_TYPE,
1528 (const void **)&text, &numBytes) == B_OK) {
1529 _WritePTY(text, numBytes);
1530 return;
1534 switch (msg->what) {
1535 case B_SIMPLE_DATA:
1536 case B_REFS_RECEIVED:
1538 // handle refs if they weren't dropped
1539 int32 i = 0;
1540 if (msg->FindRef("refs", i++, &ref) == B_OK) {
1541 _DoFileDrop(ref);
1543 while (msg->FindRef("refs", i++, &ref) == B_OK) {
1544 _WritePTY(" ", 1);
1545 _DoFileDrop(ref);
1547 } else
1548 BView::MessageReceived(msg);
1549 break;
1552 case B_COPY:
1553 Copy(be_clipboard);
1554 break;
1556 case B_PASTE:
1558 int32 code;
1559 if (msg->FindInt32("index", &code) == B_OK)
1560 Paste(be_clipboard);
1561 break;
1564 case B_CLIPBOARD_CHANGED:
1565 // This message originates from the system clipboard. Overwrite
1566 // the contents of the mouse clipboard with the ones from the
1567 // system clipboard, in case it contains text data.
1568 if (be_clipboard->Lock()) {
1569 if (fMouseClipboard->Lock()) {
1570 BMessage* clipMsgA = be_clipboard->Data();
1571 const char* text;
1572 ssize_t numBytes;
1573 if (clipMsgA->FindData("text/plain", B_MIME_TYPE,
1574 (const void**)&text, &numBytes) == B_OK ) {
1575 fMouseClipboard->Clear();
1576 BMessage* clipMsgB = fMouseClipboard->Data();
1577 clipMsgB->AddData("text/plain", B_MIME_TYPE,
1578 text, numBytes);
1579 fMouseClipboard->Commit();
1581 fMouseClipboard->Unlock();
1583 be_clipboard->Unlock();
1585 break;
1587 case B_SELECT_ALL:
1588 SelectAll();
1589 break;
1591 case B_SET_PROPERTY:
1593 int32 i;
1594 int32 encodingID;
1595 BMessage specifier;
1596 if (msg->GetCurrentSpecifier(&i, &specifier) == B_OK
1597 && strcmp("encoding",
1598 specifier.FindString("property", i)) == 0) {
1599 msg->FindInt32 ("data", &encodingID);
1600 SetEncoding(encodingID);
1601 msg->SendReply(B_REPLY);
1602 } else {
1603 BView::MessageReceived(msg);
1605 break;
1608 case B_GET_PROPERTY:
1610 int32 i;
1611 BMessage specifier;
1612 if (msg->GetCurrentSpecifier(&i, &specifier) == B_OK
1613 && strcmp("encoding",
1614 specifier.FindString("property", i)) == 0) {
1615 BMessage reply(B_REPLY);
1616 reply.AddInt32("result", Encoding());
1617 msg->SendReply(&reply);
1618 } else if (strcmp("tty",
1619 specifier.FindString("property", i)) == 0) {
1620 BMessage reply(B_REPLY);
1621 reply.AddString("result", TerminalName());
1622 msg->SendReply(&reply);
1623 } else {
1624 BView::MessageReceived(msg);
1626 break;
1629 case B_MODIFIERS_CHANGED:
1631 _UpdateModifiers();
1632 break;
1635 case B_INPUT_METHOD_EVENT:
1637 int32 opcode;
1638 if (msg->FindInt32("be:opcode", &opcode) == B_OK) {
1639 switch (opcode) {
1640 case B_INPUT_METHOD_STARTED:
1642 BMessenger messenger;
1643 if (msg->FindMessenger("be:reply_to",
1644 &messenger) == B_OK) {
1645 fInline = new (std::nothrow)
1646 InlineInput(messenger);
1648 break;
1651 case B_INPUT_METHOD_STOPPED:
1652 delete fInline;
1653 fInline = NULL;
1654 break;
1656 case B_INPUT_METHOD_CHANGED:
1657 if (fInline != NULL)
1658 _HandleInputMethodChanged(msg);
1659 break;
1661 case B_INPUT_METHOD_LOCATION_REQUEST:
1662 if (fInline != NULL)
1663 _HandleInputMethodLocationRequest();
1664 break;
1666 default:
1667 break;
1670 break;
1673 case B_MOUSE_WHEEL_CHANGED:
1675 // overridden to allow scrolling emulation in alternative screen
1676 // mode
1677 BAutolock locker(fTextBuffer);
1678 float deltaY = 0;
1679 if (fTextBuffer->IsAlternateScreenActive()
1680 && msg->FindFloat("be:wheel_delta_y", &deltaY) == B_OK
1681 && deltaY != 0) {
1682 // We are in alternative screen mode and have a vertical delta
1683 // we can work with -- emulate scrolling via terminal escape
1684 // sequences.
1685 locker.Unlock();
1687 // scroll pagewise, if one of Option, Command, or Control is
1688 // pressed
1689 int32 steps;
1690 const char* stepString;
1691 if ((modifiers() & B_SHIFT_KEY) != 0) {
1692 // pagewise
1693 stepString = deltaY > 0
1694 ? PAGE_DOWN_KEY_CODE : PAGE_UP_KEY_CODE;
1695 steps = abs((int)deltaY);
1696 } else {
1697 // three lines per step
1698 stepString = deltaY > 0
1699 ? DOWN_ARROW_KEY_CODE : UP_ARROW_KEY_CODE;
1700 steps = 3 * abs((int)deltaY);
1703 // We want to do only a single write(), so compose a string
1704 // repeating the sequence as often as required by the delta.
1705 BString toWrite;
1706 for (int32 i = 0; i <steps; i++)
1707 toWrite << stepString;
1709 _WritePTY(toWrite.String(), toWrite.Length());
1710 } else {
1711 // let the BView's implementation handle the standard scrolling
1712 locker.Unlock();
1713 BView::MessageReceived(msg);
1716 break;
1719 case MENU_CLEAR_ALL:
1720 Clear();
1721 fShell->Write(ctrl_l, 1);
1722 break;
1723 case kBlinkCursor:
1724 _BlinkCursor();
1725 break;
1726 case kUpdateSigWinch:
1727 _UpdateSIGWINCH();
1728 break;
1729 case kSecondaryMouseDropAction:
1730 _DoSecondaryMouseDropAction(msg);
1731 break;
1732 case MSG_TERMINAL_BUFFER_CHANGED:
1734 TextBufferSyncLocker _(this);
1735 _SynchronizeWithTextBuffer(0, -1);
1736 break;
1738 case MSG_SET_TERMINAL_TITLE:
1740 const char* title;
1741 if (msg->FindString("title", &title) == B_OK) {
1742 if (fListener != NULL)
1743 fListener->SetTermViewTitle(this, title);
1745 break;
1747 case MSG_SET_TERMINAL_COLORS:
1749 int32 count = 0;
1750 if (msg->FindInt32("count", &count) != B_OK)
1751 break;
1752 bool dynamic = false;
1753 if (msg->FindBool("dynamic", &dynamic) != B_OK)
1754 break;
1755 for (int i = 0; i < count; i++) {
1756 uint8 index = 0;
1757 if (msg->FindUInt8("index", i, &index) != B_OK)
1758 break;
1760 ssize_t bytes = 0;
1761 rgb_color* color = 0;
1762 if (msg->FindData("color", B_RGB_COLOR_TYPE,
1763 i, (const void**)&color, &bytes) != B_OK)
1764 break;
1765 SetTermColor(index, *color, dynamic);
1767 break;
1769 case MSG_RESET_TERMINAL_COLORS:
1771 int32 count = 0;
1772 if (msg->FindInt32("count", &count) != B_OK)
1773 break;
1774 bool dynamic = false;
1775 if (msg->FindBool("dynamic", &dynamic) != B_OK)
1776 break;
1777 for (int i = 0; i < count; i++) {
1778 uint8 index = 0;
1779 if (msg->FindUInt8("index", i, &index) != B_OK)
1780 break;
1782 if (index < kTermColorCount)
1783 SetTermColor(index,
1784 TermApp::DefaultPalette()[index], dynamic);
1786 break;
1788 case MSG_SET_CURSOR_STYLE:
1790 int32 style = BLOCK_CURSOR;
1791 if (msg->FindInt32("style", &style) == B_OK)
1792 fCursorStyle = style;
1794 bool blinking = fCursorBlinking;
1795 if (msg->FindBool("blinking", &blinking) == B_OK) {
1796 fCursorBlinking = blinking;
1797 _SwitchCursorBlinking(fCursorBlinking);
1800 bool hidden = fCursorHidden;
1801 if (msg->FindBool("hidden", &hidden) == B_OK)
1802 fCursorHidden = hidden;
1803 break;
1805 case MSG_REPORT_MOUSE_EVENT:
1807 bool report;
1808 if (msg->FindBool("reportX10MouseEvent", &report) == B_OK)
1809 fReportX10MouseEvent = report;
1811 if (msg->FindBool("reportNormalMouseEvent", &report) == B_OK)
1812 fReportNormalMouseEvent = report;
1814 if (msg->FindBool("reportButtonMouseEvent", &report) == B_OK)
1815 fReportButtonMouseEvent = report;
1817 if (msg->FindBool("reportAnyMouseEvent", &report) == B_OK)
1818 fReportAnyMouseEvent = report;
1819 break;
1821 case MSG_REMOVE_RESIZE_VIEW_IF_NEEDED:
1823 BPoint point;
1824 uint32 buttons;
1825 GetMouse(&point, &buttons, false);
1826 if (buttons != 0)
1827 break;
1829 if (fResizeView != NULL) {
1830 fResizeView->RemoveSelf();
1831 delete fResizeView;
1832 fResizeView = NULL;
1834 delete fResizeRunner;
1835 fResizeRunner = NULL;
1836 break;
1839 case MSG_QUIT_TERMNAL:
1841 int32 reason;
1842 if (msg->FindInt32("reason", &reason) != B_OK)
1843 reason = 0;
1844 if (fListener != NULL)
1845 fListener->NotifyTermViewQuit(this, reason);
1846 break;
1848 default:
1849 BView::MessageReceived(msg);
1850 break;
1855 status_t
1856 TermView::GetSupportedSuites(BMessage *message)
1858 BPropertyInfo propInfo(sPropList);
1859 message->AddString("suites", "suite/vnd.naan-termview");
1860 message->AddFlat("messages", &propInfo);
1861 return BView::GetSupportedSuites(message);
1865 void
1866 TermView::ScrollTo(BPoint where)
1868 //debug_printf("TermView::ScrollTo(): %f -> %f\n", fScrollOffset, where.y);
1869 float diff = where.y - fScrollOffset;
1870 if (diff == 0)
1871 return;
1873 float bottom = Bounds().bottom;
1874 int32 oldFirstLine = _LineAt(0);
1875 int32 oldLastLine = _LineAt(bottom);
1876 int32 newFirstLine = _LineAt(diff);
1877 int32 newLastLine = _LineAt(bottom + diff);
1879 fScrollOffset = where.y;
1881 // invalidate the current cursor position before scrolling
1882 _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
1884 // scroll contents
1885 BRect destRect(Frame().OffsetToCopy(Bounds().LeftTop()));
1886 BRect sourceRect(destRect.OffsetByCopy(0, diff));
1887 //debug_printf("CopyBits(((%f, %f) - (%f, %f)) -> (%f, %f) - (%f, %f))\n",
1888 //sourceRect.left, sourceRect.top, sourceRect.right, sourceRect.bottom,
1889 //destRect.left, destRect.top, destRect.right, destRect.bottom);
1890 CopyBits(sourceRect, destRect);
1892 // sync visible text buffer with text buffer
1893 if (newFirstLine != oldFirstLine || newLastLine != oldLastLine) {
1894 if (newFirstLine != oldFirstLine)
1896 //debug_printf("fVisibleTextBuffer->ScrollBy(%ld)\n", newFirstLine - oldFirstLine);
1897 fVisibleTextBuffer->ScrollBy(newFirstLine - oldFirstLine);
1899 TextBufferSyncLocker _(this);
1900 if (diff < 0)
1901 _SynchronizeWithTextBuffer(newFirstLine, oldFirstLine - 1);
1902 else
1903 _SynchronizeWithTextBuffer(oldLastLine + 1, newLastLine);
1908 void
1909 TermView::TargetedByScrollView(BScrollView *scrollView)
1911 BView::TargetedByScrollView(scrollView);
1913 SetScrollBar(scrollView ? scrollView->ScrollBar(B_VERTICAL) : NULL);
1917 BHandler*
1918 TermView::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier,
1919 int32 what, const char* property)
1921 BHandler* target = this;
1922 BPropertyInfo propInfo(sPropList);
1923 if (propInfo.FindMatch(message, index, specifier, what, property) < B_OK) {
1924 target = BView::ResolveSpecifier(message, index, specifier, what,
1925 property);
1928 return target;
1932 void
1933 TermView::_SecondaryMouseButtonDropped(BMessage* msg)
1935 // Launch menu to choose what is to do with the msg data
1936 BPoint point;
1937 if (msg->FindPoint("_drop_point_", &point) != B_OK)
1938 return;
1940 BMessage* insertMessage = new BMessage(*msg);
1941 insertMessage->what = kSecondaryMouseDropAction;
1942 insertMessage->AddInt8("action", kInsert);
1944 BMessage* cdMessage = new BMessage(*msg);
1945 cdMessage->what = kSecondaryMouseDropAction;
1946 cdMessage->AddInt8("action", kChangeDirectory);
1948 BMessage* lnMessage = new BMessage(*msg);
1949 lnMessage->what = kSecondaryMouseDropAction;
1950 lnMessage->AddInt8("action", kLinkFiles);
1952 BMessage* mvMessage = new BMessage(*msg);
1953 mvMessage->what = kSecondaryMouseDropAction;
1954 mvMessage->AddInt8("action", kMoveFiles);
1956 BMessage* cpMessage = new BMessage(*msg);
1957 cpMessage->what = kSecondaryMouseDropAction;
1958 cpMessage->AddInt8("action", kCopyFiles);
1960 BMenuItem* insertItem = new BMenuItem(
1961 B_TRANSLATE("Insert path"), insertMessage);
1962 BMenuItem* cdItem = new BMenuItem(
1963 B_TRANSLATE("Change directory"), cdMessage);
1964 BMenuItem* lnItem = new BMenuItem(
1965 B_TRANSLATE("Create link here"), lnMessage);
1966 BMenuItem* mvItem = new BMenuItem(B_TRANSLATE("Move here"), mvMessage);
1967 BMenuItem* cpItem = new BMenuItem(B_TRANSLATE("Copy here"), cpMessage);
1968 BMenuItem* chItem = new BMenuItem(B_TRANSLATE("Cancel"), NULL);
1970 // if the refs point to different directorys disable the cd menu item
1971 bool differentDirs = false;
1972 BDirectory firstDir;
1973 entry_ref ref;
1974 int i = 0;
1975 while (msg->FindRef("refs", i++, &ref) == B_OK) {
1976 BNode node(&ref);
1977 BEntry entry(&ref);
1978 BDirectory dir;
1979 if (node.IsDirectory())
1980 dir.SetTo(&ref);
1981 else
1982 entry.GetParent(&dir);
1984 if (i == 1) {
1985 node_ref nodeRef;
1986 dir.GetNodeRef(&nodeRef);
1987 firstDir.SetTo(&nodeRef);
1988 } else if (firstDir != dir) {
1989 differentDirs = true;
1990 break;
1993 if (differentDirs)
1994 cdItem->SetEnabled(false);
1996 BPopUpMenu *menu = new BPopUpMenu(
1997 "Secondary mouse button drop menu");
1998 menu->SetAsyncAutoDestruct(true);
1999 menu->AddItem(insertItem);
2000 menu->AddSeparatorItem();
2001 menu->AddItem(cdItem);
2002 menu->AddItem(lnItem);
2003 menu->AddItem(mvItem);
2004 menu->AddItem(cpItem);
2005 menu->AddSeparatorItem();
2006 menu->AddItem(chItem);
2007 menu->SetTargetForItems(this);
2008 menu->Go(point, true, true, true);
2012 void
2013 TermView::_DoSecondaryMouseDropAction(BMessage* msg)
2015 int8 action = -1;
2016 msg->FindInt8("action", &action);
2018 BString outString = "";
2019 BString itemString = "";
2021 switch (action) {
2022 case kInsert:
2023 break;
2024 case kChangeDirectory:
2025 outString = "cd ";
2026 break;
2027 case kLinkFiles:
2028 outString = "ln -s ";
2029 break;
2030 case kMoveFiles:
2031 outString = "mv ";
2032 break;
2033 case kCopyFiles:
2034 outString = "cp ";
2035 break;
2037 default:
2038 return;
2041 bool listContainsDirectory = false;
2042 entry_ref ref;
2043 int32 i = 0;
2044 while (msg->FindRef("refs", i++, &ref) == B_OK) {
2045 BEntry ent(&ref);
2046 BNode node(&ref);
2047 BPath path(&ent);
2048 BString string(path.Path());
2050 if (node.IsDirectory())
2051 listContainsDirectory = true;
2053 if (i > 1)
2054 itemString += " ";
2056 if (action == kChangeDirectory) {
2057 if (!node.IsDirectory()) {
2058 int32 slash = string.FindLast("/");
2059 string.Truncate(slash);
2061 string.CharacterEscape(kShellEscapeCharacters, '\\');
2062 itemString += string;
2063 break;
2065 string.CharacterEscape(kShellEscapeCharacters, '\\');
2066 itemString += string;
2069 if (listContainsDirectory && action == kCopyFiles)
2070 outString += "-R ";
2072 outString += itemString;
2074 if (action == kLinkFiles || action == kMoveFiles || action == kCopyFiles)
2075 outString += " .";
2077 if (action != kInsert)
2078 outString += "\n";
2080 _WritePTY(outString.String(), outString.Length());
2084 //! Gets dropped file full path and display it at cursor position.
2085 void
2086 TermView::_DoFileDrop(entry_ref& ref)
2088 BEntry ent(&ref);
2089 BPath path(&ent);
2090 BString string(path.Path());
2092 string.CharacterEscape(kShellEscapeCharacters, '\\');
2093 _WritePTY(string.String(), string.Length());
2097 /*! Text buffer must already be locked.
2099 void
2100 TermView::_SynchronizeWithTextBuffer(int32 visibleDirtyTop,
2101 int32 visibleDirtyBottom)
2103 TerminalBufferDirtyInfo& info = fTextBuffer->DirtyInfo();
2104 int32 linesScrolled = info.linesScrolled;
2106 //debug_printf("TermView::_SynchronizeWithTextBuffer(): dirty: %ld - %ld, "
2107 //"scrolled: %ld, visible dirty: %ld - %ld\n", info.dirtyTop, info.dirtyBottom,
2108 //info.linesScrolled, visibleDirtyTop, visibleDirtyBottom);
2110 bigtime_t now = system_time();
2111 bigtime_t timeElapsed = now - fLastSyncTime;
2112 if (timeElapsed > 2 * kSyncUpdateGranularity) {
2113 // last sync was ages ago
2114 fLastSyncTime = now;
2115 fScrolledSinceLastSync = linesScrolled;
2118 if (fSyncRunner == NULL) {
2119 // We consider clocked syncing when more than a full screen height has
2120 // been scrolled in less than a sync update period. Once we're
2121 // actively considering it, the same condition will convince us to
2122 // actually do it.
2123 if (fScrolledSinceLastSync + linesScrolled <= fRows) {
2124 // Condition doesn't hold yet. Reset if time is up, or otherwise
2125 // keep counting.
2126 if (timeElapsed > kSyncUpdateGranularity) {
2127 fConsiderClockedSync = false;
2128 fLastSyncTime = now;
2129 fScrolledSinceLastSync = linesScrolled;
2130 } else
2131 fScrolledSinceLastSync += linesScrolled;
2132 } else if (fConsiderClockedSync) {
2133 // We are convinced -- create the sync runner.
2134 fLastSyncTime = now;
2135 fScrolledSinceLastSync = 0;
2137 BMessage message(MSG_TERMINAL_BUFFER_CHANGED);
2138 fSyncRunner = new(std::nothrow) BMessageRunner(BMessenger(this),
2139 &message, kSyncUpdateGranularity);
2140 if (fSyncRunner != NULL && fSyncRunner->InitCheck() == B_OK)
2141 return;
2143 delete fSyncRunner;
2144 fSyncRunner = NULL;
2145 } else {
2146 // Looks interesting so far. Reset the counts and consider clocked
2147 // syncing.
2148 fConsiderClockedSync = true;
2149 fLastSyncTime = now;
2150 fScrolledSinceLastSync = 0;
2152 } else if (timeElapsed < kSyncUpdateGranularity) {
2153 // sync time not passed yet -- keep counting
2154 fScrolledSinceLastSync += linesScrolled;
2155 return;
2158 if (fScrolledSinceLastSync + linesScrolled <= fRows) {
2159 // time's up, but not enough happened
2160 delete fSyncRunner;
2161 fSyncRunner = NULL;
2162 fLastSyncTime = now;
2163 fScrolledSinceLastSync = linesScrolled;
2164 } else {
2165 // Things are still rolling, but the sync time's up.
2166 fLastSyncTime = now;
2167 fScrolledSinceLastSync = 0;
2170 fVisibleTextBufferChanged = true;
2172 // Simple case first -- complete invalidation.
2173 if (info.invalidateAll) {
2174 Invalidate();
2175 _UpdateScrollBarRange();
2176 _Deselect();
2178 fCursor = fTextBuffer->Cursor();
2179 _ActivateCursor(false);
2181 int32 offset = _LineAt(0);
2182 fVisibleTextBuffer->SynchronizeWith(fTextBuffer, offset, offset,
2183 offset + fTextBuffer->Height() + 2);
2185 info.Reset();
2186 return;
2189 BRect bounds = Bounds();
2190 int32 firstVisible = _LineAt(0);
2191 int32 lastVisible = _LineAt(bounds.bottom);
2192 int32 historySize = fTextBuffer->HistorySize();
2194 bool doScroll = false;
2195 if (linesScrolled > 0) {
2196 _UpdateScrollBarRange();
2198 visibleDirtyTop -= linesScrolled;
2199 visibleDirtyBottom -= linesScrolled;
2201 if (firstVisible < 0) {
2202 firstVisible -= linesScrolled;
2203 lastVisible -= linesScrolled;
2205 float scrollOffset;
2206 if (firstVisible < -historySize) {
2207 firstVisible = -historySize;
2208 doScroll = true;
2209 scrollOffset = -historySize * fFontHeight;
2210 // We need to invalidate the lower linesScrolled lines of the
2211 // visible text buffer, since those will be scrolled up and
2212 // need to be replaced. We just use visibleDirty{Top,Bottom}
2213 // for that purpose. Unless invoked from ScrollTo() (i.e.
2214 // user-initiated scrolling) those are unused. In the unlikely
2215 // case that the user is scrolling at the same time we may
2216 // invalidate too many lines, since we have to extend the given
2217 // region.
2218 // Note that in the firstVisible == 0 case the new lines are
2219 // already in the dirty region, so they will be updated anyway.
2220 if (visibleDirtyTop <= visibleDirtyBottom) {
2221 if (lastVisible < visibleDirtyTop)
2222 visibleDirtyTop = lastVisible;
2223 if (visibleDirtyBottom < lastVisible + linesScrolled)
2224 visibleDirtyBottom = lastVisible + linesScrolled;
2225 } else {
2226 visibleDirtyTop = lastVisible + 1;
2227 visibleDirtyBottom = lastVisible + linesScrolled;
2229 } else
2230 scrollOffset = fScrollOffset - linesScrolled * fFontHeight;
2232 _ScrollTo(scrollOffset, false);
2233 } else
2234 doScroll = true;
2236 if (doScroll && lastVisible >= firstVisible
2237 && !(info.IsDirtyRegionValid() && firstVisible >= info.dirtyTop
2238 && lastVisible <= info.dirtyBottom)) {
2239 // scroll manually
2240 float scrollBy = linesScrolled * fFontHeight;
2241 BRect destRect(Frame().OffsetToCopy(B_ORIGIN));
2242 BRect sourceRect(destRect.OffsetByCopy(0, scrollBy));
2244 // invalidate the current cursor position before scrolling
2245 _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
2247 //debug_printf("CopyBits(((%f, %f) - (%f, %f)) -> (%f, %f) - (%f, %f))\n",
2248 //sourceRect.left, sourceRect.top, sourceRect.right, sourceRect.bottom,
2249 //destRect.left, destRect.top, destRect.right, destRect.bottom);
2250 CopyBits(sourceRect, destRect);
2252 fVisibleTextBuffer->ScrollBy(linesScrolled);
2255 // move highlights
2256 for (int32 i = 0; Highlight* highlight = fHighlights.ItemAt(i); i++) {
2257 if (highlight->IsEmpty())
2258 continue;
2260 highlight->ScrollRange(linesScrolled);
2261 if (highlight == &fSelection) {
2262 fInitialSelectionStart.y -= linesScrolled;
2263 fInitialSelectionEnd.y -= linesScrolled;
2266 if (highlight->Start().y < -historySize) {
2267 if (highlight == &fSelection)
2268 _Deselect();
2269 else
2270 _ClearHighlight(highlight);
2275 // invalidate dirty region
2276 if (info.IsDirtyRegionValid()) {
2277 _InvalidateTextRect(0, info.dirtyTop, fTextBuffer->Width() - 1,
2278 info.dirtyBottom);
2280 // clear the selection, if affected
2281 if (!fSelection.IsEmpty()) {
2282 // TODO: We're clearing the selection more often than necessary --
2283 // to avoid that, we'd also need to track the x coordinates of the
2284 // dirty range.
2285 int32 selectionBottom = fSelection.End().x > 0
2286 ? fSelection.End().y : fSelection.End().y - 1;
2287 if (fSelection.Start().y <= info.dirtyBottom
2288 && info.dirtyTop <= selectionBottom) {
2289 _Deselect();
2294 if (visibleDirtyTop <= visibleDirtyBottom)
2295 info.ExtendDirtyRegion(visibleDirtyTop, visibleDirtyBottom);
2297 if (linesScrolled != 0 || info.IsDirtyRegionValid()) {
2298 fVisibleTextBuffer->SynchronizeWith(fTextBuffer, firstVisible,
2299 info.dirtyTop, info.dirtyBottom);
2302 // invalidate cursor, if it changed
2303 TermPos cursor = fTextBuffer->Cursor();
2304 if (fCursor != cursor || linesScrolled != 0) {
2305 // Before we scrolled we did already invalidate the old cursor.
2306 if (!doScroll)
2307 _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
2308 fCursor = cursor;
2309 _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
2310 _ActivateCursor(false);
2313 info.Reset();
2317 void
2318 TermView::_VisibleTextBufferChanged()
2320 if (!fVisibleTextBufferChanged)
2321 return;
2323 fVisibleTextBufferChanged = false;
2324 fActiveState->VisibleTextBufferChanged();
2328 /*! Write strings to PTY device. If encoding system isn't UTF8, change
2329 encoding to UTF8 before writing PTY.
2331 void
2332 TermView::_WritePTY(const char* text, int32 numBytes)
2334 if (fEncoding != M_UTF8) {
2335 while (numBytes > 0) {
2336 char buffer[1024];
2337 int32 bufferSize = sizeof(buffer);
2338 int32 sourceSize = numBytes;
2339 int32 state = 0;
2340 if (convert_to_utf8(fEncoding, text, &sourceSize, buffer,
2341 &bufferSize, &state) != B_OK || bufferSize == 0) {
2342 break;
2345 fShell->Write(buffer, bufferSize);
2346 text += sourceSize;
2347 numBytes -= sourceSize;
2349 } else {
2350 fShell->Write(text, numBytes);
2355 //! Returns the square of the actual pixel distance between both points
2356 float
2357 TermView::_MouseDistanceSinceLastClick(BPoint where)
2359 return (fLastClickPoint.x - where.x) * (fLastClickPoint.x - where.x)
2360 + (fLastClickPoint.y - where.y) * (fLastClickPoint.y - where.y);
2364 void
2365 TermView::_SendMouseEvent(int32 buttons, int32 mode, int32 x, int32 y,
2366 bool motion)
2368 char xtermButtons;
2369 if (buttons == B_PRIMARY_MOUSE_BUTTON)
2370 xtermButtons = 32 + 0;
2371 else if (buttons == B_SECONDARY_MOUSE_BUTTON)
2372 xtermButtons = 32 + 1;
2373 else if (buttons == B_TERTIARY_MOUSE_BUTTON)
2374 xtermButtons = 32 + 2;
2375 else
2376 xtermButtons = 32 + 3;
2378 if (motion)
2379 xtermButtons += 32;
2381 char xtermX = x + 1 + 32;
2382 char xtermY = y + 1 + 32;
2384 char destBuffer[6];
2385 destBuffer[0] = '\033';
2386 destBuffer[1] = '[';
2387 destBuffer[2] = 'M';
2388 destBuffer[3] = xtermButtons;
2389 destBuffer[4] = xtermX;
2390 destBuffer[5] = xtermY;
2391 fShell->Write(destBuffer, 6);
2395 void
2396 TermView::MouseDown(BPoint where)
2398 if (!IsFocus())
2399 MakeFocus();
2401 _UpdateModifiers();
2403 BMessage* currentMessage = Window()->CurrentMessage();
2404 int32 buttons = currentMessage->GetInt32("buttons", 0);
2406 fActiveState->MouseDown(where, buttons, fModifiers);
2408 fMouseButtons = buttons;
2409 fLastClickPoint = where;
2413 void
2414 TermView::MouseMoved(BPoint where, uint32 transit, const BMessage *message)
2416 _UpdateModifiers();
2418 fActiveState->MouseMoved(where, transit, message, fModifiers);
2422 void
2423 TermView::MouseUp(BPoint where)
2425 _UpdateModifiers();
2427 int32 buttons = Window()->CurrentMessage()->GetInt32("buttons", 0);
2429 fActiveState->MouseUp(where, buttons);
2431 fMouseButtons = buttons;
2435 //! Select a range of text.
2436 void
2437 TermView::_Select(TermPos start, TermPos end, bool inclusive,
2438 bool setInitialSelection)
2440 TextBufferSyncLocker _(this);
2442 _SynchronizeWithTextBuffer(0, -1);
2444 if (end < start)
2445 std::swap(start, end);
2447 if (inclusive)
2448 end.x++;
2450 //debug_printf("TermView::_Select(): (%ld, %ld) - (%ld, %ld)\n", start.x,
2451 //start.y, end.x, end.y);
2453 if (start.x < 0)
2454 start.x = 0;
2455 if (end.x >= fColumns)
2456 end.x = fColumns;
2458 TermPos minPos(0, -fTextBuffer->HistorySize());
2459 TermPos maxPos(0, fTextBuffer->Height());
2460 start = restrict_value(start, minPos, maxPos);
2461 end = restrict_value(end, minPos, maxPos);
2463 // if the end is past the end of the line, select the line break, too
2464 if (fTextBuffer->LineLength(end.y) < end.x
2465 && end.y < fTextBuffer->Height()) {
2466 end.y++;
2467 end.x = 0;
2470 if (fTextBuffer->IsFullWidthChar(start.y, start.x)) {
2471 start.x--;
2472 if (start.x < 0)
2473 start.x = 0;
2476 if (fTextBuffer->IsFullWidthChar(end.y, end.x)) {
2477 end.x++;
2478 if (end.x >= fColumns)
2479 end.x = fColumns;
2482 if (!fSelection.IsEmpty())
2483 _InvalidateTextRange(fSelection.Start(), fSelection.End());
2485 fSelection.SetRange(start, end);
2487 if (setInitialSelection) {
2488 fInitialSelectionStart = fSelection.Start();
2489 fInitialSelectionEnd = fSelection.End();
2492 _InvalidateTextRange(fSelection.Start(), fSelection.End());
2496 //! Extend selection (shift + mouse click).
2497 void
2498 TermView::_ExtendSelection(TermPos pos, bool inclusive,
2499 bool useInitialSelection)
2501 if (!useInitialSelection && !_HasSelection())
2502 return;
2504 TermPos start = fSelection.Start();
2505 TermPos end = fSelection.End();
2507 if (useInitialSelection) {
2508 start = fInitialSelectionStart;
2509 end = fInitialSelectionEnd;
2512 if (inclusive) {
2513 if (pos >= start && pos >= end)
2514 pos.x++;
2517 if (pos < start)
2518 _Select(pos, end, false, !useInitialSelection);
2519 else if (pos > end)
2520 _Select(start, pos, false, !useInitialSelection);
2521 else if (useInitialSelection)
2522 _Select(start, end, false, false);
2526 // clear the selection.
2527 void
2528 TermView::_Deselect()
2530 //debug_printf("TermView::_Deselect(): has selection: %d\n", _HasSelection());
2531 if (_ClearHighlight(&fSelection)) {
2532 fInitialSelectionStart.SetTo(0, 0);
2533 fInitialSelectionEnd.SetTo(0, 0);
2538 bool
2539 TermView::_HasSelection() const
2541 return !fSelection.IsEmpty();
2545 void
2546 TermView::_SelectWord(BPoint where, bool extend, bool useInitialSelection)
2548 BAutolock _(fTextBuffer);
2550 TermPos pos = _ConvertToTerminal(where);
2551 TermPos start, end;
2552 if (!fTextBuffer->FindWord(pos, fCharClassifier, true, start, end))
2553 return;
2555 if (extend) {
2556 if (start
2557 < (useInitialSelection
2558 ? fInitialSelectionStart : fSelection.Start())) {
2559 _ExtendSelection(start, false, useInitialSelection);
2560 } else if (end
2561 > (useInitialSelection
2562 ? fInitialSelectionEnd : fSelection.End())) {
2563 _ExtendSelection(end, false, useInitialSelection);
2564 } else if (useInitialSelection)
2565 _Select(start, end, false, false);
2566 } else
2567 _Select(start, end, false, !useInitialSelection);
2571 void
2572 TermView::_SelectLine(BPoint where, bool extend, bool useInitialSelection)
2574 TermPos start = TermPos(0, _ConvertToTerminal(where).y);
2575 TermPos end = TermPos(0, start.y + 1);
2577 if (extend) {
2578 if (start
2579 < (useInitialSelection
2580 ? fInitialSelectionStart : fSelection.Start())) {
2581 _ExtendSelection(start, false, useInitialSelection);
2582 } else if (end
2583 > (useInitialSelection
2584 ? fInitialSelectionEnd : fSelection.End())) {
2585 _ExtendSelection(end, false, useInitialSelection);
2586 } else if (useInitialSelection)
2587 _Select(start, end, false, false);
2588 } else
2589 _Select(start, end, false, !useInitialSelection);
2593 void
2594 TermView::_AddHighlight(Highlight* highlight)
2596 fHighlights.AddItem(highlight);
2598 if (!highlight->IsEmpty())
2599 _InvalidateTextRange(highlight->Start(), highlight->End());
2603 void
2604 TermView::_RemoveHighlight(Highlight* highlight)
2606 if (!highlight->IsEmpty())
2607 _InvalidateTextRange(highlight->Start(), highlight->End());
2609 fHighlights.RemoveItem(highlight);
2613 bool
2614 TermView::_ClearHighlight(Highlight* highlight)
2616 if (highlight->IsEmpty())
2617 return false;
2619 _InvalidateTextRange(highlight->Start(), highlight->End());
2621 highlight->SetRange(TermPos(0, 0), TermPos(0, 0));
2622 return true;
2626 TermView::Highlight*
2627 TermView::_CheckHighlightRegion(const TermPos &pos) const
2629 for (int32 i = 0; Highlight* highlight = fHighlights.ItemAt(i); i++) {
2630 if (highlight->RangeContains(pos))
2631 return highlight;
2634 return NULL;
2638 TermView::Highlight*
2639 TermView::_CheckHighlightRegion(int32 row, int32 firstColumn,
2640 int32& lastColumn) const
2642 Highlight* nextHighlight = NULL;
2644 for (int32 i = 0; Highlight* highlight = fHighlights.ItemAt(i); i++) {
2645 if (highlight->IsEmpty())
2646 continue;
2648 if (row == highlight->Start().y && firstColumn < highlight->Start().x
2649 && lastColumn >= highlight->Start().x) {
2650 // region starts before the highlight, but intersects with it
2651 if (nextHighlight == NULL
2652 || highlight->Start().x < nextHighlight->Start().x) {
2653 nextHighlight = highlight;
2655 continue;
2658 if (row == highlight->End().y && firstColumn < highlight->End().x
2659 && lastColumn >= highlight->End().x) {
2660 // region starts in the highlight, but exceeds the end
2661 lastColumn = highlight->End().x - 1;
2662 return highlight;
2665 TermPos pos(firstColumn, row);
2666 if (highlight->RangeContains(pos))
2667 return highlight;
2670 if (nextHighlight != NULL)
2671 lastColumn = nextHighlight->Start().x - 1;
2672 return NULL;
2676 void
2677 TermView::GetFrameSize(float *width, float *height)
2679 int32 historySize;
2681 BAutolock _(fTextBuffer);
2682 historySize = fTextBuffer->HistorySize();
2685 if (width != NULL)
2686 *width = fColumns * fFontWidth;
2688 if (height != NULL)
2689 *height = (fRows + historySize) * fFontHeight;
2693 // Find a string, and select it if found
2694 bool
2695 TermView::Find(const BString &str, bool forwardSearch, bool matchCase,
2696 bool matchWord)
2698 TextBufferSyncLocker _(this);
2699 _SynchronizeWithTextBuffer(0, -1);
2701 TermPos start;
2702 if (_HasSelection()) {
2703 if (forwardSearch)
2704 start = fSelection.End();
2705 else
2706 start = fSelection.Start();
2707 } else {
2708 // search from the very beginning/end
2709 if (forwardSearch)
2710 start = TermPos(0, -fTextBuffer->HistorySize());
2711 else
2712 start = TermPos(0, fTextBuffer->Height());
2715 TermPos matchStart, matchEnd;
2716 if (!fTextBuffer->Find(str.String(), start, forwardSearch, matchCase,
2717 matchWord, matchStart, matchEnd)) {
2718 return false;
2721 _Select(matchStart, matchEnd, false, true);
2722 _ScrollToRange(fSelection.Start(), fSelection.End());
2724 return true;
2728 //! Get the selected text and copy to str
2729 void
2730 TermView::GetSelection(BString &str)
2732 str.SetTo("");
2733 BAutolock _(fTextBuffer);
2734 fTextBuffer->GetStringFromRegion(str, fSelection.Start(), fSelection.End());
2738 bool
2739 TermView::CheckShellGone() const
2741 if (!fShell)
2742 return false;
2744 // check, if the shell does still live
2745 pid_t pid = fShell->ProcessID();
2746 team_info info;
2747 return get_team_info(pid, &info) == B_BAD_TEAM_ID;
2751 void
2752 TermView::InitiateDrag()
2754 BAutolock _(fTextBuffer);
2756 BString copyStr("");
2757 fTextBuffer->GetStringFromRegion(copyStr, fSelection.Start(),
2758 fSelection.End());
2760 BMessage message(B_MIME_DATA);
2761 message.AddData("text/plain", B_MIME_TYPE, copyStr.String(),
2762 copyStr.Length());
2764 BPoint start = _ConvertFromTerminal(fSelection.Start());
2765 BPoint end = _ConvertFromTerminal(fSelection.End());
2767 BRect rect;
2768 if (fSelection.Start().y == fSelection.End().y)
2769 rect.Set(start.x, start.y, end.x + fFontWidth, end.y + fFontHeight);
2770 else
2771 rect.Set(0, start.y, fColumns * fFontWidth, end.y + fFontHeight);
2773 rect = rect & Bounds();
2775 DragMessage(&message, rect);
2779 void
2780 TermView::_ScrollTo(float y, bool scrollGfx)
2782 if (!scrollGfx)
2783 fScrollOffset = y;
2785 if (fScrollBar != NULL)
2786 fScrollBar->SetValue(y);
2787 else
2788 ScrollTo(BPoint(0, y));
2792 void
2793 TermView::_ScrollToRange(TermPos start, TermPos end)
2795 if (start > end)
2796 std::swap(start, end);
2798 float startY = _LineOffset(start.y);
2799 float endY = _LineOffset(end.y) + fFontHeight - 1;
2800 float height = Bounds().Height();
2802 if (endY - startY > height) {
2803 // The range is greater than the height. Scroll to the closest border.
2805 // already as good as it gets?
2806 if (startY <= 0 && endY >= height)
2807 return;
2809 if (startY > 0) {
2810 // scroll down to align the start with the top of the view
2811 _ScrollTo(fScrollOffset + startY, true);
2812 } else {
2813 // scroll up to align the end with the bottom of the view
2814 _ScrollTo(fScrollOffset + endY - height, true);
2816 } else {
2817 // The range is smaller than the height.
2819 // already visible?
2820 if (startY >= 0 && endY <= height)
2821 return;
2823 if (startY < 0) {
2824 // scroll up to make the start visible
2825 _ScrollTo(fScrollOffset + startY, true);
2826 } else {
2827 // scroll down to make the end visible
2828 _ScrollTo(fScrollOffset + endY - height, true);
2834 void
2835 TermView::DisableResizeView(int32 disableCount)
2837 fResizeViewDisableCount += disableCount;
2841 void
2842 TermView::_DrawInlineMethodString()
2844 if (!fInline || !fInline->String())
2845 return;
2847 const int32 numChars = BString(fInline->String()).CountChars();
2849 BPoint startPoint = _ConvertFromTerminal(fCursor);
2850 BPoint endPoint = startPoint;
2851 endPoint.x += fFontWidth * numChars;
2852 endPoint.y += fFontHeight + 1;
2854 BRect eraseRect(startPoint, endPoint);
2856 PushState();
2857 SetHighColor(fTextForeColor);
2858 FillRect(eraseRect);
2859 PopState();
2861 BPoint loc = _ConvertFromTerminal(fCursor);
2862 loc.y += fFontHeight;
2863 SetFont(&fHalfFont);
2864 SetHighColor(fTextBackColor);
2865 SetLowColor(fTextForeColor);
2866 DrawString(fInline->String(), loc);
2870 void
2871 TermView::_HandleInputMethodChanged(BMessage *message)
2873 const char *string = NULL;
2874 if (message->FindString("be:string", &string) < B_OK || string == NULL)
2875 return;
2877 _ActivateCursor(false);
2879 if (IsFocus())
2880 be_app->ObscureCursor();
2882 // If we find the "be:confirmed" boolean (and the boolean is true),
2883 // it means it's over for now, so the current InlineInput object
2884 // should become inactive. We will probably receive a
2885 // B_INPUT_METHOD_STOPPED message after this one.
2886 bool confirmed;
2887 if (message->FindBool("be:confirmed", &confirmed) != B_OK)
2888 confirmed = false;
2890 fInline->SetString("");
2892 Invalidate();
2893 // TODO: Debug only
2894 snooze(100000);
2896 fInline->SetString(string);
2897 fInline->ResetClauses();
2899 if (!confirmed && !fInline->IsActive())
2900 fInline->SetActive(true);
2902 // Get the clauses, and pass them to the InlineInput object
2903 // TODO: Find out if what we did it's ok, currently we don't consider
2904 // clauses at all, while the bebook says we should; though the visual
2905 // effect we obtained seems correct. Weird.
2906 int32 clauseCount = 0;
2907 int32 clauseStart;
2908 int32 clauseEnd;
2909 while (message->FindInt32("be:clause_start", clauseCount, &clauseStart)
2910 == B_OK
2911 && message->FindInt32("be:clause_end", clauseCount, &clauseEnd)
2912 == B_OK) {
2913 if (!fInline->AddClause(clauseStart, clauseEnd))
2914 break;
2915 clauseCount++;
2918 if (confirmed) {
2919 fInline->SetString("");
2920 _ActivateCursor(true);
2922 // now we need to feed ourselves the individual characters as if the
2923 // user would have pressed them now - this lets KeyDown() pick out all
2924 // the special characters like B_BACKSPACE, cursor keys and the like:
2925 const char* currPos = string;
2926 const char* prevPos = currPos;
2927 while (*currPos != '\0') {
2928 if ((*currPos & 0xC0) == 0xC0) {
2929 // found the start of an UTF-8 char, we collect while it lasts
2930 ++currPos;
2931 while ((*currPos & 0xC0) == 0x80)
2932 ++currPos;
2933 } else if ((*currPos & 0xC0) == 0x80) {
2934 // illegal: character starts with utf-8 intermediate byte, skip it
2935 prevPos = ++currPos;
2936 } else {
2937 // single byte character/code, just feed that
2938 ++currPos;
2940 KeyDown(prevPos, currPos - prevPos);
2941 prevPos = currPos;
2944 Invalidate();
2945 } else {
2946 // temporarily show transient state of inline input
2947 int32 selectionStart = 0;
2948 int32 selectionEnd = 0;
2949 message->FindInt32("be:selection", 0, &selectionStart);
2950 message->FindInt32("be:selection", 1, &selectionEnd);
2952 fInline->SetSelectionOffset(selectionStart);
2953 fInline->SetSelectionLength(selectionEnd - selectionStart);
2954 Invalidate();
2959 void
2960 TermView::_HandleInputMethodLocationRequest()
2962 BMessage message(B_INPUT_METHOD_EVENT);
2963 message.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST);
2965 BString string(fInline->String());
2967 const int32 &limit = string.CountChars();
2968 BPoint where = _ConvertFromTerminal(fCursor);
2969 where.y += fFontHeight;
2971 for (int32 i = 0; i < limit; i++) {
2972 // Add the location of the UTF8 characters
2974 where.x += fFontWidth;
2975 ConvertToScreen(&where);
2977 message.AddPoint("be:location_reply", where);
2978 message.AddFloat("be:height_reply", fFontHeight);
2981 fInline->Method()->SendMessage(&message);
2985 void
2986 TermView::_CancelInputMethod()
2988 if (!fInline)
2989 return;
2991 InlineInput *inlineInput = fInline;
2992 fInline = NULL;
2994 if (inlineInput->IsActive() && Window()) {
2995 Invalidate();
2997 BMessage message(B_INPUT_METHOD_EVENT);
2998 message.AddInt32("be:opcode", B_INPUT_METHOD_STOPPED);
2999 inlineInput->Method()->SendMessage(&message);
3002 delete inlineInput;
3006 void
3007 TermView::_UpdateModifiers()
3009 // TODO: This method is a general work-around for missing or out-of-order
3010 // B_MODIFIERS_CHANGED messages. This should really be fixed where it is
3011 // broken (app server?).
3012 int32 oldModifiers = fModifiers;
3013 fModifiers = modifiers();
3014 if (fModifiers != oldModifiers && fActiveState != NULL)
3015 fActiveState->ModifiersChanged(oldModifiers, fModifiers);
3019 void
3020 TermView::_NextState(State* state)
3022 if (state != fActiveState) {
3023 if (fActiveState != NULL)
3024 fActiveState->Exited();
3025 fActiveState = state;
3026 fActiveState->Entered();
3031 // #pragma mark - Listener
3034 TermView::Listener::~Listener()
3039 void
3040 TermView::Listener::NotifyTermViewQuit(TermView* view, int32 reason)
3045 void
3046 TermView::Listener::SetTermViewTitle(TermView* view, const char* title)
3051 void
3052 TermView::Listener::PreviousTermView(TermView* view)
3057 void
3058 TermView::Listener::NextTermView(TermView* view)
3063 // #pragma mark -
3066 #ifdef USE_DEBUG_SNAPSHOTS
3068 void
3069 TermView::MakeDebugSnapshots()
3071 BAutolock _(fTextBuffer);
3072 time_t timeStamp = time(NULL);
3073 fTextBuffer->MakeLinesSnapshots(timeStamp, ".TextBuffer.dump");
3074 fVisibleTextBuffer->MakeLinesSnapshots(timeStamp, ".VisualTextBuffer.dump");
3078 void
3079 TermView::StartStopDebugCapture()
3081 BAutolock _(fTextBuffer);
3082 fTextBuffer->StartStopDebugCapture();
3085 #endif