3218 lines
73 KiB
C++
3218 lines
73 KiB
C++
/*
|
|
* Copyright 2001-2014, Haiku, Inc.
|
|
* 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 <signal.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <termios.h>
|
|
|
|
#include <algorithm>
|
|
#include <new>
|
|
#include <vector>
|
|
|
|
#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."},
|
|
{ 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<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);
|
|
}
|
|
|
|
|
|
// #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;
|
|
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 ) {
|
|
_WritePTY(text, numBytes);
|
|
}
|
|
|
|
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, uint32 attr, char *buf,
|
|
int32 width, Highlight* highlight, bool cursor, BView *inView)
|
|
{
|
|
if (highlight != NULL)
|
|
attr = highlight->Highlighter()->AdjustTextAttributes(attr);
|
|
|
|
inView->SetFont(IS_BOLD(attr) && !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;
|
|
|
|
// color attribute
|
|
int forecolor = IS_FORECOLOR(attr);
|
|
int backcolor = IS_BACKCOLOR(attr);
|
|
|
|
if (IS_FORESET(attr))
|
|
rgb_fore = fTextBuffer->PaletteColor(forecolor);
|
|
if (IS_BACKSET(attr))
|
|
rgb_back = fTextBuffer->PaletteColor(backcolor);
|
|
|
|
// 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 (IS_INVERSE(attr)) {
|
|
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);
|
|
inView->SetHighColor(rgb_fore);
|
|
|
|
// Draw character.
|
|
if (IS_BOLD(attr)) {
|
|
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);
|
|
|
|
// underline attribute
|
|
if (IS_UNDER(attr)) {
|
|
inView->MovePenTo(x1, y1 + fFontAscent);
|
|
inView->StrokeLine(BPoint(x1 , y1 + fFontAscent),
|
|
BPoint(x2 , y1 + fFontAscent));
|
|
}
|
|
}
|
|
|
|
|
|
/*! 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;
|
|
uint32 attr = 0;
|
|
|
|
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 = IS_WIDTH(attr) ? 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
|
|
attr = fVisibleTextBuffer->GetLineColor(
|
|
fCursor.y - firstVisible);
|
|
|
|
if (IS_BACKSET(attr))
|
|
rgb_back = fTextBuffer->PaletteColor(IS_BACKCOLOR(attr));
|
|
SetHighColor(rgb_back);
|
|
}
|
|
|
|
if (IS_WIDTH(attr) && 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) {
|
|
uint32 attr = 0;
|
|
|
|
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
|
|
attr = fVisibleTextBuffer->GetLineColor(j - firstVisible);
|
|
|
|
if (IS_BACKSET(attr)) {
|
|
int backcolor = IS_BACKCOLOR(attr);
|
|
rgb_back = fTextBuffer->PaletteColor(backcolor);
|
|
}
|
|
|
|
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 (IS_WIDTH(attr))
|
|
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 <steps; i++)
|
|
toWrite << stepString;
|
|
|
|
_WritePTY(toWrite.String(), toWrite.Length());
|
|
} else {
|
|
// let the BView's implementation handle the standard scrolling
|
|
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:
|
|
{
|
|
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_REPORT_MOUSE_EVENT:
|
|
{
|
|
bool value;
|
|
if (message->FindBool("reportX10MouseEvent", &value) == B_OK)
|
|
fReportX10MouseEvent = value;
|
|
|
|
if (message->FindBool("reportNormalMouseEvent", &value) == B_OK)
|
|
fReportNormalMouseEvent = value;
|
|
|
|
if (message->FindBool("reportButtonMouseEvent", &value) == B_OK)
|
|
fReportButtonMouseEvent = value;
|
|
|
|
if (message->FindBool("reportAnyMouseEvent", &value) == B_OK)
|
|
fReportAnyMouseEvent = value;
|
|
|
|
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)
|
|
{
|
|
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;
|
|
|
|
if (motion)
|
|
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)
|
|
xtermButtons = 0;
|
|
else if (buttons == B_SECONDARY_MOUSE_BUTTON)
|
|
xtermButtons = 1;
|
|
else if (buttons == B_TERTIARY_MOUSE_BUTTON)
|
|
xtermButtons = 2;
|
|
else
|
|
xtermButtons = 3;
|
|
|
|
if (motion)
|
|
xtermButtons += 32;
|
|
|
|
int16 xtermX = x + 1;
|
|
int16 xtermY = y + 1;
|
|
|
|
char destBuffer[13];
|
|
destBuffer[0] = '\033';
|
|
destBuffer[1] = '[';
|
|
destBuffer[2] = '<';
|
|
destBuffer[3] = xtermButtons + '0';
|
|
destBuffer[4] = ';';
|
|
destBuffer[5] = xtermX / 100 % 10 + '0';
|
|
destBuffer[6] = xtermX / 10 % 10 + '0';
|
|
destBuffer[7] = xtermX % 10 + '0';
|
|
destBuffer[8] = ';';
|
|
destBuffer[9] = xtermY / 100 % 10 + '0';
|
|
destBuffer[10] = xtermY / 10 % 10 + '0';
|
|
destBuffer[11] = xtermY % 10 + '0';
|
|
// No support for button press/release
|
|
destBuffer[12] = 'M';
|
|
fShell->Write(destBuffer, 13);
|
|
}
|
|
}
|
|
|
|
|
|
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
|