* Copyright 2001-2025, 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 <stdlib.h>
#include <string.h>
#include <termios.h>
#include <algorithm>
#include <new>
#include <Alert.h>
#include <Application.h>
#include <Beep.h>
#include <Catalog.h>
#include <Clipboard.h>
#include <Debug.h>
#include <Directory.h>
#include <Dragger.h>
#include <Input.h>
#include <Locale.h>
#include <MenuItem.h>
#include <Message.h>
#include <MessageRunner.h>
#include <Node.h>
#include <Path.h>
#include <PopUpMenu.h>
#include <PropertyInfo.h>
#include <Region.h>
#include <Roster.h>
#include <ScrollBar.h>
#include <ScrollView.h>
#include <String.h>
#include <StringView.h>
#include <UTF8.h>
#include <Window.h>
#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."},
{ "command",
{B_EXECUTE_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0},
"execute command"},
{ 0 },
};
static const uint32 kUpdateSigWinch = 'Rwin';
static const uint32 kBlinkCursor = 'BlCr';
static const bigtime_t kSyncUpdateGranularity = 100000;
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 };
const int32 kSecondaryMouseDropAction = 'SMDA';
enum {
kInsert,
kChangeDirectory,
kLinkFiles,
kMoveFiles,
kCopyFiles
};
template<typename Type>
static inline Type
restrict_value(const Type& value, const Type& min, const Type& max)
{
return value < min ? min : (value > max ? max : value);
}
template<typename Type>
static inline Type
saturated_add(Type a, Type b)
{
const Type max = (Type)(-1);
return (max - a >= b ? a + b : max);
}
class TermView::TextBufferSyncLocker {
public:
TextBufferSyncLocker(TermView* view)
:
fView(view)
{
fView->fTextBuffer->Lock();
}
~TextBufferSyncLocker()
{
fView->fTextBuffer->Unlock();
if (fView->fVisibleTextBufferChanged)
fView->_VisibleTextBufferChanged();
}
private:
TermView* fView;
};
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)
{
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)
{
status_t status = _InitObject(shellParameters);
if (status != B_OK)
throw status;
ResizeToPreferred();
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)
{
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;
const char** argv = NULL;
_GetArgumentsFromMessage(archive, argv, argc);
status_t status = _InitObject(ShellParameters(argc, argv));
delete[] argv;
if (status != B_OK)
throw status;
bool useRect = false;
if ((archive->FindBool("use_rect", &useRect) == B_OK) && useRect)
SetTermSize(frame);
}
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);
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;
fCursor = TermPos(0, 0);
fTextBuffer = NULL;
fVisibleTextBuffer = NULL;
fVisibleTextBufferChanged = false;
fScrollBar = NULL;
fInline = NULL;
fTextForeColor = kBlackColor;
fTextBackColor = kWhiteColor;
fSelectForeColor = kWhiteColor;
fSelectBackColor = kBlackColor;
fCursorBackColor = fTextForeColor;
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;
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;
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);
ShellParameters modifiedShellParameters(shellParameters);
modifiedShellParameters.SetEncoding(fEncoding);
error = _AttachShell(fShell, modifiedShellParameters);
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 = _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;
}
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) const
{
int32 location = int32(y + fScrollOffset);
if (location < 0)
location -= fFontHeight - 1;
return location / fFontHeight;
}
inline float
TermView::_LineOffset(int32 index) const
{
return index * fFontHeight - fScrollOffset;
}
TermPos
TermView::_ConvertToTerminal(const BPoint &p) const
{
return TermPos(p.x >= 0 ? (int32)p.x / fFontWidth : -1, _LineAt(p.y));
}
inline BPoint
TermView::_ConvertFromTerminal(const TermPos &pos) const
{
return BPoint(fFontWidth * pos.x, _LineOffset(pos.y));
}
inline void
TermView::_InvalidateTextRect(int32 x1, int32 y1, int32 x2, int32 y2)
{
BRect rect(x1 * fFontWidth, _LineOffset(y1),
(x2 + 1) * fFontWidth * 2 - 1, _LineOffset(y2 + 1) - 1);
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();
}
void
TermView::GetFontSize(float* _width, float* _height) const
{
*_width = fFontWidth;
*_height = fFontHeight;
}
int
TermView::Rows() const
{
return fRows;
}
int
TermView::Columns() const
{
return fColumns;
}
BRect
TermView::SetTermSize(int rows, int columns, bool notifyShell)
{
if (rows == fRows && columns == fColumns)
return BRect(0, 0, fColumns * fFontWidth, fRows * fFontHeight);
if (rows > 0)
fRows = rows;
if (columns > 0)
fColumns = columns;
_Deselect();
{
BAutolock _(fTextBuffer);
if (fTextBuffer->ResizeTo(columns, rows) != B_OK
|| fVisibleTextBuffer->ResizeTo(columns, rows + 2, 0)
!= B_OK) {
return Bounds();
}
}
Invalidate();
if (fScrollBar != NULL) {
_UpdateScrollBarRange();
fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows);
}
BRect rect(0, 0, fColumns * fFontWidth, fRows * fFontHeight);
{
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) const
{
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) const
{
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;
}
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);
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;
int cursorStyle = PrefHandler::Default() == NULL ? BLOCK_CURSOR
: PrefHandler::Default()->getCursor(PREF_CURSOR_STYLE);
fTextBuffer->SetCursorStyle(cursorStyle);
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 = fTextBuffer->IsMode(MODE_CURSOR_BLINKING);
if (blinkingOn) {
if (fCursorBlinkRunner == NULL) {
BMessage blinkMessage(kBlinkCursor);
fCursorBlinkRunner = new (std::nothrow) BMessageRunner(
BMessenger(this), &blinkMessage, kCursorBlinkInterval);
}
} else {
fCursorState = 0;
delete fCursorBlinkRunner;
fCursorBlinkRunner = NULL;
}
_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
}
void
TermView::SwitchCursorBlinking(bool blinkingOn)
{
if (blinkingOn) {
fTextBuffer->SetMode(MODE_CURSOR_BLINKING);
} else {
fTextBuffer->ResetMode(MODE_CURSOR_BLINKING);
}
SwitchCursorBlinking();
}
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 ) {
bool useBracketedPaste = fTextBuffer->IsMode(MODE_BRACKETED_PASTE);
if (useBracketedPaste)
fShell->Write(BEGIN_BRACKETED_PASTE_CODE, strlen(BEGIN_BRACKETED_PASTE_CODE));
_WritePTY(text, numBytes);
if (useBracketedPaste)
fShell->Write(END_BRACKETED_PASTE_CODE, strlen(END_BRACKETED_PASTE_CODE));
}
clipboard->Unlock();
_ScrollTo(0, true);
}
}
void
TermView::SyncClipboard()
{
if (be_clipboard != fMouseClipboard && 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();
}
}
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);
Invalidate();
_ScrollTo(0, false);
if (fScrollBar) {
fScrollBar->SetRange(0, 0);
fScrollBar->SetProportion(1);
}
}
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);
}
}
void
TermView::_GetArgumentsFromMessage(const BMessage* message, const char**& argv, int32& argc)
{
type_code type;
if (message->GetInfo("argv", &type, &argc) == B_OK) {
argv = new const char*[argc + 1];
int32 i = 0;
while (message->FindString("argv", i, &argv[i]) == B_OK)
i++;
argv[i] = NULL;
}
}
status_t
TermView::_AttachShell(Shell *shell, const ShellParameters& shellParameters)
{
if (shell == NULL)
return B_BAD_VALUE;
status_t status = shell->Open(fRows, fColumns, shellParameters);
if (status != B_OK)
return status;
fShell = shell;
return fShell->AttachBuffer(TextBuffer());
}
Shell*
TermView::_DetachShell()
{
Shell* shell = fShell;
fShell->DetachBuffer();
fShell = NULL;
return shell;
}
void
TermView::_Activate()
{
fActive = true;
bool blink = PrefHandler::Default()->getBool(PREF_BLINK_CURSOR);
SwitchCursorBlinking(blink);
}
void
TermView::_Deactivate()
{
fCursorState = 0;
_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
SwitchCursorBlinking(false);
fActive = false;
}
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);
float x2 = x1 + fFontWidth * width;
float y2 = y1 + fFontHeight;
rgb_color rgb_fore = fTextForeColor;
rgb_color rgb_back = fTextBackColor;
rgb_color rgb_under = fTextForeColor;
rgb_color rgb_over = fTextForeColor;
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());
if (cursor) {
rgb_fore = fCursorForeColor;
rgb_back = fCursorBackColor;
} else if (highlight != NULL) {
rgb_fore = highlight->Highlighter()->ForegroundColor();
rgb_back = highlight->Highlighter()->BackgroundColor();
} else {
if (attr.IsInverse()) {
rgb_color rgb_tmp = rgb_fore;
rgb_fore = rgb_back;
rgb_back = rgb_tmp;
}
}
inView->SetHighColor(rgb_back);
inView->FillRect(BRect(x1, y1, x2 - 1, y2 - 1));
inView->SetLowColor(rgb_back);
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;
}
}
}
if (attr.IsOver()) {
inView->SetHighColor(rgb_over);
inView->MovePenTo(x1, y1);
inView->StrokeLine(BPoint(x1 , y1), BPoint(x2 , y1));
}
inView->SetHighColor(rgb_fore);
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<uint8>(bright.red, 64);
bright.green = saturated_add<uint8>(bright.green, 64);
bright.blue = saturated_add<uint8>(bright.blue, 64);
inView->SetHighColor(bright);
}
}
inView->MovePenTo(x1, y1 + fFontAscent);
inView->DrawString((char *)buf);
inView->SetDrawingMode(B_OP_COPY);
}
*/
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();
int32 cursorStyle = fTextBuffer->CursorStyle();
if (cursorVisible) {
switch (cursorStyle) {
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
&& (fTextBuffer->CursorStyle() == 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())
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() && cursorStyle != IBEAM_CURSOR)
rect.right += fFontWidth;
if (Window()->IsActive() && IsFocus()) {
FillRect(rect);
} else {
StrokeRect(rect);
}
}
}
bool
TermView::_IsCursorVisible() const
{
return !fTextBuffer->IsMode(MODE_CURSOR_HIDDEN) && 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;
}
void
TermView::_UpdateScrollBarRange()
{
if (fScrollBar == NULL)
return;
int32 historySize;
{
BAutolock _(fTextBuffer);
historySize = fTextBuffer->HistorySize();
}
float viewHeight = fRows * fFontHeight;
float historyHeight = (float)historySize * fFontHeight;
fScrollBar->SetRange(-historyHeight, 0);
if (historySize > 0)
fScrollBar->SetProportion(viewHeight / (viewHeight + historyHeight));
}
void
TermView::_UpdateSIGWINCH()
{
if (fFrameResized) {
fShell->UpdateWindowSize(fRows, fColumns);
fFrameResized = false;
}
}
void
TermView::AttachedToWindow()
{
fMouseButtons = 0;
_UpdateModifiers();
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);
if (y1 <= y2) {
float clearLeft = fColumns * fFontWidth;
if (clearLeft <= updateRect.right) {
BRect rect(clearLeft, updateRect.top, updateRect.right,
updateRect.bottom);
SetHighColor(fTextBackColor);
FillRect(rect);
}
}
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);
}
}
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);
int32 count = fVisibleTextBuffer->GetString(j - firstVisible, i,
lastColumn, buf, attr);
if (count == 0) {
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()) {
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);
i = nextColumn;
continue;
}
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++) {
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
}
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)
{
int32 columns = (int32)((width + 1) / fFontWidth);
int32 rows = (int32)((height + 1) / fFontHeight);
if (columns == fColumns && rows == fRows)
return;
bool hasResizeView = fResizeRunner != NULL;
if (!hasResizeView) {
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.SetToFormat("%" B_PRId32 " × %" B_PRId32, columns, 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";
if (message->WasDropped() && (message->what == B_SIMPLE_DATA
|| message->what == B_MIME_DATA)) {
char *text;
ssize_t numBytes;
int32 i = 0;
if (message->FindRef("refs", i++, &ref) == B_OK) {
int32 buttons = 0;
message->FindInt32("buttons", &buttons);
if (buttons == B_SECONDARY_MOUSE_BUTTON) {
_SecondaryMouseButtonDropped(message);
return;
}
_DoFileDrop(ref);
while (message->FindRef("refs", i++, &ref) == B_OK) {
_WritePTY(" ", 1);
_DoFileDrop(ref);
}
return;
} 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:
{
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:
SyncClipboard();
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_EXECUTE_PROPERTY:
{
int32 i;
BMessage specifier;
if (message->GetCurrentSpecifier(&i, &specifier) == B_OK
&& strcmp("command",
specifier.FindString("property", i)) == 0) {
Shell* shell = _DetachShell();
shell->Close();
int32 argc = 0;
const char** argv = NULL;
_GetArgumentsFromMessage(message, argv, argc);
if (message->GetBool("clear", false))
Clear();
ShellParameters shellParameters(argc, argv);
shellParameters.SetEncoding(fEncoding);
_AttachShell(shell, shellParameters);
delete[] argv;
message->SendReply(B_REPLY);
} 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:
{
BAutolock locker(fTextBuffer);
float deltaY = 0;
if (fTextBuffer->IsAlternateScreenActive()
&& message->FindFloat("be:wheel_delta_y", &deltaY) == B_OK
&& deltaY != 0) {
locker.Unlock();
int32 steps;
const char* stepString;
if ((modifiers() & B_SHIFT_KEY) != 0) {
stepString = deltaY > 0
? PAGE_DOWN_KEY_CODE : PAGE_UP_KEY_CODE;
steps = abs((int)deltaY);
} else {
stepString = deltaY > 0
? DOWN_ARROW_KEY_CODE : UP_ARROW_KEY_CODE;
steps = 3 * abs((int)deltaY);
}
BString toWrite;
for (int32 i = 0; i <steps; i++)
toWrite << stepString;
_WritePTY(toWrite.String(), toWrite.Length());
} else {
locker.Unlock();
BView::MessageReceived(message);
}
break;
}
case MENU_CLEAR_ALL:
Clear();
fShell->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:
{
SwitchCursorBlinking();
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)
{
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;
_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
BRect destRect(Frame().OffsetToCopy(Bounds().LeftTop()));
BRect sourceRect(destRect.OffsetByCopy(0, diff));
CopyBits(sourceRect, destRect);
if (newFirstLine != oldFirstLine || newLastLine != oldLastLine) {
if (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)
{
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);
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());
}
void
TermView::_DoFileDrop(entry_ref& ref)
{
BEntry ent(&ref);
BPath path(&ent);
BString string(path.Path());
string.CharacterEscape(kShellEscapeCharacters, '\\');
_WritePTY(string.String(), string.Length());
}
*/
void
TermView::_SynchronizeWithTextBuffer(int32 visibleDirtyTop,
int32 visibleDirtyBottom)
{
TerminalBufferDirtyInfo& info = fTextBuffer->DirtyInfo();
int32 linesScrolled = info.linesScrolled;
bigtime_t now = system_time();
bigtime_t timeElapsed = now - fLastSyncTime;
if (timeElapsed > 2 * kSyncUpdateGranularity) {
fLastSyncTime = now;
fScrolledSinceLastSync = linesScrolled;
}
if (fSyncRunner == NULL) {
if (fScrolledSinceLastSync + linesScrolled <= fRows) {
if (timeElapsed > kSyncUpdateGranularity) {
fConsiderClockedSync = false;
fLastSyncTime = now;
fScrolledSinceLastSync = linesScrolled;
} else
fScrolledSinceLastSync += linesScrolled;
} else if (fConsiderClockedSync) {
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 {
fConsiderClockedSync = true;
fLastSyncTime = now;
fScrolledSinceLastSync = 0;
}
} else if (timeElapsed < kSyncUpdateGranularity) {
fScrolledSinceLastSync += linesScrolled;
return;
}
if (fScrolledSinceLastSync + linesScrolled <= fRows) {
delete fSyncRunner;
fSyncRunner = NULL;
fLastSyncTime = now;
fScrolledSinceLastSync = linesScrolled;
} else {
fLastSyncTime = now;
fScrolledSinceLastSync = 0;
}
fVisibleTextBufferChanged = true;
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;
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)) {
float scrollBy = linesScrolled * fFontHeight;
BRect destRect(Frame().OffsetToCopy(B_ORIGIN));
BRect sourceRect(destRect.OffsetByCopy(0, scrollBy));
_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
CopyBits(sourceRect, destRect);
fVisibleTextBuffer->ScrollBy(linesScrolled);
}
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);
}
}
}
if (info.IsDirtyRegionValid()) {
_InvalidateTextRect(0, info.dirtyTop, fTextBuffer->Width() - 1,
info.dirtyBottom);
if (!fSelection.IsEmpty()) {
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);
}
TermPos cursor = fTextBuffer->Cursor();
if (fCursor != cursor || linesScrolled != 0) {
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();
}
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);
}
}
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 (!fTextBuffer->IsMode(MODE_EXTENDED_MOUSE_COORDINATES)) {
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;
if (buttons != 0 && motion && fTextBuffer->IsMode(MODE_REPORT_BUTTON_MOUSE_EVENT))
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;
if (buttons == 0 && motion && fTextBuffer->IsMode(MODE_REPORT_BUTTON_MOUSE_EVENT))
return;
if (buttons != 0 && motion && fTextBuffer->IsMode(MODE_REPORT_BUTTON_MOUSE_EVENT))
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;
}
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++;
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 (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());
}
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);
}
void
TermView::_Deselect()
{
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) {
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) {
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) const
{
int32 historySize;
{
BAutolock _(fTextBuffer);
historySize = fTextBuffer->HistorySize();
}
if (width != NULL)
*width = fColumns * fFontWidth;
if (height != NULL)
*height = (fRows + historySize) * fFontHeight;
}
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 {
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;
}
void
TermView::GetSelection(BString &str) const
{
str.SetTo("");
BAutolock _(fTextBuffer);
fTextBuffer->GetStringFromRegion(str, fSelection.Start(), fSelection.End());
}
bool
TermView::CheckShellGone() const
{
if (!fShell)
return false;
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) {
if (startY <= 0 && endY >= height)
return;
if (startY > 0) {
_ScrollTo(fScrollOffset + startY, true);
} else {
_ScrollTo(fScrollOffset + endY - height, true);
}
} else {
if (startY >= 0 && endY <= height)
return;
if (startY < 0) {
_ScrollTo(fScrollOffset + startY, true);
} else {
_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();
bool confirmed;
if (message->FindBool("be:confirmed", &confirmed) != B_OK)
confirmed = false;
fInline->SetString("");
Invalidate();
snooze(100000);
fInline->SetString(string);
fInline->ResetClauses();
if (!confirmed && !fInline->IsActive())
fInline->SetActive(true);
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);
const char* currPos = string;
const char* prevPos = currPos;
while (*currPos != '\0') {
if ((*currPos & 0xC0) == 0xC0) {
++currPos;
while ((*currPos & 0xC0) == 0x80)
++currPos;
} else if ((*currPos & 0xC0) == 0x80) {
prevPos = ++currPos;
} else {
++currPos;
}
KeyDown(prevPos, currPos - prevPos);
prevPos = currPos;
}
} else {
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++) {
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()
{
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();
}
}
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)
{
}
#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