SerialConnect: implement scrollback.

This commit is contained in:
Adrien Destugues
2014-08-12 23:13:37 +02:00
parent d18836726c
commit b59ed15486
4 changed files with 238 additions and 109 deletions
+51 -41
View File
@@ -13,6 +13,7 @@
#include <Menu.h>
#include <MenuBar.h>
#include <MenuItem.h>
#include <ScrollView.h>
#include <SerialPort.h>
#include "SerialApp.h"
@@ -41,13 +42,30 @@ SerialWindow::SerialWindow()
B_DOCUMENT_WINDOW, B_QUIT_ON_WINDOW_CLOSE | B_AUTO_UPDATE_SIZE_LIMITS)
, fLogFilePanel(NULL)
{
SetLayout(new BGroupLayout(B_VERTICAL, 0.0f));
BMenuBar* menuBar = new BMenuBar(Bounds(), "menuBar");
menuBar->ResizeToPreferred();
BMenuBar* menuBar = new BMenuBar("menuBar");
fTermView = new TermView();
BRect r = Bounds();
r.top = menuBar->Bounds().bottom + 1;
r.right -= B_V_SCROLL_BAR_WIDTH;
fTermView = new TermView(r);
fTermView->ResizeToPreferred();
r = fTermView->Frame();
r.left = r.right + 1;
r.right = r.left + B_V_SCROLL_BAR_WIDTH;
r.top -= 1;
r.bottom -= B_H_SCROLL_BAR_HEIGHT - 1;
BScrollBar* scrollBar = new BScrollBar(r, "scrollbar", NULL, 0, 0,
B_VERTICAL);
scrollBar->SetTarget(fTermView);
ResizeTo(r.right - 1, r.bottom + B_H_SCROLL_BAR_HEIGHT - 1);
AddChild(menuBar);
AddChild(fTermView);
AddChild(scrollBar);
fConnectionMenu = new BMenu("Connection");
BMenu* fileMenu = new BMenu("File");
@@ -62,7 +80,7 @@ SerialWindow::SerialWindow()
// TODO edit menu - what's in it ?
//BMenu* editMenu = new BMenu("Edit");
//menuBar->AddItem(editMenu);
BMenuItem* logFile = new BMenuItem("Log to file" B_UTF8_ELLIPSIS,
new BMessage(kMsgLogfile));
fileMenu->AddItem(logFile);
@@ -78,23 +96,23 @@ SerialWindow::SerialWindow()
// Configuring all this by menus may be a bit unhandy. Make a setting
// window instead ?
fBaudrateMenu = new BMenu("Baud rate");
fBaudrateMenu = new BMenu("Baud rate");
fBaudrateMenu->SetRadioMode(true);
settingsMenu->AddItem(fBaudrateMenu);
fParityMenu = new BMenu("Parity");
fParityMenu = new BMenu("Parity");
fParityMenu->SetRadioMode(true);
settingsMenu->AddItem(fParityMenu);
fStopbitsMenu = new BMenu("Stop bits");
fStopbitsMenu = new BMenu("Stop bits");
fStopbitsMenu->SetRadioMode(true);
settingsMenu->AddItem(fStopbitsMenu);
fFlowcontrolMenu = new BMenu("Flow control");
fFlowcontrolMenu = new BMenu("Flow control");
fFlowcontrolMenu->SetRadioMode(true);
settingsMenu->AddItem(fFlowcontrolMenu);
fDatabitsMenu = new BMenu("Data bits");
fDatabitsMenu = new BMenu("Data bits");
fDatabitsMenu->SetRadioMode(true);
settingsMenu->AddItem(fDatabitsMenu);
@@ -147,7 +165,7 @@ SerialWindow::SerialWindow()
message->AddInt32("baudrate", kBaudrateConstants[i]);
char buffer[7];
sprintf(buffer,"%d", kBaudrates[i]);
sprintf(buffer, "%d", kBaudrates[i]);
BMenuItem* item = new BMenuItem(buffer, message);
fBaudrateMenu->AddItem(item);
@@ -181,7 +199,6 @@ SerialWindow::SerialWindow()
}
SerialWindow::~SerialWindow()
{
delete fLogFilePanel;
@@ -198,7 +215,7 @@ void SerialWindow::MenusBeginning()
int deviceCount = serialPort.CountDevices();
bool connected = false;
for(int i = 0; i < deviceCount; i++)
for (int i = 0; i < deviceCount; i++)
{
char buffer[256];
serialPort.GetDeviceName(i, buffer, 256);
@@ -210,7 +227,7 @@ void SerialWindow::MenusBeginning()
const BString& connectedPort = ((SerialApp*)be_app)->GetPort();
if(connectedPort == buffer) {
if (connectedPort == buffer) {
connected = true;
portItem->SetMarked(true);
}
@@ -223,7 +240,7 @@ void SerialWindow::MenusBeginning()
BMenuItem* disconnect = new BMenuItem("Disconnect",
new BMessage(kMsgOpenPort), 'Z', B_OPTION_KEY);
if(!connected)
if (!connected)
disconnect->SetEnabled(false);
fConnectionMenu->AddItem(disconnect);
} else {
@@ -233,6 +250,7 @@ void SerialWindow::MenusBeginning()
}
}
void SerialWindow::MessageReceived(BMessage* message)
{
switch(message->what)
@@ -240,7 +258,7 @@ void SerialWindow::MessageReceived(BMessage* message)
case kMsgOpenPort:
{
BMenuItem* disconnectMenu;
if(message->FindPointer("source", (void**)&disconnectMenu) == B_OK)
if (message->FindPointer("source", (void**)&disconnectMenu) == B_OK)
disconnectMenu->SetMarked(false);
be_app->PostMessage(new BMessage(*message));
break;
@@ -257,7 +275,7 @@ void SerialWindow::MessageReceived(BMessage* message)
case kMsgLogfile:
{
// Let's lazy init the file panel
if(fLogFilePanel == NULL) {
if (fLogFilePanel == NULL) {
fLogFilePanel = new BFilePanel(B_SAVE_PANEL, &be_app_messenger,
NULL, B_FILE_NODE, false);
fLogFilePanel->SetMessage(message);
@@ -273,71 +291,63 @@ void SerialWindow::MessageReceived(BMessage* message)
parity_mode parity;
uint32 flowcontrol;
if(message->FindInt32("databits", (int32*)&dataBits) == B_OK)
{
for(int i = 0; i < fDatabitsMenu->CountItems(); i++)
{
if (message->FindInt32("databits", (int32*)&dataBits) == B_OK) {
for (int i = 0; i < fDatabitsMenu->CountItems(); i++) {
BMenuItem* item = fDatabitsMenu->ItemAt(i);
int32 code;
item->Message()->FindInt32("databits", &code);
if(code == dataBits)
if (code == dataBits)
item->SetMarked(true);
}
}
if(message->FindInt32("stopbits", (int32*)&stopBits) == B_OK)
{
for(int i = 0; i < fStopbitsMenu->CountItems(); i++)
{
if (message->FindInt32("stopbits", (int32*)&stopBits) == B_OK) {
for (int i = 0; i < fStopbitsMenu->CountItems(); i++) {
BMenuItem* item = fStopbitsMenu->ItemAt(i);
int32 code;
item->Message()->FindInt32("stopbits", &code);
if(code == stopBits)
if (code == stopBits)
item->SetMarked(true);
}
}
if(message->FindInt32("parity", (int32*)&parity) == B_OK)
if (message->FindInt32("parity", (int32*)&parity) == B_OK)
{
for(int i = 0; i < fParityMenu->CountItems(); i++)
{
for (int i = 0; i < fParityMenu->CountItems(); i++) {
BMenuItem* item = fParityMenu->ItemAt(i);
int32 code;
item->Message()->FindInt32("parity", &code);
if(code == parity)
if (code == parity)
item->SetMarked(true);
}
}
if(message->FindInt32("flowcontrol", (int32*)&flowcontrol) == B_OK)
{
for(int i = 0; i < fFlowcontrolMenu->CountItems(); i++)
{
if (message->FindInt32("flowcontrol", (int32*)&flowcontrol)
== B_OK) {
for (int i = 0; i < fFlowcontrolMenu->CountItems(); i++) {
BMenuItem* item = fFlowcontrolMenu->ItemAt(i);
int32 code;
item->Message()->FindInt32("flowcontrol", &code);
if(code == (int32)flowcontrol)
if (code == (int32)flowcontrol)
item->SetMarked(true);
}
}
if(message->FindInt32("baudrate", &baudrate) == B_OK)
{
for(int i = 0; i < fBaudrateMenu->CountItems(); i++)
{
if (message->FindInt32("baudrate", &baudrate) == B_OK) {
for (int i = 0; i < fBaudrateMenu->CountItems(); i++) {
BMenuItem* item = fBaudrateMenu->ItemAt(i);
int32 code;
item->Message()->FindInt32("baudrate", &code);
if(baudrate == code)
if (baudrate == code)
item->SetMarked(true);
}
}
break;
}
default:
+1
View File
@@ -23,6 +23,7 @@ class SerialWindow: public BWindow
private:
TermView* fTermView;
BMenu* fConnectionMenu;
BMenu* fDatabitsMenu;
BMenu* fStopbitsMenu;
+155 -52
View File
@@ -11,27 +11,30 @@
#include <Entry.h>
#include <File.h>
#include <Layout.h>
#include <ScrollBar.h>
#include "SerialApp.h"
struct ScrollBufferItem {
int cols;
VTermScreenCell cells[];
};
TermView::TermView()
:
BView("TermView", B_WILL_DRAW | B_FRAME_EVENTS)
{
SetFont(be_fixed_font);
_Init();
}
font_height height;
GetFontHeight(&height);
fFontHeight = height.ascent + height.descent + height.leading + 1;
fFontWidth = be_fixed_font->StringWidth("X");
fTerm = vterm_new(kDefaultHeight, kDefaultWidth);
fTermScreen = vterm_obtain_screen(fTerm);
vterm_screen_set_callbacks(fTermScreen, &sScreenCallbacks, this);
vterm_screen_reset(fTermScreen, 1);
vterm_parser_set_utf8(fTerm, 1);
TermView::TermView(BRect r)
:
BView(r, "TermView", B_FOLLOW_ALL_SIDES, B_WILL_DRAW | B_FRAME_EVENTS)
{
_Init();
}
@@ -41,7 +44,8 @@ TermView::~TermView()
}
void TermView::AttachedToWindow()
void
TermView::AttachedToWindow()
{
MakeFocus();
@@ -49,7 +53,7 @@ void TermView::AttachedToWindow()
VTermPos firstPos;
firstPos.row = 0;
firstPos.col = 0;
vterm_screen_get_cell(fTermScreen, firstPos, &cell);
_GetCell(firstPos, cell);
rgb_color background;
background.red = cell.bg.red;
@@ -61,17 +65,15 @@ void TermView::AttachedToWindow()
}
void TermView::Draw(BRect updateRect)
void
TermView::Draw(BRect updateRect)
{
VTermRect updatedChars = PixelsToGlyphs(updateRect);
VTermRect updatedChars = _PixelsToGlyphs(updateRect);
VTermPos pos;
font_height height;
GetFontHeight(&height);
int availableRows, availableCols;
vterm_get_size(fTerm, &availableRows, &availableCols);
for (pos.row = updatedChars.start_row; pos.row <= updatedChars.end_row;
pos.row++) {
float x = updatedChars.start_col * fFontWidth + kBorderSpacing;
@@ -82,19 +84,7 @@ void TermView::Draw(BRect updateRect)
pos.col <= updatedChars.end_col;) {
VTermScreenCell cell;
if (pos.col < 0 || pos.row < 0 || pos.col >= availableCols
|| pos.row >= availableRows) {
// All cells outside the used terminal area are drawn with the
// same background color as the top-left one.
VTermPos firstPos;
firstPos.row = 0;
firstPos.col = 0;
vterm_screen_get_cell(fTermScreen, firstPos, &cell);
cell.chars[0] = 0;
cell.width = 1;
} else
vterm_screen_get_cell(fTermScreen, pos, &cell);
_GetCell(pos, cell);
rgb_color foreground, background;
foreground.red = cell.fg.red;
@@ -119,7 +109,7 @@ void TermView::Draw(BRect updateRect)
BPoint penLocation = PenLocation();
FillRect(BRect(penLocation.x, penLocation.y - height.ascent,
penLocation.x + cell.width * fFontWidth - 1,
penLocation.y + height.descent + height.leading - 1),
penLocation.y + height.descent + height.leading),
B_SOLID_LOW);
if (cell.chars[0] == 0) {
@@ -138,15 +128,17 @@ void TermView::Draw(BRect updateRect)
}
void TermView::FrameResized(float width, float height)
void
TermView::FrameResized(float width, float height)
{
VTermRect newSize = PixelsToGlyphs(BRect(0, 0, width - 2 * kBorderSpacing,
VTermRect newSize = _PixelsToGlyphs(BRect(0, 0, width - 2 * kBorderSpacing,
height - 2 * kBorderSpacing));
vterm_set_size(fTerm, newSize.end_row, newSize.end_col);
}
void TermView::GetPreferredSize(float* width, float* height)
void
TermView::GetPreferredSize(float* width, float* height)
{
if (width != NULL)
*width = kDefaultWidth * fFontWidth + 2 * kBorderSpacing;
@@ -155,7 +147,8 @@ void TermView::GetPreferredSize(float* width, float* height)
}
void TermView::KeyDown(const char* bytes, int32 numBytes)
void
TermView::KeyDown(const char* bytes, int32 numBytes)
{
BMessage* keyEvent = new BMessage(kMsgDataWrite);
keyEvent->AddData("data", B_RAW_TYPE, bytes, numBytes);
@@ -163,7 +156,8 @@ void TermView::KeyDown(const char* bytes, int32 numBytes)
}
void TermView::MessageReceived(BMessage* message)
void
TermView::MessageReceived(BMessage* message)
{
switch(message->what)
{
@@ -183,7 +177,8 @@ void TermView::MessageReceived(BMessage* message)
}
void TermView::PushBytes(const char* bytes, size_t length)
void
TermView::PushBytes(const char* bytes, size_t length)
{
vterm_push_bytes(fTerm, bytes, length);
}
@@ -192,7 +187,27 @@ void TermView::PushBytes(const char* bytes, size_t length)
// #pragma mark -
VTermRect TermView::PixelsToGlyphs(BRect pixels) const
void
TermView::_Init()
{
SetFont(be_fixed_font);
font_height height;
GetFontHeight(&height);
fFontHeight = height.ascent + height.descent + height.leading + 1;
fFontWidth = be_fixed_font->StringWidth("X");
fTerm = vterm_new(kDefaultHeight, kDefaultWidth);
fTermScreen = vterm_obtain_screen(fTerm);
vterm_screen_set_callbacks(fTermScreen, &sScreenCallbacks, this);
vterm_screen_reset(fTermScreen, 1);
vterm_parser_set_utf8(fTerm, 1);
}
VTermRect
TermView::_PixelsToGlyphs(BRect pixels) const
{
pixels.OffsetBy(-kBorderSpacing, -kBorderSpacing);
@@ -201,7 +216,7 @@ VTermRect TermView::PixelsToGlyphs(BRect pixels) const
rect.end_col = (int)ceil(pixels.right / fFontWidth);
rect.start_row = (int)floor(pixels.top / fFontHeight);
rect.end_row = (int)ceil(pixels.bottom / fFontHeight);
/*
#if 0
printf(
"TOP %d ch < %f px\n"
"BTM %d ch < %f px\n"
@@ -212,12 +227,12 @@ VTermRect TermView::PixelsToGlyphs(BRect pixels) const
rect.start_col, pixels.left,
rect.end_col, pixels.right
);
*/
#endif
return rect;
}
BRect TermView::GlyphsToPixels(const VTermRect& glyphs) const
BRect TermView::_GlyphsToPixels(const VTermRect& glyphs) const
{
BRect rect;
rect.top = glyphs.start_row * fFontHeight;
@@ -242,40 +257,128 @@ BRect TermView::GlyphsToPixels(const VTermRect& glyphs) const
}
BRect TermView::GlyphsToPixels(const int width, const int height) const
BRect
TermView::_GlyphsToPixels(const int width, const int height) const
{
VTermRect rect;
rect.start_row = 0;
rect.start_col = 0;
rect.end_row = height;
rect.end_col = width;
return GlyphsToPixels(rect);
return _GlyphsToPixels(rect);
}
void TermView::Damage(VTermRect rect)
void
TermView::_GetCell(VTermPos pos, VTermScreenCell& cell)
{
Invalidate();
// Invalidate(GlyphsToPixels(rect));
int availableRows, availableCols;
vterm_get_size(fTerm, &availableRows, &availableCols);
if (pos.col < 0 || pos.row < -kScrollBackSize || pos.col >= availableCols
|| pos.row >= availableRows) {
// All cells outside the used terminal area are drawn with the same
// background color as the top-left one.
// TODO should they use the attributes of the closest neighbor instead?
VTermPos firstPos;
firstPos.row = 0;
firstPos.col = 0;
vterm_screen_get_cell(fTermScreen, firstPos, &cell);
cell.chars[0] = 0;
cell.width = 1;
} else if (pos.row < 0) {
// This is a cell from the scroll-back buffer
int offset = - pos.row - 1;
ScrollBufferItem* line = (ScrollBufferItem*)fScrollBuffer.ItemAt(offset);
if (line == NULL || pos.col >= line->cols) {
VTermPos firstPos;
firstPos.row = 0;
firstPos.col = 0;
vterm_screen_get_cell(fTermScreen, firstPos, &cell);
cell.chars[0] = 0;
cell.width = 1;
} else
cell = line->cells[pos.col];
} else
vterm_screen_get_cell(fTermScreen, pos, &cell);
}
/* static */
int TermView::Damage(VTermRect rect, void* user)
void
TermView::_Damage(VTermRect rect)
{
// Invalidate();
Invalidate(_GlyphsToPixels(rect));
}
void
TermView::_PushLine(int cols, const VTermScreenCell* cells)
{
ScrollBufferItem* item = (ScrollBufferItem*)malloc(sizeof(int)
+ cols * sizeof(VTermScreenCell));
item->cols = cols;
memcpy(item->cells, cells, cols * sizeof(VTermScreenCell));
fScrollBuffer.AddItem(item, 0);
free(fScrollBuffer.RemoveItem(kScrollBackSize));
int availableRows, availableCols;
vterm_get_size(fTerm, &availableRows, &availableCols);
VTermRect dirty;
dirty.start_col = 0;
dirty.end_col = availableCols;
dirty.end_row = 0;
dirty.start_row = -fScrollBuffer.CountItems();
// FIXME we should rather use CopyRect if possible, and only invalidate the
// newly exposed area here.
Invalidate(_GlyphsToPixels(dirty));
BScrollBar* scrollBar = ScrollBar(B_VERTICAL);
if (scrollBar != NULL) {
// FIXME this is not exactly right, it's off by a few pixels so one step
// isn't exactly equal to one line.
float range = (fScrollBuffer.CountItems() + availableRows) * fFontHeight;
scrollBar->SetRange(availableRows * fFontHeight - range, 0.0f);
// TODO we need to adjust this in FrameResized, as availableRows can
// change
scrollBar->SetProportion(availableRows * fFontHeight / range);
scrollBar->SetSteps(fFontHeight, fFontHeight * 3);
}
}
/* static */ int
TermView::_Damage(VTermRect rect, void* user)
{
TermView* view = (TermView*)user;
view->Damage(rect);
view->_Damage(rect);
return 0;
}
const VTermScreenCallbacks TermView::sScreenCallbacks = {
&TermView::Damage,
/* static */ int
TermView::_PushLine(int cols, const VTermScreenCell* cells, void* user)
{
TermView* view = (TermView*)user;
view->_PushLine(cols, cells);
return 0;
}
const
VTermScreenCallbacks TermView::sScreenCallbacks = {
&TermView::_Damage,
/*.moverect =*/ NULL,
/*.movecursor =*/ NULL,
/*.settermprop =*/ NULL,
/*.setmousefunc =*/ NULL,
/*.bell =*/ NULL,
/*.resize =*/ NULL,
&TermView::_PushLine,
/*.sb_popline =*/ NULL,
};
+31 -16
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2012, Adrien Destugues, [email protected]
* Copyright 2012-2014, Adrien Destugues, [email protected]
* Distributed under the terms of the MIT licence.
*/
@@ -10,31 +10,45 @@ extern "C" {
#include <vterm.h>
}
class TermView: public BView
{
public:
TermView();
~TermView();
TermView();
TermView(BRect bounds);
~TermView();
void AttachedToWindow();
void Draw(BRect updateRect);
void FrameResized(float width, float height);
void GetPreferredSize(float* width, float* height);
void KeyDown(const char* bytes, int32 numBytes);
void MessageReceived(BMessage* message);
void PushBytes(const char* bytes, const size_t length);
void AttachedToWindow();
void Draw(BRect updateRect);
void FrameResized(float width, float height);
void GetPreferredSize(float* width, float* height);
void KeyDown(const char* bytes, int32 numBytes);
void MessageReceived(BMessage* message);
void PushBytes(const char* bytes, const size_t length);
private:
VTermRect PixelsToGlyphs(BRect pixels) const;
BRect GlyphsToPixels(const VTermRect& glyphs) const;
BRect GlyphsToPixels(const int width, const int height) const;
void Damage(VTermRect rect);
void _Init();
static int Damage(VTermRect rect, void* user);
VTermRect _PixelsToGlyphs(BRect pixels) const;
BRect _GlyphsToPixels(const VTermRect& glyphs) const;
BRect _GlyphsToPixels(const int width, const int height)
const;
void _GetCell(VTermPos pos, VTermScreenCell& cell);
void _Damage(VTermRect rect);
void _PushLine(int cols, const VTermScreenCell* cells);
static int _Damage(VTermRect rect, void* user);
static int _PushLine(int cols, const VTermScreenCell* cells,
void* user);
static int _PopLine(int cols, const VTermScreenCell* cells,
void* user);
private:
VTerm* fTerm;
VTermScreen* fTermScreen;
BList fScrollBuffer;
float fFontWidth;
float fFontHeight;
@@ -43,4 +57,5 @@ class TermView: public BView
static const int kDefaultWidth = 80;
static const int kDefaultHeight = 25;
static const int kBorderSpacing = 3;
static const int kScrollBackSize = 1000;
};