/* * Copyright 2001-2023, Haiku, Inc. All rights reserved. * Copyright 2003-2004 Kian Duffy, myob@users.sourceforge.net * Parts Copyright 1998-1999 Kazuho Okui and Takashi Murai. * All rights reserved. Distributed under the terms of the MIT license. * * Authors: * Stefano Ceccherini, stefano.ceccherini@gmail.com * Kian Duffy, myob@users.sourceforge.net * Y.Hayakawa, hida@sawada.riec.tohoku.ac.jp * Jonathan Schleifer, js@webkeks.org * Simon South, simon@simonsouth.net * Ingo Weinhold, ingo_weinhold@gmx.de * Clemens Zeidler, haiku@Clemens-Zeidler.de * Siarzhuk Zharski, zharik@gmx.li */ #include "TermView.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ActiveProcessInfo.h" #include "Colors.h" #include "InlineInput.h" #include "PrefHandler.h" #include "Shell.h" #include "ShellParameters.h" #include "TermApp.h" #include "TermConst.h" #include "TerminalBuffer.h" #include "TerminalCharClassifier.h" #include "TermViewStates.h" #include "VTkeymap.h" #define ROWS_DEFAULT 25 #define COLUMNS_DEFAULT 80 #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "Terminal TermView" static property_info sPropList[] = { { "encoding", {B_GET_PROPERTY, 0}, {B_DIRECT_SPECIFIER, 0}, "get terminal encoding"}, { "encoding", {B_SET_PROPERTY, 0}, {B_DIRECT_SPECIFIER, 0}, "set terminal encoding"}, { "tty", {B_GET_PROPERTY, 0}, {B_DIRECT_SPECIFIER, 0}, "get tty name."}, { 0 } }; static const uint32 kUpdateSigWinch = 'Rwin'; static const uint32 kBlinkCursor = 'BlCr'; static const bigtime_t kSyncUpdateGranularity = 100000; // 0.1 s static const int32 kCursorBlinkIntervals = 3; static const int32 kCursorVisibleIntervals = 2; static const bigtime_t kCursorBlinkInterval = 500000; static const rgb_color kBlackColor = { 0, 0, 0, 255 }; static const rgb_color kWhiteColor = { 255, 255, 255, 255 }; // secondary mouse button drop const int32 kSecondaryMouseDropAction = 'SMDA'; enum { kInsert, kChangeDirectory, kLinkFiles, kMoveFiles, kCopyFiles }; template static inline Type restrict_value(const Type& value, const Type& min, const Type& max) { return value < min ? min : (value > max ? max : value); } template static inline Type saturated_add(Type a, Type b) { const Type max = (Type)(-1); return (max - a >= b ? a + b : max); } // #pragma mark - TextBufferSyncLocker class TermView::TextBufferSyncLocker { public: TextBufferSyncLocker(TermView* view) : fView(view) { fView->fTextBuffer->Lock(); } ~TextBufferSyncLocker() { fView->fTextBuffer->Unlock(); if (fView->fVisibleTextBufferChanged) fView->_VisibleTextBufferChanged(); } private: TermView* fView; }; // #pragma mark - TermView TermView::TermView(BRect frame, const ShellParameters& shellParameters, int32 historySize) : BView(frame, "termview", B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE), fListener(NULL), fColumns(COLUMNS_DEFAULT), fRows(ROWS_DEFAULT), fEncoding(M_UTF8), fActive(false), fScrBufSize(historySize), fReportX10MouseEvent(false), fReportNormalMouseEvent(false), fReportButtonMouseEvent(false), fReportAnyMouseEvent(false), fEnableExtendedMouseCoordinates(false) { status_t status = _InitObject(shellParameters); if (status != B_OK) throw status; SetTermSize(frame); } TermView::TermView(int rows, int columns, const ShellParameters& shellParameters, int32 historySize) : BView(BRect(0, 0, 0, 0), "termview", B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE), fListener(NULL), fColumns(columns), fRows(rows), fEncoding(M_UTF8), fActive(false), fScrBufSize(historySize), fReportX10MouseEvent(false), fReportNormalMouseEvent(false), fReportButtonMouseEvent(false), fReportAnyMouseEvent(false), fEnableExtendedMouseCoordinates(false) { status_t status = _InitObject(shellParameters); if (status != B_OK) throw status; ResizeToPreferred(); // TODO: Don't show the dragger, since replicant capabilities // don't work very well ATM. /* BRect rect(0, 0, 16, 16); rect.OffsetTo(Bounds().right - rect.Width(), Bounds().bottom - rect.Height()); SetFlags(Flags() | B_DRAW_ON_CHILDREN | B_FOLLOW_ALL); AddChild(new BDragger(rect, this, B_FOLLOW_RIGHT|B_FOLLOW_BOTTOM, B_WILL_DRAW));*/ } TermView::TermView(BMessage* archive) : BView(archive), fListener(NULL), fColumns(COLUMNS_DEFAULT), fRows(ROWS_DEFAULT), fEncoding(M_UTF8), fActive(false), fScrBufSize(1000), fReportX10MouseEvent(false), fReportNormalMouseEvent(false), fReportButtonMouseEvent(false), fReportAnyMouseEvent(false), fEnableExtendedMouseCoordinates(false) { BRect frame = Bounds(); if (archive->FindInt32("encoding", (int32*)&fEncoding) < B_OK) fEncoding = M_UTF8; if (archive->FindInt32("columns", (int32*)&fColumns) < B_OK) fColumns = COLUMNS_DEFAULT; if (archive->FindInt32("rows", (int32*)&fRows) < B_OK) fRows = ROWS_DEFAULT; int32 argc = 0; if (archive->HasInt32("argc")) archive->FindInt32("argc", &argc); const char **argv = new const char*[argc]; for (int32 i = 0; i < argc; i++) { archive->FindString("argv", i, (const char**)&argv[i]); } // TODO: Retrieve colors, history size, etc. from archive status_t status = _InitObject(ShellParameters(argc, argv)); if (status != B_OK) throw status; bool useRect = false; if ((archive->FindBool("use_rect", &useRect) == B_OK) && useRect) SetTermSize(frame); delete[] argv; } /*! Initializes the object for further use. The members fRows, fColumns, fEncoding, and fScrBufSize must already be initialized; they are not touched by this method. */ status_t TermView::_InitObject(const ShellParameters& shellParameters) { SetFlags(Flags() | B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE/* | B_INPUT_METHOD_AWARE*/); fShell = NULL; fWinchRunner = NULL; fCursorBlinkRunner = NULL; fAutoScrollRunner = NULL; fResizeRunner = NULL; fResizeView = NULL; fCharClassifier = NULL; fFontWidth = 0; fFontHeight = 0; fFontAscent = 0; fEmulateBold = false; fAllowBold = true; fFrameResized = false; fResizeViewDisableCount = 0; fLastActivityTime = 0; fCursorState = 0; fCursorStyle = BLOCK_CURSOR; fCursorBlinking = true; fCursorHidden = false; fCursor = TermPos(0, 0); fTextBuffer = NULL; fVisibleTextBuffer = NULL; fVisibleTextBufferChanged = false; fScrollBar = NULL; fInline = NULL; fSelectForeColor = kWhiteColor; fSelectBackColor = kBlackColor; fScrollOffset = 0; fLastSyncTime = 0; fScrolledSinceLastSync = 0; fSyncRunner = NULL; fConsiderClockedSync = false; fSelection.SetHighlighter(this); fSelection.SetRange(TermPos(0, 0), TermPos(0, 0)); fPrevPos = TermPos(-1, - 1); fKeymap = NULL; fKeymapChars = NULL; fUseOptionAsMetaKey = false; fInterpretMetaKey = true; fMetaKeySendsEscape = true; fUseBracketedPaste = false; fReportX10MouseEvent = false; fReportNormalMouseEvent = false; fReportButtonMouseEvent = false; fReportAnyMouseEvent = false; fEnableExtendedMouseCoordinates = false; fMouseClipboard = be_clipboard; fDefaultState = new(std::nothrow) DefaultState(this); fSelectState = new(std::nothrow) SelectState(this); fHyperLinkState = new(std::nothrow) HyperLinkState(this); fHyperLinkMenuState = new(std::nothrow) HyperLinkMenuState(this); fActiveState = NULL; fTextBuffer = new(std::nothrow) TerminalBuffer; if (fTextBuffer == NULL) return B_NO_MEMORY; fVisibleTextBuffer = new(std::nothrow) BasicTerminalBuffer; if (fVisibleTextBuffer == NULL) return B_NO_MEMORY; // TODO: Make the special word chars user-settable! fCharClassifier = new(std::nothrow) DefaultCharClassifier( kDefaultAdditionalWordCharacters); if (fCharClassifier == NULL) return B_NO_MEMORY; status_t error = fTextBuffer->Init(fColumns, fRows, fScrBufSize); if (error != B_OK) return error; fTextBuffer->SetEncoding(fEncoding); error = fVisibleTextBuffer->Init(fColumns, fRows + 2, 0); if (error != B_OK) return error; fShell = new (std::nothrow) Shell(); if (fShell == NULL) return B_NO_MEMORY; SetTermFont(be_fixed_font); // set the shell parameters' encoding ShellParameters modifiedShellParameters(shellParameters); modifiedShellParameters.SetEncoding(fEncoding); error = fShell->Open(fRows, fColumns, modifiedShellParameters); if (error < B_OK) return error; error = _AttachShell(fShell); if (error < B_OK) return error; fHighlights.AddItem(&fSelection); if (fDefaultState == NULL || fSelectState == NULL || fHyperLinkState == NULL || fHyperLinkMenuState == NULL) { return B_NO_MEMORY; } SetLowColor(fTextBackColor); SetViewColor(B_TRANSPARENT_32_BIT); _NextState(fDefaultState); return B_OK; } TermView::~TermView() { Shell* shell = fShell; // _DetachShell sets fShell to NULL _DetachShell(); delete fDefaultState; delete fSelectState; delete fHyperLinkState; delete fHyperLinkMenuState; delete fSyncRunner; delete fAutoScrollRunner; delete fCharClassifier; delete fVisibleTextBuffer; delete fTextBuffer; delete shell; } bool TermView::IsShellBusy() const { return fShell != NULL && fShell->HasActiveProcesses(); } bool TermView::GetActiveProcessInfo(ActiveProcessInfo& _info) const { if (fShell == NULL) { _info.Unset(); return false; } return fShell->GetActiveProcessInfo(_info); } bool TermView::GetShellInfo(ShellInfo& _info) const { if (fShell == NULL) { _info = ShellInfo(); return false; } _info = fShell->Info(); return true; } /* static */ BArchivable * TermView::Instantiate(BMessage* data) { if (validate_instantiation(data, "TermView")) { TermView *view = new (std::nothrow) TermView(data); return view; } return NULL; } status_t TermView::Archive(BMessage* data, bool deep) const { status_t status = BView::Archive(data, deep); if (status == B_OK) status = data->AddString("add_on", TERM_SIGNATURE); if (status == B_OK) status = data->AddInt32("encoding", (int32)fEncoding); if (status == B_OK) status = data->AddInt32("columns", (int32)fColumns); if (status == B_OK) status = data->AddInt32("rows", (int32)fRows); if (data->ReplaceString("class", "TermView") != B_OK) data->AddString("class", "TermView"); return status; } rgb_color TermView::ForegroundColor() { return fSelectForeColor; } rgb_color TermView::BackgroundColor() { return fSelectBackColor; } inline int32 TermView::_LineAt(float y) { int32 location = int32(y + fScrollOffset); // Make sure negative offsets are rounded towards the lower neighbor, too. if (location < 0) location -= fFontHeight - 1; return location / fFontHeight; } inline float TermView::_LineOffset(int32 index) { return index * fFontHeight - fScrollOffset; } // convert view coordinates to terminal text buffer position TermPos TermView::_ConvertToTerminal(const BPoint &p) { return TermPos(p.x >= 0 ? (int32)p.x / fFontWidth : -1, _LineAt(p.y)); } // convert terminal text buffer position to view coordinates inline BPoint TermView::_ConvertFromTerminal(const TermPos &pos) { return BPoint(fFontWidth * pos.x, _LineOffset(pos.y)); } inline void TermView::_InvalidateTextRect(int32 x1, int32 y1, int32 x2, int32 y2) { // assume the worst case with full-width characters - invalidate 2 cells BRect rect(x1 * fFontWidth, _LineOffset(y1), (x2 + 1) * fFontWidth * 2 - 1, _LineOffset(y2 + 1) - 1); //debug_printf("Invalidate((%f, %f) - (%f, %f))\n", rect.left, rect.top, //rect.right, rect.bottom); Invalidate(rect); } void TermView::GetPreferredSize(float *width, float *height) { if (width) *width = fColumns * fFontWidth - 1; if (height) *height = fRows * fFontHeight - 1; } const char * TermView::TerminalName() const { if (fShell == NULL) return NULL; return fShell->TTYName(); } //! Get width and height for terminal font void TermView::GetFontSize(float* _width, float* _height) { *_width = fFontWidth; *_height = fFontHeight; } int TermView::Rows() const { return fRows; } int TermView::Columns() const { return fColumns; } //! Set number of rows and columns in terminal BRect TermView::SetTermSize(int rows, int columns, bool notifyShell) { // if nothing changed, don't do anything if (rows == fRows && columns == fColumns) return BRect(0, 0, fColumns * fFontWidth, fRows * fFontHeight); //debug_printf("TermView::SetTermSize(%d, %d)\n", rows, columns); if (rows > 0) fRows = rows; if (columns > 0) fColumns = columns; // To keep things simple, get rid of the selection first. _Deselect(); { BAutolock _(fTextBuffer); if (fTextBuffer->ResizeTo(columns, rows) != B_OK || fVisibleTextBuffer->ResizeTo(columns, rows + 2, 0) != B_OK) { return Bounds(); } } //debug_printf("Invalidate()\n"); Invalidate(); if (fScrollBar != NULL) { _UpdateScrollBarRange(); fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows); } BRect rect(0, 0, fColumns * fFontWidth, fRows * fFontHeight); // synchronize the visible text buffer { TextBufferSyncLocker _(this); _SynchronizeWithTextBuffer(0, -1); int32 offset = _LineAt(0); fVisibleTextBuffer->SynchronizeWith(fTextBuffer, offset, offset, offset + rows + 2); fVisibleTextBufferChanged = true; } if (notifyShell) fFrameResized = true; return rect; } void TermView::SetTermSize(BRect rect, bool notifyShell) { int rows; int columns; GetTermSizeFromRect(rect, &rows, &columns); SetTermSize(rows, columns, notifyShell); } void TermView::GetTermSizeFromRect(const BRect &rect, int *_rows, int *_columns) { int columns = int((rect.IntegerWidth() + 1) / fFontWidth); int rows = int((rect.IntegerHeight() + 1) / fFontHeight); if (_rows) *_rows = rows; if (_columns) *_columns = columns; } void TermView::SetTextColor(rgb_color fore, rgb_color back) { fTextBackColor = back; fTextForeColor = fore; SetLowColor(fTextBackColor); } void TermView::SetCursorColor(rgb_color fore, rgb_color back) { fCursorForeColor = fore; fCursorBackColor = back; } void TermView::SetSelectColor(rgb_color fore, rgb_color back) { fSelectForeColor = fore; fSelectBackColor = back; } void TermView::SetTermColor(uint index, rgb_color color, bool dynamic) { if (!dynamic) { if (index < kTermColorCount) fTextBuffer->SetPaletteColor(index, color); return; } switch (index) { case 10: fTextForeColor = color; break; case 11: fTextBackColor = color; SetLowColor(fTextBackColor); break; case 12: fCursorBackColor = color; break; case 110: fTextForeColor = PrefHandler::Default()->getRGB( PREF_TEXT_FORE_COLOR); break; case 111: fTextBackColor = PrefHandler::Default()->getRGB( PREF_TEXT_BACK_COLOR); SetLowColor(fTextBackColor); break; case 112: fCursorBackColor = PrefHandler::Default()->getRGB( PREF_CURSOR_BACK_COLOR); break; default: break; } } status_t TermView::GetTermColor(uint index, rgb_color* color) { if (color == NULL) return B_BAD_VALUE; switch (index) { case 10: *color = fTextForeColor; break; case 11: *color = fTextBackColor; break; case 12: *color = fCursorBackColor; break; default: return B_BAD_VALUE; break; } return B_OK; } int TermView::Encoding() const { return fEncoding; } void TermView::SetEncoding(int encoding) { fEncoding = encoding; if (fShell != NULL) fShell->SetEncoding(fEncoding); BAutolock _(fTextBuffer); fTextBuffer->SetEncoding(fEncoding); } void TermView::SetKeymap(const key_map* keymap, const char* chars) { fKeymap = keymap; fKeymapChars = chars; fKeymapTableForModifiers.Put(B_SHIFT_KEY, &fKeymap->shift_map); fKeymapTableForModifiers.Put(B_CAPS_LOCK, &fKeymap->caps_map); fKeymapTableForModifiers.Put(B_CAPS_LOCK | B_SHIFT_KEY, &fKeymap->caps_shift_map); fKeymapTableForModifiers.Put(B_CONTROL_KEY, &fKeymap->control_map); } void TermView::SetUseOptionAsMetaKey(bool enable) { fUseOptionAsMetaKey = enable && fKeymap != NULL && fKeymapChars != NULL; } void TermView::SetMouseClipboard(BClipboard *clipboard) { fMouseClipboard = clipboard; } void TermView::GetTermFont(BFont *font) const { if (font != NULL) *font = fHalfFont; } //! Sets font for terminal void TermView::SetTermFont(const BFont *font) { float halfWidth = 0; fHalfFont = font; fBoldFont = font; uint16 face = fBoldFont.Face(); fBoldFont.SetFace(B_BOLD_FACE | (face & ~B_REGULAR_FACE)); fHalfFont.SetSpacing(B_FIXED_SPACING); // calculate half font's max width // Not Bounding, check only A-Z (For case of fHalfFont is KanjiFont.) for (int c = 0x20; c <= 0x7e; c++) { char buf[4]; sprintf(buf, "%c", c); float tmpWidth = fHalfFont.StringWidth(buf); if (tmpWidth > halfWidth) halfWidth = tmpWidth; } fFontWidth = halfWidth; font_height hh; fHalfFont.GetHeight(&hh); int font_ascent = (int)hh.ascent; int font_descent =(int)hh.descent; int font_leading =(int)hh.leading; if (font_leading == 0) font_leading = 1; fFontAscent = font_ascent; fFontHeight = font_ascent + font_descent + font_leading + 1; fCursorStyle = PrefHandler::Default() == NULL ? BLOCK_CURSOR : PrefHandler::Default()->getCursor(PREF_CURSOR_STYLE); bool blinking = PrefHandler::Default()->getBool(PREF_BLINK_CURSOR); SwitchCursorBlinking(blinking); fEmulateBold = PrefHandler::Default() == NULL ? false : PrefHandler::Default()->getBool(PREF_EMULATE_BOLD); fAllowBold = PrefHandler::Default() == NULL ? false : PrefHandler::Default()->getBool(PREF_ALLOW_BOLD); _ScrollTo(0, false); if (fScrollBar != NULL) fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows); } void TermView::SetScrollBar(BScrollBar *scrollBar) { fScrollBar = scrollBar; if (fScrollBar != NULL) fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows); } void TermView::SwitchCursorBlinking(bool blinkingOn) { fCursorBlinking = blinkingOn; if (blinkingOn) { if (fCursorBlinkRunner == NULL) { BMessage blinkMessage(kBlinkCursor); fCursorBlinkRunner = new (std::nothrow) BMessageRunner( BMessenger(this), &blinkMessage, kCursorBlinkInterval); } } else { // make sure the cursor becomes visible fCursorState = 0; _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y); delete fCursorBlinkRunner; fCursorBlinkRunner = NULL; } } void TermView::Copy(BClipboard *clipboard) { BAutolock _(fTextBuffer); if (!_HasSelection()) return; BString copyStr; fTextBuffer->GetStringFromRegion(copyStr, fSelection.Start(), fSelection.End()); if (clipboard->Lock()) { BMessage *clipMsg = NULL; clipboard->Clear(); if ((clipMsg = clipboard->Data()) != NULL) { clipMsg->AddData("text/plain", B_MIME_TYPE, copyStr.String(), copyStr.Length()); clipboard->Commit(); } clipboard->Unlock(); } } void TermView::Paste(BClipboard *clipboard) { if (clipboard->Lock()) { BMessage *clipMsg = clipboard->Data(); const char* text; ssize_t numBytes; if (clipMsg->FindData("text/plain", B_MIME_TYPE, (const void**)&text, &numBytes) == B_OK ) { if (fUseBracketedPaste) fShell->Write(BEGIN_BRACKETED_PASTE_CODE, strlen(BEGIN_BRACKETED_PASTE_CODE)); _WritePTY(text, numBytes); if (fUseBracketedPaste) fShell->Write(END_BRACKETED_PASTE_CODE, strlen(END_BRACKETED_PASTE_CODE)); } clipboard->Unlock(); _ScrollTo(0, true); } } void TermView::SelectAll() { BAutolock _(fTextBuffer); _Select(TermPos(0, -fTextBuffer->HistorySize()), TermPos(0, fTextBuffer->Height()), false, true); } void TermView::Clear() { _Deselect(); { BAutolock _(fTextBuffer); fTextBuffer->Clear(true); } fVisibleTextBuffer->Clear(true); //debug_printf("Invalidate()\n"); Invalidate(); _ScrollTo(0, false); if (fScrollBar) { fScrollBar->SetRange(0, 0); fScrollBar->SetProportion(1); } } //! Draw region void TermView::_InvalidateTextRange(TermPos start, TermPos end) { if (end < start) std::swap(start, end); if (start.y == end.y) { _InvalidateTextRect(start.x, start.y, end.x, end.y); } else { _InvalidateTextRect(start.x, start.y, fColumns, start.y); if (end.y - start.y > 0) _InvalidateTextRect(0, start.y + 1, fColumns, end.y - 1); _InvalidateTextRect(0, end.y, end.x, end.y); } } status_t TermView::_AttachShell(Shell *shell) { if (shell == NULL) return B_BAD_VALUE; fShell = shell; return fShell->AttachBuffer(TextBuffer()); } void TermView::_DetachShell() { fShell->DetachBuffer(); fShell = NULL; } void TermView::_Activate() { fActive = true; bool blink = PrefHandler::Default()->getBool(PREF_BLINK_CURSOR); SwitchCursorBlinking(blink); } void TermView::_Deactivate() { // make sure the cursor becomes visible fCursorState = 0; _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y); SwitchCursorBlinking(false); fActive = false; } //! Draw part of a line in the given view. void TermView::_DrawLinePart(float x1, float y1, Attributes attr, char *buf, int32 width, Highlight* highlight, bool cursor, BView *inView) { if (highlight != NULL) attr.state = highlight->Highlighter()->AdjustTextAttributes(attr.state); inView->SetFont(attr.IsBold() && !fEmulateBold && fAllowBold ? &fBoldFont : &fHalfFont); // Set pen point float x2 = x1 + fFontWidth * width; float y2 = y1 + fFontHeight; rgb_color rgb_fore = fTextForeColor; rgb_color rgb_back = fTextBackColor; rgb_color rgb_under = fTextForeColor; // color attribute if (attr.IsForeSet()) rgb_fore = attr.ForegroundColor(fTextBuffer->Palette()); if (attr.IsBackSet()) rgb_back = attr.BackgroundColor(fTextBuffer->Palette()); if (attr.IsUnderSet()) rgb_under = attr.UnderlineColor(fTextBuffer->Palette()); // Selection check. if (cursor) { rgb_fore = fCursorForeColor; rgb_back = fCursorBackColor; } else if (highlight != NULL) { rgb_fore = highlight->Highlighter()->ForegroundColor(); rgb_back = highlight->Highlighter()->BackgroundColor(); } else { // Reverse attribute(If selected area, don't reverse color). if (attr.IsInverse()) { rgb_color rgb_tmp = rgb_fore; rgb_fore = rgb_back; rgb_back = rgb_tmp; } } // Fill color at Background color and set low color. inView->SetHighColor(rgb_back); inView->FillRect(BRect(x1, y1, x2 - 1, y2 - 1)); inView->SetLowColor(rgb_back); // underline attribute if (attr.IsUnder()) { inView->SetHighColor(rgb_under); switch (attr.UnderlineStyle()) { default: case SINGLE_UNDERLINE: inView->MovePenTo(x1, y1 + fFontAscent + 1); inView->StrokeLine(BPoint(x1 , y1 + fFontAscent + 1), BPoint(x2 , y1 + fFontAscent + 1)); break; case DOUBLE_UNDERLINE: inView->MovePenTo(x1, y1 + fFontAscent); inView->StrokeLine(BPoint(x1 , y1 + fFontAscent), BPoint(x2 , y1 + fFontAscent)); inView->MovePenTo(x1, y1 + fFontAscent + 2); inView->StrokeLine(BPoint(x1 , y1 + fFontAscent + 2), BPoint(x2 , y1 + fFontAscent + 2)); break; case CURLY_UNDERLINE: { inView->MovePenTo(x1, y1 + fFontAscent + 1); bool up = true; for (float x = x1; x < x2; x += 3) { inView->StrokeLine(BPoint(x, y1 + fFontAscent + (up ? 0 : 2)), BPoint(std::min(x + 2, x2), y1 + fFontAscent + (up ? 2 : 0))); up = !up; } break; } case DOTTED_UNDERLINE: { inView->MovePenTo(x1, y1 + fFontAscent + 1); for (float x = x1; x < x2; x += 4) { inView->StrokeLine(BPoint(x, y1 + fFontAscent + 1), BPoint(std::min(x, x2), y1 + fFontAscent + 1)); } break; } case DASHED_UNDERLINE: { inView->MovePenTo(x1, y1 + fFontAscent + 1); for (float x = x1; x < x2; x += 5) { inView->StrokeLine(BPoint(x, y1 + fFontAscent + 1), BPoint(std::min(x + 2, x2), y1 + fFontAscent + 1)); } break; } } } inView->SetHighColor(rgb_fore); // Draw character. if (attr.IsBold()) { if (fEmulateBold) { inView->MovePenTo(x1 - 1, y1 + fFontAscent - 1); inView->DrawString((char *)buf); inView->SetDrawingMode(B_OP_BLEND); } else { rgb_color bright = rgb_fore; bright.red = saturated_add(bright.red, 64); bright.green = saturated_add(bright.green, 64); bright.blue = saturated_add(bright.blue, 64); inView->SetHighColor(bright); } } inView->MovePenTo(x1, y1 + fFontAscent); inView->DrawString((char *)buf); inView->SetDrawingMode(B_OP_COPY); } /*! Caller must have locked fTextBuffer. */ void TermView::_DrawCursor() { BRect rect(fFontWidth * fCursor.x, _LineOffset(fCursor.y), 0, 0); rect.right = rect.left + fFontWidth - 1; rect.bottom = rect.top + fFontHeight - 1; int32 firstVisible = _LineAt(0); UTF8Char character; Attributes attr; bool cursorVisible = _IsCursorVisible(); if (cursorVisible) { switch (fCursorStyle) { case UNDERLINE_CURSOR: rect.top = rect.bottom - 2; break; case IBEAM_CURSOR: rect.right = rect.left + 1; break; case BLOCK_CURSOR: default: break; } } Highlight* highlight = _CheckHighlightRegion(TermPos(fCursor.x, fCursor.y)); if (fVisibleTextBuffer->GetChar(fCursor.y - firstVisible, fCursor.x, character, attr) == A_CHAR && (fCursorStyle == BLOCK_CURSOR || !cursorVisible)) { int32 width = attr.IsWidth() ? FULL_WIDTH : HALF_WIDTH; char buffer[5]; int32 bytes = UTF8Char::ByteCount(character.bytes[0]); memcpy(buffer, character.bytes, bytes); buffer[bytes] = '\0'; _DrawLinePart(fCursor.x * fFontWidth, (int32)rect.top, attr, buffer, width, highlight, cursorVisible, this); } else { if (highlight != NULL) SetHighColor(highlight->Highlighter()->BackgroundColor()); else if (cursorVisible) SetHighColor(fCursorBackColor ); else { uint32 count = 0; rgb_color rgb_back = fTextBackColor; if (fTextBuffer->IsAlternateScreenActive()) // alternate screen uses cell attributes beyond the line ends fTextBuffer->GetCellAttributes( fCursor.y, fCursor.x, attr, count); else fVisibleTextBuffer->GetLineColor(fCursor.y - firstVisible, attr); if (attr.IsBackSet()) rgb_back = attr.BackgroundColor(fTextBuffer->Palette()); SetHighColor(rgb_back); } if (attr.IsWidth() && fCursorStyle != IBEAM_CURSOR) rect.right += fFontWidth; FillRect(rect); } } bool TermView::_IsCursorVisible() const { return !fCursorHidden && fCursorState < kCursorVisibleIntervals; } void TermView::_BlinkCursor() { bool wasVisible = _IsCursorVisible(); if (!wasVisible && fInline && fInline->IsActive()) return; bigtime_t now = system_time(); if (Window()->IsActive() && now - fLastActivityTime >= kCursorBlinkInterval) fCursorState = (fCursorState + 1) % kCursorBlinkIntervals; else fCursorState = 0; if (wasVisible != _IsCursorVisible()) _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y); } void TermView::_ActivateCursor(bool invalidate) { fLastActivityTime = system_time(); if (invalidate && fCursorState != 0) _BlinkCursor(); else fCursorState = 0; } //! Update scroll bar range and knob size. void TermView::_UpdateScrollBarRange() { if (fScrollBar == NULL) return; int32 historySize; { BAutolock _(fTextBuffer); historySize = fTextBuffer->HistorySize(); } float viewHeight = fRows * fFontHeight; float historyHeight = (float)historySize * fFontHeight; //debug_printf("TermView::_UpdateScrollBarRange(): history: %ld, range: %f - 0\n", //historySize, -historyHeight); fScrollBar->SetRange(-historyHeight, 0); if (historySize > 0) fScrollBar->SetProportion(viewHeight / (viewHeight + historyHeight)); } //! Handler for SIGWINCH void TermView::_UpdateSIGWINCH() { if (fFrameResized) { fShell->UpdateWindowSize(fRows, fColumns); fFrameResized = false; } } void TermView::AttachedToWindow() { fMouseButtons = 0; _UpdateModifiers(); // update the terminal size because it may have changed while the TermView // was detached from the window. On such conditions FrameResized was not // called when the resize occured SetTermSize(Bounds(), true); MakeFocus(true); if (fScrollBar) { fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows); _UpdateScrollBarRange(); } BMessenger thisMessenger(this); BMessage message(kUpdateSigWinch); fWinchRunner = new (std::nothrow) BMessageRunner(thisMessenger, &message, 500000); { TextBufferSyncLocker _(this); fTextBuffer->SetListener(thisMessenger); _SynchronizeWithTextBuffer(0, -1); } be_clipboard->StartWatching(thisMessenger); } void TermView::DetachedFromWindow() { be_clipboard->StopWatching(BMessenger(this)); _NextState(fDefaultState); delete fWinchRunner; fWinchRunner = NULL; delete fCursorBlinkRunner; fCursorBlinkRunner = NULL; delete fResizeRunner; fResizeRunner = NULL; { BAutolock _(fTextBuffer); fTextBuffer->UnsetListener(); } } void TermView::Draw(BRect updateRect) { int32 x1 = (int32)(updateRect.left / fFontWidth); int32 x2 = std::min((int)(updateRect.right / fFontWidth), fColumns - 1); int32 firstVisible = _LineAt(0); int32 y1 = _LineAt(updateRect.top); int32 y2 = std::min(_LineAt(updateRect.bottom), (int32)fRows - 1); // clear the area to the right of the line ends if (y1 <= y2) { float clearLeft = fColumns * fFontWidth; if (clearLeft <= updateRect.right) { BRect rect(clearLeft, updateRect.top, updateRect.right, updateRect.bottom); SetHighColor(fTextBackColor); FillRect(rect); } } // clear the area below the last line if (y2 == fRows - 1) { float clearTop = _LineOffset(fRows); if (clearTop <= updateRect.bottom) { BRect rect(updateRect.left, clearTop, updateRect.right, updateRect.bottom); SetHighColor(fTextBackColor); FillRect(rect); } } // draw the affected line parts if (x1 <= x2) { Attributes attr; for (int32 j = y1; j <= y2; j++) { int32 k = x1; char buf[fColumns * 4 + 1]; if (fVisibleTextBuffer->IsFullWidthChar(j - firstVisible, k)) k--; if (k < 0) k = 0; for (int32 i = k; i <= x2;) { int32 lastColumn = x2; Highlight* highlight = _CheckHighlightRegion(j, i, lastColumn); // This will clip lastColumn to the selection start or end // to ensure the selection is not drawn at the same time as // something else int32 count = fVisibleTextBuffer->GetString(j - firstVisible, i, lastColumn, buf, attr); // debug_printf(" fVisibleTextBuffer->GetString(%ld, %ld, %ld) -> (%ld, \"%.*s\"), highlight: %p\n", // j - firstVisible, i, lastColumn, count, (int)count, buf, highlight); if (count == 0) { // No chars to draw : we just fill the rectangle with the // back color of the last char at the left int nextColumn = lastColumn + 1; BRect rect(fFontWidth * i, _LineOffset(j), fFontWidth * nextColumn - 1, 0); rect.bottom = rect.top + fFontHeight - 1; rgb_color rgb_back = highlight != NULL ? highlight->Highlighter()->BackgroundColor() : fTextBackColor; if (fTextBuffer->IsAlternateScreenActive()) { // alternate screen uses cell attributes // beyond the line ends uint32 count = 0; fTextBuffer->GetCellAttributes(j, i, attr, count); rect.right = rect.left + fFontWidth * count - 1; nextColumn = i + count; } else fVisibleTextBuffer->GetLineColor(j - firstVisible, attr); if (attr.IsBackSet()) rgb_back = attr.BackgroundColor(fTextBuffer->Palette()); SetHighColor(rgb_back); rgb_back = HighColor(); FillRect(rect); // Go on to the next block i = nextColumn; continue; } // Note: full-width characters GetString()-ed always // with count 1, so this hardcoding is safe. From the other // side - drawing the whole string with one call render the // characters not aligned to cells grid - that looks much more // inaccurate for full-width strings than for half-width ones. if (attr.IsWidth()) count = FULL_WIDTH; _DrawLinePart(fFontWidth * i, (int32)_LineOffset(j), attr, buf, count, highlight, false, this); i += count; } } } if (fInline && fInline->IsActive()) _DrawInlineMethodString(); if (fCursor >= TermPos(x1, y1) && fCursor <= TermPos(x2, y2)) _DrawCursor(); } void TermView::_DoPrint(BRect updateRect) { #if 0 uint32 attr; uchar buf[1024]; const int numLines = (int)((updateRect.Height()) / fFontHeight); int y1 = (int)(updateRect.top) / fFontHeight; y1 = y1 -(fScrBufSize - numLines * 2); if (y1 < 0) y1 = 0; const int y2 = y1 + numLines -1; const int x1 = (int)(updateRect.left) / fFontWidth; const int x2 = (int)(updateRect.right) / fFontWidth; for (int j = y1; j <= y2; j++) { // If(x1, y1) Buffer is in string full width character, // alignment start position. int k = x1; if (fTextBuffer->IsFullWidthChar(j, k)) k--; if (k < 0) k = 0; for (int i = k; i <= x2;) { int count = fTextBuffer->GetString(j, i, x2, buf, &attr); if (count < 0) { i += abs(count); continue; } _DrawLinePart(fFontWidth * i, fFontHeight * j, attr, buf, count, false, false, this); i += count; } } #endif // 0 } void TermView::WindowActivated(bool active) { BView::WindowActivated(active); if (active && IsFocus()) { if (!fActive) _Activate(); } else { if (fActive) _Deactivate(); } _UpdateModifiers(); fActiveState->WindowActivated(active); } void TermView::MakeFocus(bool focusState) { BView::MakeFocus(focusState); if (focusState && Window() && Window()->IsActive()) { if (!fActive) _Activate(); } else { if (fActive) _Deactivate(); } } void TermView::KeyDown(const char *bytes, int32 numBytes) { _UpdateModifiers(); fActiveState->KeyDown(bytes, numBytes); } void TermView::FrameResized(float width, float height) { //debug_printf("TermView::FrameResized(%f, %f)\n", width, height); int32 columns = (int32)((width + 1) / fFontWidth); int32 rows = (int32)((height + 1) / fFontHeight); if (columns == fColumns && rows == fRows) return; bool hasResizeView = fResizeRunner != NULL; if (!hasResizeView) { // show the current size in a view fResizeView = new BStringView(BRect(100, 100, 300, 140), "size", ""); fResizeView->SetAlignment(B_ALIGN_CENTER); fResizeView->SetFont(be_bold_font); fResizeView->SetViewColor(fTextBackColor); fResizeView->SetLowColor(fTextBackColor); fResizeView->SetHighColor(fTextForeColor); BMessage message(MSG_REMOVE_RESIZE_VIEW_IF_NEEDED); fResizeRunner = new(std::nothrow) BMessageRunner(BMessenger(this), &message, 25000LL); } BString text; text << columns << " x " << rows; fResizeView->SetText(text.String()); fResizeView->GetPreferredSize(&width, &height); fResizeView->ResizeTo(width * 1.5, height * 1.5); fResizeView->MoveTo((Bounds().Width() - fResizeView->Bounds().Width()) / 2, (Bounds().Height()- fResizeView->Bounds().Height()) / 2); if (!hasResizeView && fResizeViewDisableCount < 1) AddChild(fResizeView); if (fResizeViewDisableCount > 0) fResizeViewDisableCount--; SetTermSize(rows, columns, true); } void TermView::MessageReceived(BMessage *message) { if (fActiveState->MessageReceived(message)) return; entry_ref ref; const char *ctrl_l = "\x0c"; // first check for any dropped message if (message->WasDropped() && (message->what == B_SIMPLE_DATA || message->what == B_MIME_DATA)) { char *text; ssize_t numBytes; //rgb_color *color; int32 i = 0; if (message->FindRef("refs", i++, &ref) == B_OK) { // first check if secondary mouse button is pressed int32 buttons = 0; message->FindInt32("buttons", &buttons); if (buttons == B_SECONDARY_MOUSE_BUTTON) { // start popup menu _SecondaryMouseButtonDropped(message); return; } _DoFileDrop(ref); while (message->FindRef("refs", i++, &ref) == B_OK) { _WritePTY(" ", 1); _DoFileDrop(ref); } return; #if 0 } else if (message->FindData("RGBColor", B_RGB_COLOR_TYPE, (const void **)&color, &numBytes) == B_OK && numBytes == sizeof(color)) { // TODO: handle color drop // maybe only on replicants ? return; #endif } else if (message->FindData("text/plain", B_MIME_TYPE, (const void **)&text, &numBytes) == B_OK) { _WritePTY(text, numBytes); return; } } switch (message->what) { case B_SIMPLE_DATA: case B_REFS_RECEIVED: { // handle refs if they weren't dropped int32 i = 0; if (message->FindRef("refs", i++, &ref) == B_OK) { _DoFileDrop(ref); while (message->FindRef("refs", i++, &ref) == B_OK) { _WritePTY(" ", 1); _DoFileDrop(ref); } } else BView::MessageReceived(message); break; } case B_COPY: Copy(be_clipboard); break; case B_PASTE: { int32 code; if (message->FindInt32("index", &code) == B_OK) Paste(be_clipboard); break; } case B_CLIPBOARD_CHANGED: // This message originates from the system clipboard. Overwrite // the contents of the mouse clipboard with the ones from the // system clipboard, in case it contains text data. if (be_clipboard->Lock()) { if (fMouseClipboard->Lock()) { BMessage* clipMsgA = be_clipboard->Data(); const char* text; ssize_t numBytes; if (clipMsgA->FindData("text/plain", B_MIME_TYPE, (const void**)&text, &numBytes) == B_OK ) { fMouseClipboard->Clear(); BMessage* clipMsgB = fMouseClipboard->Data(); clipMsgB->AddData("text/plain", B_MIME_TYPE, text, numBytes); fMouseClipboard->Commit(); } fMouseClipboard->Unlock(); } be_clipboard->Unlock(); } break; case B_SELECT_ALL: SelectAll(); break; case B_SET_PROPERTY: { int32 i; int32 encodingID; BMessage specifier; if (message->GetCurrentSpecifier(&i, &specifier) == B_OK && strcmp("encoding", specifier.FindString("property", i)) == 0) { message->FindInt32 ("data", &encodingID); SetEncoding(encodingID); message->SendReply(B_REPLY); } else { BView::MessageReceived(message); } break; } case B_GET_PROPERTY: { int32 i; BMessage specifier; if (message->GetCurrentSpecifier(&i, &specifier) == B_OK) { if (strcmp("encoding", specifier.FindString("property", i)) == 0) { BMessage reply(B_REPLY); reply.AddInt32("result", Encoding()); message->SendReply(&reply); } else if (strcmp("tty", specifier.FindString("property", i)) == 0) { BMessage reply(B_REPLY); reply.AddString("result", TerminalName()); message->SendReply(&reply); } else BView::MessageReceived(message); } else BView::MessageReceived(message); break; } case B_MODIFIERS_CHANGED: { _UpdateModifiers(); break; } case B_INPUT_METHOD_EVENT: { int32 opcode; if (message->FindInt32("be:opcode", &opcode) == B_OK) { switch (opcode) { case B_INPUT_METHOD_STARTED: { BMessenger messenger; if (message->FindMessenger("be:reply_to", &messenger) == B_OK) { fInline = new (std::nothrow) InlineInput(messenger); } break; } case B_INPUT_METHOD_STOPPED: delete fInline; fInline = NULL; break; case B_INPUT_METHOD_CHANGED: if (fInline != NULL) _HandleInputMethodChanged(message); break; case B_INPUT_METHOD_LOCATION_REQUEST: if (fInline != NULL) _HandleInputMethodLocationRequest(); break; default: break; } } break; } case B_MOUSE_WHEEL_CHANGED: { // overridden to allow scrolling emulation in alternative screen // mode BAutolock locker(fTextBuffer); float deltaY = 0; if (fTextBuffer->IsAlternateScreenActive() && message->FindFloat("be:wheel_delta_y", &deltaY) == B_OK && deltaY != 0) { // We are in alternative screen mode and have a vertical delta // we can work with -- emulate scrolling via terminal escape // sequences. locker.Unlock(); // scroll pagewise, if one of Option, Command, or Control is // pressed int32 steps; const char* stepString; if ((modifiers() & B_SHIFT_KEY) != 0) { // pagewise stepString = deltaY > 0 ? PAGE_DOWN_KEY_CODE : PAGE_UP_KEY_CODE; steps = abs((int)deltaY); } else { // three lines per step stepString = deltaY > 0 ? DOWN_ARROW_KEY_CODE : UP_ARROW_KEY_CODE; steps = 3 * abs((int)deltaY); } // We want to do only a single write(), so compose a string // repeating the sequence as often as required by the delta. BString toWrite; for (int32 i = 0; i Write(ctrl_l, 1); break; case kBlinkCursor: _BlinkCursor(); break; case kUpdateSigWinch: _UpdateSIGWINCH(); break; case kSecondaryMouseDropAction: _DoSecondaryMouseDropAction(message); break; case MSG_TERMINAL_BUFFER_CHANGED: { TextBufferSyncLocker _(this); _SynchronizeWithTextBuffer(0, -1); break; } case MSG_SET_TERMINAL_TITLE: { const char* title; if (message->FindString("title", &title) == B_OK) { if (fListener != NULL) fListener->SetTermViewTitle(this, title); } break; } case MSG_SET_TERMINAL_COLORS: { int32 count = 0; if (message->FindInt32("count", &count) != B_OK) break; bool dynamic = false; if (message->FindBool("dynamic", &dynamic) != B_OK) break; for (int i = 0; i < count; i++) { uint8 index = 0; if (message->FindUInt8("index", i, &index) != B_OK) break; ssize_t bytes = 0; rgb_color* color = 0; if (message->FindData("color", B_RGB_COLOR_TYPE, i, (const void**)&color, &bytes) != B_OK) break; SetTermColor(index, *color, dynamic); } break; } case MSG_RESET_TERMINAL_COLORS: { int32 count = 0; if (message->FindInt32("count", &count) != B_OK) break; bool dynamic = false; if (message->FindBool("dynamic", &dynamic) != B_OK) break; for (int i = 0; i < count; i++) { uint8 index = 0; if (message->FindUInt8("index", i, &index) != B_OK) break; SetTermColor(index, TermApp::DefaultPalette()[index], dynamic); } break; } case MSG_GET_TERMINAL_COLOR: { uint8 index = 0; if (message->FindUInt8("index", &index) != B_OK) break; rgb_color color; status_t status = GetTermColor(index, &color); if (status == B_OK) { BString reply; reply.SetToFormat("\033]%u;rgb:%02x/%02x/%02x\033\\", index, color.red, color.green, color.blue); fShell->Write(reply.String(), reply.Length()); } break; } case MSG_SET_CURSOR_STYLE: { int32 style = BLOCK_CURSOR; if (message->FindInt32("style", &style) == B_OK) fCursorStyle = style; bool blinking = fCursorBlinking; if (message->FindBool("blinking", &blinking) == B_OK) SwitchCursorBlinking(blinking); bool hidden = fCursorHidden; if (message->FindBool("hidden", &hidden) == B_OK) fCursorHidden = hidden; break; } case MSG_ENABLE_META_KEY: { bool enable; if (message->FindBool("enableInterpretMetaKey", &enable) == B_OK) fInterpretMetaKey = enable; if (message->FindBool("enableMetaKeySendsEscape", &enable) == B_OK) fMetaKeySendsEscape = enable; break; } case MSG_ENABLE_BRACKETED_PASTE: { bool enable; if (message->FindBool("enableBracketedPaste", &enable) == B_OK) fUseBracketedPaste = enable; break; } case MSG_REPORT_MOUSE_EVENT: { bool value; if (message->FindBool("reportX10MouseEvent", &value) == B_OK) fReportX10MouseEvent = value; // setting one of the three disables the other two if (message->FindBool("reportNormalMouseEvent", &value) == B_OK) { fReportNormalMouseEvent = value; fReportButtonMouseEvent = false; fReportAnyMouseEvent = false; } if (message->FindBool("reportButtonMouseEvent", &value) == B_OK) { fReportButtonMouseEvent = value; fReportNormalMouseEvent = false; fReportAnyMouseEvent = false; } if (message->FindBool("reportAnyMouseEvent", &value) == B_OK) { fReportAnyMouseEvent = value; fReportNormalMouseEvent = false; fReportButtonMouseEvent = false; } if (message->FindBool( "enableExtendedMouseCoordinates", &value) == B_OK) fEnableExtendedMouseCoordinates = value; break; } case MSG_REMOVE_RESIZE_VIEW_IF_NEEDED: { BPoint point; uint32 buttons; GetMouse(&point, &buttons, false); if (buttons != 0) break; if (fResizeView != NULL) { fResizeView->RemoveSelf(); delete fResizeView; fResizeView = NULL; } delete fResizeRunner; fResizeRunner = NULL; break; } case MSG_QUIT_TERMNAL: { int32 reason; if (message->FindInt32("reason", &reason) != B_OK) reason = 0; if (fListener != NULL) fListener->NotifyTermViewQuit(this, reason); break; } default: BView::MessageReceived(message); break; } } status_t TermView::GetSupportedSuites(BMessage* message) { BPropertyInfo propInfo(sPropList); message->AddString("suites", "suite/vnd.naan-termview"); message->AddFlat("messages", &propInfo); return BView::GetSupportedSuites(message); } void TermView::ScrollTo(BPoint where) { //debug_printf("TermView::ScrollTo(): %f -> %f\n", fScrollOffset, where.y); float diff = where.y - fScrollOffset; if (diff == 0) return; float bottom = Bounds().bottom; int32 oldFirstLine = _LineAt(0); int32 oldLastLine = _LineAt(bottom); int32 newFirstLine = _LineAt(diff); int32 newLastLine = _LineAt(bottom + diff); fScrollOffset = where.y; // invalidate the current cursor position before scrolling _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y); // scroll contents BRect destRect(Frame().OffsetToCopy(Bounds().LeftTop())); BRect sourceRect(destRect.OffsetByCopy(0, diff)); //debug_printf("CopyBits(((%f, %f) - (%f, %f)) -> (%f, %f) - (%f, %f))\n", //sourceRect.left, sourceRect.top, sourceRect.right, sourceRect.bottom, //destRect.left, destRect.top, destRect.right, destRect.bottom); CopyBits(sourceRect, destRect); // sync visible text buffer with text buffer if (newFirstLine != oldFirstLine || newLastLine != oldLastLine) { if (newFirstLine != oldFirstLine) { //debug_printf("fVisibleTextBuffer->ScrollBy(%ld)\n", newFirstLine - oldFirstLine); fVisibleTextBuffer->ScrollBy(newFirstLine - oldFirstLine); } TextBufferSyncLocker _(this); if (diff < 0) _SynchronizeWithTextBuffer(newFirstLine, oldFirstLine - 1); else _SynchronizeWithTextBuffer(oldLastLine + 1, newLastLine); } } void TermView::TargetedByScrollView(BScrollView *scrollView) { BView::TargetedByScrollView(scrollView); SetScrollBar(scrollView ? scrollView->ScrollBar(B_VERTICAL) : NULL); } BHandler* TermView::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier, int32 what, const char* property) { BHandler* target = this; BPropertyInfo propInfo(sPropList); if (propInfo.FindMatch(message, index, specifier, what, property) < B_OK) { target = BView::ResolveSpecifier(message, index, specifier, what, property); } return target; } void TermView::_SecondaryMouseButtonDropped(BMessage* message) { // Launch menu to choose what is to do with the message data BPoint point; if (message->FindPoint("_drop_point_", &point) != B_OK) return; BMessage* insertMessage = new BMessage(*message); insertMessage->what = kSecondaryMouseDropAction; insertMessage->AddInt8("action", kInsert); BMessage* cdMessage = new BMessage(*message); cdMessage->what = kSecondaryMouseDropAction; cdMessage->AddInt8("action", kChangeDirectory); BMessage* lnMessage = new BMessage(*message); lnMessage->what = kSecondaryMouseDropAction; lnMessage->AddInt8("action", kLinkFiles); BMessage* mvMessage = new BMessage(*message); mvMessage->what = kSecondaryMouseDropAction; mvMessage->AddInt8("action", kMoveFiles); BMessage* cpMessage = new BMessage(*message); cpMessage->what = kSecondaryMouseDropAction; cpMessage->AddInt8("action", kCopyFiles); BMenuItem* insertItem = new BMenuItem( B_TRANSLATE("Insert path"), insertMessage); BMenuItem* cdItem = new BMenuItem( B_TRANSLATE("Change directory"), cdMessage); BMenuItem* lnItem = new BMenuItem( B_TRANSLATE("Create link here"), lnMessage); BMenuItem* mvItem = new BMenuItem(B_TRANSLATE("Move here"), mvMessage); BMenuItem* cpItem = new BMenuItem(B_TRANSLATE("Copy here"), cpMessage); BMenuItem* chItem = new BMenuItem(B_TRANSLATE("Cancel"), NULL); // if the refs point to different directorys disable the cd menu item bool differentDirs = false; BDirectory firstDir; entry_ref ref; int i = 0; while (message->FindRef("refs", i++, &ref) == B_OK) { BNode node(&ref); BEntry entry(&ref); BDirectory dir; if (node.IsDirectory()) dir.SetTo(&ref); else entry.GetParent(&dir); if (i == 1) { node_ref nodeRef; dir.GetNodeRef(&nodeRef); firstDir.SetTo(&nodeRef); } else if (firstDir != dir) { differentDirs = true; break; } } if (differentDirs) cdItem->SetEnabled(false); BPopUpMenu *menu = new BPopUpMenu( "Secondary mouse button drop menu"); menu->SetAsyncAutoDestruct(true); menu->AddItem(insertItem); menu->AddSeparatorItem(); menu->AddItem(cdItem); menu->AddItem(lnItem); menu->AddItem(mvItem); menu->AddItem(cpItem); menu->AddSeparatorItem(); menu->AddItem(chItem); menu->SetTargetForItems(this); menu->Go(point, true, true, true); } void TermView::_DoSecondaryMouseDropAction(BMessage* message) { int8 action = -1; message->FindInt8("action", &action); BString outString = ""; BString itemString = ""; switch (action) { case kInsert: break; case kChangeDirectory: outString = "cd "; break; case kLinkFiles: outString = "ln -s "; break; case kMoveFiles: outString = "mv "; break; case kCopyFiles: outString = "cp "; break; default: return; } bool listContainsDirectory = false; entry_ref ref; int32 i = 0; while (message->FindRef("refs", i++, &ref) == B_OK) { BEntry ent(&ref); BNode node(&ref); BPath path(&ent); BString string(path.Path()); if (node.IsDirectory()) listContainsDirectory = true; if (i > 1) itemString += " "; if (action == kChangeDirectory) { if (!node.IsDirectory()) { int32 slash = string.FindLast("/"); string.Truncate(slash); } string.CharacterEscape(kShellEscapeCharacters, '\\'); itemString += string; break; } string.CharacterEscape(kShellEscapeCharacters, '\\'); itemString += string; } if (listContainsDirectory && action == kCopyFiles) outString += "-R "; outString += itemString; if (action == kLinkFiles || action == kMoveFiles || action == kCopyFiles) outString += " ."; if (action != kInsert) outString += "\n"; _WritePTY(outString.String(), outString.Length()); } //! Gets dropped file full path and display it at cursor position. void TermView::_DoFileDrop(entry_ref& ref) { BEntry ent(&ref); BPath path(&ent); BString string(path.Path()); string.CharacterEscape(kShellEscapeCharacters, '\\'); _WritePTY(string.String(), string.Length()); } /*! Text buffer must already be locked. */ void TermView::_SynchronizeWithTextBuffer(int32 visibleDirtyTop, int32 visibleDirtyBottom) { TerminalBufferDirtyInfo& info = fTextBuffer->DirtyInfo(); int32 linesScrolled = info.linesScrolled; //debug_printf("TermView::_SynchronizeWithTextBuffer(): dirty: %ld - %ld, " //"scrolled: %ld, visible dirty: %ld - %ld\n", info.dirtyTop, info.dirtyBottom, //info.linesScrolled, visibleDirtyTop, visibleDirtyBottom); bigtime_t now = system_time(); bigtime_t timeElapsed = now - fLastSyncTime; if (timeElapsed > 2 * kSyncUpdateGranularity) { // last sync was ages ago fLastSyncTime = now; fScrolledSinceLastSync = linesScrolled; } if (fSyncRunner == NULL) { // We consider clocked syncing when more than a full screen height has // been scrolled in less than a sync update period. Once we're // actively considering it, the same condition will convince us to // actually do it. if (fScrolledSinceLastSync + linesScrolled <= fRows) { // Condition doesn't hold yet. Reset if time is up, or otherwise // keep counting. if (timeElapsed > kSyncUpdateGranularity) { fConsiderClockedSync = false; fLastSyncTime = now; fScrolledSinceLastSync = linesScrolled; } else fScrolledSinceLastSync += linesScrolled; } else if (fConsiderClockedSync) { // We are convinced -- create the sync runner. fLastSyncTime = now; fScrolledSinceLastSync = 0; BMessage message(MSG_TERMINAL_BUFFER_CHANGED); fSyncRunner = new(std::nothrow) BMessageRunner(BMessenger(this), &message, kSyncUpdateGranularity); if (fSyncRunner != NULL && fSyncRunner->InitCheck() == B_OK) return; delete fSyncRunner; fSyncRunner = NULL; } else { // Looks interesting so far. Reset the counts and consider clocked // syncing. fConsiderClockedSync = true; fLastSyncTime = now; fScrolledSinceLastSync = 0; } } else if (timeElapsed < kSyncUpdateGranularity) { // sync time not passed yet -- keep counting fScrolledSinceLastSync += linesScrolled; return; } if (fScrolledSinceLastSync + linesScrolled <= fRows) { // time's up, but not enough happened delete fSyncRunner; fSyncRunner = NULL; fLastSyncTime = now; fScrolledSinceLastSync = linesScrolled; } else { // Things are still rolling, but the sync time's up. fLastSyncTime = now; fScrolledSinceLastSync = 0; } fVisibleTextBufferChanged = true; // Simple case first -- complete invalidation. if (info.invalidateAll) { Invalidate(); _UpdateScrollBarRange(); _Deselect(); fCursor = fTextBuffer->Cursor(); _ActivateCursor(false); int32 offset = _LineAt(0); fVisibleTextBuffer->SynchronizeWith(fTextBuffer, offset, offset, offset + fTextBuffer->Height() + 2); info.Reset(); return; } BRect bounds = Bounds(); int32 firstVisible = _LineAt(0); int32 lastVisible = _LineAt(bounds.bottom); int32 historySize = fTextBuffer->HistorySize(); bool doScroll = false; if (linesScrolled > 0) { _UpdateScrollBarRange(); visibleDirtyTop -= linesScrolled; visibleDirtyBottom -= linesScrolled; if (firstVisible < 0) { firstVisible -= linesScrolled; lastVisible -= linesScrolled; float scrollOffset; if (firstVisible < -historySize) { firstVisible = -historySize; doScroll = true; scrollOffset = -historySize * fFontHeight; // We need to invalidate the lower linesScrolled lines of the // visible text buffer, since those will be scrolled up and // need to be replaced. We just use visibleDirty{Top,Bottom} // for that purpose. Unless invoked from ScrollTo() (i.e. // user-initiated scrolling) those are unused. In the unlikely // case that the user is scrolling at the same time we may // invalidate too many lines, since we have to extend the given // region. // Note that in the firstVisible == 0 case the new lines are // already in the dirty region, so they will be updated anyway. if (visibleDirtyTop <= visibleDirtyBottom) { if (lastVisible < visibleDirtyTop) visibleDirtyTop = lastVisible; if (visibleDirtyBottom < lastVisible + linesScrolled) visibleDirtyBottom = lastVisible + linesScrolled; } else { visibleDirtyTop = lastVisible + 1; visibleDirtyBottom = lastVisible + linesScrolled; } } else scrollOffset = fScrollOffset - linesScrolled * fFontHeight; _ScrollTo(scrollOffset, false); } else doScroll = true; if (doScroll && lastVisible >= firstVisible && !(info.IsDirtyRegionValid() && firstVisible >= info.dirtyTop && lastVisible <= info.dirtyBottom)) { // scroll manually float scrollBy = linesScrolled * fFontHeight; BRect destRect(Frame().OffsetToCopy(B_ORIGIN)); BRect sourceRect(destRect.OffsetByCopy(0, scrollBy)); // invalidate the current cursor position before scrolling _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y); //debug_printf("CopyBits(((%f, %f) - (%f, %f)) -> (%f, %f) - (%f, %f))\n", //sourceRect.left, sourceRect.top, sourceRect.right, sourceRect.bottom, //destRect.left, destRect.top, destRect.right, destRect.bottom); CopyBits(sourceRect, destRect); fVisibleTextBuffer->ScrollBy(linesScrolled); } // move highlights for (int32 i = 0; Highlight* highlight = fHighlights.ItemAt(i); i++) { if (highlight->IsEmpty()) continue; highlight->ScrollRange(linesScrolled); if (highlight == &fSelection) { fInitialSelectionStart.y -= linesScrolled; fInitialSelectionEnd.y -= linesScrolled; } if (highlight->Start().y < -historySize) { if (highlight == &fSelection) _Deselect(); else _ClearHighlight(highlight); } } } // invalidate dirty region if (info.IsDirtyRegionValid()) { _InvalidateTextRect(0, info.dirtyTop, fTextBuffer->Width() - 1, info.dirtyBottom); // clear the selection, if affected if (!fSelection.IsEmpty()) { // TODO: We're clearing the selection more often than necessary -- // to avoid that, we'd also need to track the x coordinates of the // dirty range. int32 selectionBottom = fSelection.End().x > 0 ? fSelection.End().y : fSelection.End().y - 1; if (fSelection.Start().y <= info.dirtyBottom && info.dirtyTop <= selectionBottom) { _Deselect(); } } } if (visibleDirtyTop <= visibleDirtyBottom) info.ExtendDirtyRegion(visibleDirtyTop, visibleDirtyBottom); if (linesScrolled != 0 || info.IsDirtyRegionValid()) { fVisibleTextBuffer->SynchronizeWith(fTextBuffer, firstVisible, info.dirtyTop, info.dirtyBottom); } // invalidate cursor, if it changed TermPos cursor = fTextBuffer->Cursor(); if (fCursor != cursor || linesScrolled != 0) { // Before we scrolled we did already invalidate the old cursor. if (!doScroll) _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y); fCursor = cursor; _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y); _ActivateCursor(false); } info.Reset(); } void TermView::_VisibleTextBufferChanged() { if (!fVisibleTextBufferChanged) return; fVisibleTextBufferChanged = false; fActiveState->VisibleTextBufferChanged(); } /*! Write strings to PTY device. If encoding system isn't UTF8, change encoding to UTF8 before writing PTY. */ void TermView::_WritePTY(const char* text, int32 numBytes) { if (fEncoding != M_UTF8) { while (numBytes > 0) { char buffer[1024]; int32 bufferSize = sizeof(buffer); int32 sourceSize = numBytes; int32 state = 0; if (convert_to_utf8(fEncoding, text, &sourceSize, buffer, &bufferSize, &state) != B_OK || bufferSize == 0) { break; } fShell->Write(buffer, bufferSize); text += sourceSize; numBytes -= sourceSize; } } else { fShell->Write(text, numBytes); } } //! Returns the square of the actual pixel distance between both points float TermView::_MouseDistanceSinceLastClick(BPoint where) { return (fLastClickPoint.x - where.x) * (fLastClickPoint.x - where.x) + (fLastClickPoint.y - where.y) * (fLastClickPoint.y - where.y); } void TermView::_SendMouseEvent(int32 buttons, int32 mode, int32 x, int32 y, bool motion, bool upEvent) { if (!fEnableExtendedMouseCoordinates) { char xtermButtons; if (buttons == B_PRIMARY_MOUSE_BUTTON) xtermButtons = 32 + 0; else if (buttons == B_SECONDARY_MOUSE_BUTTON) xtermButtons = 32 + 1; else if (buttons == B_TERTIARY_MOUSE_BUTTON) xtermButtons = 32 + 2; else xtermButtons = 32 + 3; // dragging motion if (buttons != 0 && motion && fReportButtonMouseEvent) xtermButtons += 32; char xtermX = x + 1 + 32; char xtermY = y + 1 + 32; char destBuffer[6]; destBuffer[0] = '\033'; destBuffer[1] = '['; destBuffer[2] = 'M'; destBuffer[3] = xtermButtons; destBuffer[4] = xtermX; destBuffer[5] = xtermY; fShell->Write(destBuffer, 6); } else { char xtermButtons; if ((buttons & B_PRIMARY_MOUSE_BUTTON) != (motion ? 0 : (fMouseButtons & B_PRIMARY_MOUSE_BUTTON))) { xtermButtons = 0; } else if ((buttons & B_SECONDARY_MOUSE_BUTTON) != (motion ? 0 : (fMouseButtons & B_SECONDARY_MOUSE_BUTTON))) { xtermButtons = 2; } else if ((buttons & B_TERTIARY_MOUSE_BUTTON) != (motion ? 0 : (fMouseButtons & B_TERTIARY_MOUSE_BUTTON))) { xtermButtons = 1; } else xtermButtons = 3; // nur button events requested if (buttons == 0 && motion && fReportButtonMouseEvent) return; // dragging motion if (buttons != 0 && motion && fReportButtonMouseEvent) xtermButtons += 32; int16 xtermX = x + 1; int16 xtermY = y + 1; char destBuffer[21]; int size = snprintf(destBuffer, sizeof(destBuffer), "\033[<%u;%u;%u%c", xtermButtons, xtermX, xtermY, upEvent ? 'm' : 'M'); fShell->Write(destBuffer, size); } } void TermView::MouseDown(BPoint where) { if (!IsFocus()) MakeFocus(); _UpdateModifiers(); BMessage* currentMessage = Window()->CurrentMessage(); int32 buttons = currentMessage->GetInt32("buttons", 0); fActiveState->MouseDown(where, buttons, fModifiers); fMouseButtons = buttons; fLastClickPoint = where; } void TermView::MouseMoved(BPoint where, uint32 transit, const BMessage *message) { _UpdateModifiers(); fActiveState->MouseMoved(where, transit, message, fModifiers); } void TermView::MouseUp(BPoint where) { _UpdateModifiers(); int32 buttons = Window()->CurrentMessage()->GetInt32("buttons", 0); fActiveState->MouseUp(where, buttons); fMouseButtons = buttons; } //! Select a range of text. void TermView::_Select(TermPos start, TermPos end, bool inclusive, bool setInitialSelection) { TextBufferSyncLocker _(this); _SynchronizeWithTextBuffer(0, -1); if (end < start) std::swap(start, end); if (inclusive) end.x++; //debug_printf("TermView::_Select(): (%ld, %ld) - (%ld, %ld)\n", start.x, //start.y, end.x, end.y); if (start.x < 0) start.x = 0; if (end.x >= fColumns) end.x = fColumns; TermPos minPos(0, -fTextBuffer->HistorySize()); TermPos maxPos(0, fTextBuffer->Height()); start = restrict_value(start, minPos, maxPos); end = restrict_value(end, minPos, maxPos); // if the end is past the end of the line, select the line break, too if (fTextBuffer->LineLength(end.y) < end.x && end.y < fTextBuffer->Height()) { end.y++; end.x = 0; } if (fTextBuffer->IsFullWidthChar(start.y, start.x)) { start.x--; if (start.x < 0) start.x = 0; } if (fTextBuffer->IsFullWidthChar(end.y, end.x)) { end.x++; if (end.x >= fColumns) end.x = fColumns; } if (!fSelection.IsEmpty()) _InvalidateTextRange(fSelection.Start(), fSelection.End()); fSelection.SetRange(start, end); if (setInitialSelection) { fInitialSelectionStart = fSelection.Start(); fInitialSelectionEnd = fSelection.End(); } _InvalidateTextRange(fSelection.Start(), fSelection.End()); } //! Extend selection (shift + mouse click). void TermView::_ExtendSelection(TermPos pos, bool inclusive, bool useInitialSelection) { if (!useInitialSelection && !_HasSelection()) return; TermPos start = fSelection.Start(); TermPos end = fSelection.End(); if (useInitialSelection) { start = fInitialSelectionStart; end = fInitialSelectionEnd; } if (inclusive) { if (pos >= start && pos >= end) pos.x++; } if (pos < start) _Select(pos, end, false, !useInitialSelection); else if (pos > end) _Select(start, pos, false, !useInitialSelection); else if (useInitialSelection) _Select(start, end, false, false); } // clear the selection. void TermView::_Deselect() { //debug_printf("TermView::_Deselect(): has selection: %d\n", _HasSelection()); if (_ClearHighlight(&fSelection)) { fInitialSelectionStart.SetTo(0, 0); fInitialSelectionEnd.SetTo(0, 0); } } bool TermView::_HasSelection() const { return !fSelection.IsEmpty(); } void TermView::_SelectWord(BPoint where, bool extend, bool useInitialSelection) { BAutolock _(fTextBuffer); TermPos pos = _ConvertToTerminal(where); TermPos start, end; if (!fTextBuffer->FindWord(pos, fCharClassifier, true, start, end)) return; if (extend) { if (start < (useInitialSelection ? fInitialSelectionStart : fSelection.Start())) { _ExtendSelection(start, false, useInitialSelection); } else if (end > (useInitialSelection ? fInitialSelectionEnd : fSelection.End())) { _ExtendSelection(end, false, useInitialSelection); } else if (useInitialSelection) _Select(start, end, false, false); } else _Select(start, end, false, !useInitialSelection); } void TermView::_SelectLine(BPoint where, bool extend, bool useInitialSelection) { TermPos start = TermPos(0, _ConvertToTerminal(where).y); TermPos end = TermPos(0, start.y + 1); if (extend) { if (start < (useInitialSelection ? fInitialSelectionStart : fSelection.Start())) { _ExtendSelection(start, false, useInitialSelection); } else if (end > (useInitialSelection ? fInitialSelectionEnd : fSelection.End())) { _ExtendSelection(end, false, useInitialSelection); } else if (useInitialSelection) _Select(start, end, false, false); } else _Select(start, end, false, !useInitialSelection); } void TermView::_AddHighlight(Highlight* highlight) { fHighlights.AddItem(highlight); if (!highlight->IsEmpty()) _InvalidateTextRange(highlight->Start(), highlight->End()); } void TermView::_RemoveHighlight(Highlight* highlight) { if (!highlight->IsEmpty()) _InvalidateTextRange(highlight->Start(), highlight->End()); fHighlights.RemoveItem(highlight); } bool TermView::_ClearHighlight(Highlight* highlight) { if (highlight->IsEmpty()) return false; _InvalidateTextRange(highlight->Start(), highlight->End()); highlight->SetRange(TermPos(0, 0), TermPos(0, 0)); return true; } TermView::Highlight* TermView::_CheckHighlightRegion(const TermPos &pos) const { for (int32 i = 0; Highlight* highlight = fHighlights.ItemAt(i); i++) { if (highlight->RangeContains(pos)) return highlight; } return NULL; } TermView::Highlight* TermView::_CheckHighlightRegion(int32 row, int32 firstColumn, int32& lastColumn) const { Highlight* nextHighlight = NULL; for (int32 i = 0; Highlight* highlight = fHighlights.ItemAt(i); i++) { if (highlight->IsEmpty()) continue; if (row == highlight->Start().y && firstColumn < highlight->Start().x && lastColumn >= highlight->Start().x) { // region starts before the highlight, but intersects with it if (nextHighlight == NULL || highlight->Start().x < nextHighlight->Start().x) { nextHighlight = highlight; } continue; } if (row == highlight->End().y && firstColumn < highlight->End().x && lastColumn >= highlight->End().x) { // region starts in the highlight, but exceeds the end lastColumn = highlight->End().x - 1; return highlight; } TermPos pos(firstColumn, row); if (highlight->RangeContains(pos)) return highlight; } if (nextHighlight != NULL) lastColumn = nextHighlight->Start().x - 1; return NULL; } void TermView::GetFrameSize(float *width, float *height) { int32 historySize; { BAutolock _(fTextBuffer); historySize = fTextBuffer->HistorySize(); } if (width != NULL) *width = fColumns * fFontWidth; if (height != NULL) *height = (fRows + historySize) * fFontHeight; } // Find a string, and select it if found bool TermView::Find(const BString &str, bool forwardSearch, bool matchCase, bool matchWord) { TextBufferSyncLocker _(this); _SynchronizeWithTextBuffer(0, -1); TermPos start; if (_HasSelection()) { if (forwardSearch) start = fSelection.End(); else start = fSelection.Start(); } else { // search from the very beginning/end if (forwardSearch) start = TermPos(0, -fTextBuffer->HistorySize()); else start = TermPos(0, fTextBuffer->Height()); } TermPos matchStart, matchEnd; if (!fTextBuffer->Find(str.String(), start, forwardSearch, matchCase, matchWord, matchStart, matchEnd)) { return false; } _Select(matchStart, matchEnd, false, true); _ScrollToRange(fSelection.Start(), fSelection.End()); return true; } //! Get the selected text and copy to str void TermView::GetSelection(BString &str) { str.SetTo(""); BAutolock _(fTextBuffer); fTextBuffer->GetStringFromRegion(str, fSelection.Start(), fSelection.End()); } bool TermView::CheckShellGone() const { if (!fShell) return false; // check, if the shell does still live pid_t pid = fShell->ProcessID(); team_info info; return get_team_info(pid, &info) == B_BAD_TEAM_ID; } void TermView::InitiateDrag() { BAutolock _(fTextBuffer); BString copyStr(""); fTextBuffer->GetStringFromRegion(copyStr, fSelection.Start(), fSelection.End()); BMessage message(B_MIME_DATA); message.AddData("text/plain", B_MIME_TYPE, copyStr.String(), copyStr.Length()); BPoint start = _ConvertFromTerminal(fSelection.Start()); BPoint end = _ConvertFromTerminal(fSelection.End()); BRect rect; if (fSelection.Start().y == fSelection.End().y) rect.Set(start.x, start.y, end.x + fFontWidth, end.y + fFontHeight); else rect.Set(0, start.y, fColumns * fFontWidth, end.y + fFontHeight); rect = rect & Bounds(); DragMessage(&message, rect); } void TermView::_ScrollTo(float y, bool scrollGfx) { if (!scrollGfx) fScrollOffset = y; if (fScrollBar != NULL) fScrollBar->SetValue(y); else ScrollTo(BPoint(0, y)); } void TermView::_ScrollToRange(TermPos start, TermPos end) { if (start > end) std::swap(start, end); float startY = _LineOffset(start.y); float endY = _LineOffset(end.y) + fFontHeight - 1; float height = Bounds().Height(); if (endY - startY > height) { // The range is greater than the height. Scroll to the closest border. // already as good as it gets? if (startY <= 0 && endY >= height) return; if (startY > 0) { // scroll down to align the start with the top of the view _ScrollTo(fScrollOffset + startY, true); } else { // scroll up to align the end with the bottom of the view _ScrollTo(fScrollOffset + endY - height, true); } } else { // The range is smaller than the height. // already visible? if (startY >= 0 && endY <= height) return; if (startY < 0) { // scroll up to make the start visible _ScrollTo(fScrollOffset + startY, true); } else { // scroll down to make the end visible _ScrollTo(fScrollOffset + endY - height, true); } } } void TermView::DisableResizeView(int32 disableCount) { fResizeViewDisableCount += disableCount; } void TermView::_DrawInlineMethodString() { if (!fInline || !fInline->String()) return; const int32 numChars = BString(fInline->String()).CountChars(); BPoint startPoint = _ConvertFromTerminal(fCursor); BPoint endPoint = startPoint; endPoint.x += fFontWidth * numChars; endPoint.y += fFontHeight + 1; BRect eraseRect(startPoint, endPoint); PushState(); SetHighColor(fTextForeColor); FillRect(eraseRect); PopState(); BPoint loc = _ConvertFromTerminal(fCursor); loc.y += fFontHeight; SetFont(&fHalfFont); SetHighColor(fTextBackColor); SetLowColor(fTextForeColor); DrawString(fInline->String(), loc); } void TermView::_HandleInputMethodChanged(BMessage *message) { const char *string = NULL; if (message->FindString("be:string", &string) < B_OK || string == NULL) return; _ActivateCursor(false); if (IsFocus()) be_app->ObscureCursor(); // If we find the "be:confirmed" boolean (and the boolean is true), // it means it's over for now, so the current InlineInput object // should become inactive. We will probably receive a // B_INPUT_METHOD_STOPPED message after this one. bool confirmed; if (message->FindBool("be:confirmed", &confirmed) != B_OK) confirmed = false; fInline->SetString(""); Invalidate(); // TODO: Debug only snooze(100000); fInline->SetString(string); fInline->ResetClauses(); if (!confirmed && !fInline->IsActive()) fInline->SetActive(true); // Get the clauses, and pass them to the InlineInput object // TODO: Find out if what we did it's ok, currently we don't consider // clauses at all, while the bebook says we should; though the visual // effect we obtained seems correct. Weird. int32 clauseCount = 0; int32 clauseStart; int32 clauseEnd; while (message->FindInt32("be:clause_start", clauseCount, &clauseStart) == B_OK && message->FindInt32("be:clause_end", clauseCount, &clauseEnd) == B_OK) { if (!fInline->AddClause(clauseStart, clauseEnd)) break; clauseCount++; } if (confirmed) { fInline->SetString(""); _ActivateCursor(true); // now we need to feed ourselves the individual characters as if the // user would have pressed them now - this lets KeyDown() pick out all // the special characters like B_BACKSPACE, cursor keys and the like: const char* currPos = string; const char* prevPos = currPos; while (*currPos != '\0') { if ((*currPos & 0xC0) == 0xC0) { // found the start of an UTF-8 char, we collect while it lasts ++currPos; while ((*currPos & 0xC0) == 0x80) ++currPos; } else if ((*currPos & 0xC0) == 0x80) { // illegal: character starts with utf-8 intermediate byte, skip it prevPos = ++currPos; } else { // single byte character/code, just feed that ++currPos; } KeyDown(prevPos, currPos - prevPos); prevPos = currPos; } } else { // temporarily show transient state of inline input int32 selectionStart = 0; int32 selectionEnd = 0; message->FindInt32("be:selection", 0, &selectionStart); message->FindInt32("be:selection", 1, &selectionEnd); fInline->SetSelectionOffset(selectionStart); fInline->SetSelectionLength(selectionEnd - selectionStart); } Invalidate(); } void TermView::_HandleInputMethodLocationRequest() { BMessage message(B_INPUT_METHOD_EVENT); message.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST); BString string(fInline->String()); const int32 &limit = string.CountChars(); BPoint where = _ConvertFromTerminal(fCursor); where.y += fFontHeight; for (int32 i = 0; i < limit; i++) { // Add the location of the UTF8 characters where.x += fFontWidth; ConvertToScreen(&where); message.AddPoint("be:location_reply", where); message.AddFloat("be:height_reply", fFontHeight); } fInline->Method()->SendMessage(&message); } void TermView::_CancelInputMethod() { if (!fInline) return; InlineInput *inlineInput = fInline; fInline = NULL; if (inlineInput->IsActive() && Window()) { Invalidate(); BMessage message(B_INPUT_METHOD_EVENT); message.AddInt32("be:opcode", B_INPUT_METHOD_STOPPED); inlineInput->Method()->SendMessage(&message); } delete inlineInput; } void TermView::_UpdateModifiers() { // TODO: This method is a general work-around for missing or out-of-order // B_MODIFIERS_CHANGED messages. This should really be fixed where it is // broken (app server?). int32 oldModifiers = fModifiers; fModifiers = modifiers(); if (fModifiers != oldModifiers && fActiveState != NULL) fActiveState->ModifiersChanged(oldModifiers, fModifiers); } void TermView::_NextState(State* state) { if (state != fActiveState) { if (fActiveState != NULL) fActiveState->Exited(); fActiveState = state; fActiveState->Entered(); } } // #pragma mark - Listener TermView::Listener::~Listener() { } void TermView::Listener::NotifyTermViewQuit(TermView* view, int32 reason) { } void TermView::Listener::SetTermViewTitle(TermView* view, const char* title) { } void TermView::Listener::PreviousTermView(TermView* view) { } void TermView::Listener::NextTermView(TermView* view) { } // #pragma mark - #ifdef USE_DEBUG_SNAPSHOTS void TermView::MakeDebugSnapshots() { BAutolock _(fTextBuffer); time_t timeStamp = time(NULL); fTextBuffer->MakeLinesSnapshots(timeStamp, ".TextBuffer.dump"); fVisibleTextBuffer->MakeLinesSnapshots(timeStamp, ".VisualTextBuffer.dump"); } void TermView::StartStopDebugCapture() { BAutolock _(fTextBuffer); fTextBuffer->StartStopDebugCapture(); } #endif