Browse Source

Convert RPC console to QTextEdit instead of QTableView

* This allows copy/pasting whole or partial messages
* Handle output more consistently in console
    * No more scrollbars-in-scrollbars: by setting per-pixel scrolling on the table, cells can have any height
* Decorations for "request" and "reply" are changed to the txin and txout icons instead of colored squares
miguelfreitas
Wladimir J. van der Laan 13 years ago
parent
commit
c6aa86afc2
  1. 22
      src/qt/forms/rpcconsole.ui
  2. 124
      src/qt/rpcconsole.cpp
  3. 5
      src/qt/rpcconsole.h

22
src/qt/forms/rpcconsole.ui

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>706</width> <width>706</width>
<height>382</height> <height>446</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -327,30 +327,22 @@
<number>3</number> <number>3</number>
</property> </property>
<item> <item>
<widget class="QTableWidget" name="messagesWidget"> <widget class="QTextEdit" name="messagesWidget">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>0</width> <width>0</width>
<height>100</height> <height>100</height>
</size> </size>
</property> </property>
<property name="tabKeyNavigation"> <property name="readOnly">
<bool>false</bool> <bool>true</bool>
</property> </property>
<property name="selectionBehavior"> <property name="tabKeyNavigation" stdset="0">
<enum>QAbstractItemView::SelectRows</enum> <bool>false</bool>
</property> </property>
<property name="columnCount"> <property name="columnCount" stdset="0">
<number>2</number> <number>2</number>
</property> </property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<column/>
<column/>
</widget> </widget>
</item> </item>
<item> <item>

124
src/qt/rpcconsole.cpp

@ -10,6 +10,7 @@
#include <QThread> #include <QThread>
#include <QTextEdit> #include <QTextEdit>
#include <QKeyEvent> #include <QKeyEvent>
#include <QUrl>
#include <boost/tokenizer.hpp> #include <boost/tokenizer.hpp>
@ -19,6 +20,19 @@
const int CONSOLE_SCROLLBACK = 50; const int CONSOLE_SCROLLBACK = 50;
const int CONSOLE_HISTORY = 50; const int CONSOLE_HISTORY = 50;
const QSize ICON_SIZE(24, 24);
const struct {
const char *url;
const char *source;
} ICON_MAPPING[] = {
{"cmd-request", ":/icons/tx_input"},
{"cmd-reply", ":/icons/tx_output"},
{"cmd-error", ":/icons/tx_output"},
{"misc", ":/icons/tx_inout"},
{NULL, NULL}
};
/* Object for executing console RPC commands in a separate thread. /* Object for executing console RPC commands in a separate thread.
*/ */
class RPCExecutor: public QObject class RPCExecutor: public QObject
@ -83,12 +97,9 @@ void RPCExecutor::request(const QString &command)
RPCConsole::RPCConsole(QWidget *parent) : RPCConsole::RPCConsole(QWidget *parent) :
QDialog(parent), QDialog(parent),
ui(new Ui::RPCConsole), ui(new Ui::RPCConsole),
firstLayout(true),
historyPtr(0) historyPtr(0)
{ {
ui->setupUi(this); ui->setupUi(this);
ui->messagesWidget->horizontalHeader()->setResizeMode(1, QHeaderView::Stretch);
ui->messagesWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
#ifndef WIN32 #ifndef WIN32
// Show Debug logfile label and Open button only for Windows // Show Debug logfile label and Open button only for Windows
@ -99,13 +110,6 @@ RPCConsole::RPCConsole(QWidget *parent) :
// Install event filter for up and down arrow // Install event filter for up and down arrow
ui->lineEdit->installEventFilter(this); ui->lineEdit->installEventFilter(this);
// Add "Copy message" to context menu explicitly
QAction *copyMessageAction = new QAction(tr("&Copy"), this);
copyMessageAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_C));
copyMessageAction->setShortcutContext(Qt::WidgetShortcut);
connect(copyMessageAction, SIGNAL(triggered()), this, SLOT(copyMessage()));
ui->messagesWidget->addAction(copyMessageAction);
connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear())); connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear()));
connect(ui->openDebugLogfileButton, SIGNAL(clicked()), this, SLOT(on_openDebugLogfileButton_clicked())); connect(ui->openDebugLogfileButton, SIGNAL(clicked()), this, SLOT(on_openDebugLogfileButton_clicked()));
@ -159,68 +163,62 @@ void RPCConsole::setClientModel(ClientModel *model)
} }
} }
static QColor categoryColor(int category) static QString categoryClass(int category)
{ {
switch(category) switch(category)
{ {
case RPCConsole::MC_ERROR: return QColor(255,0,0); break; case RPCConsole::CMD_REQUEST: return "cmd-request"; break;
case RPCConsole::MC_DEBUG: return QColor(192,192,192); break; case RPCConsole::CMD_REPLY: return "cmd-reply"; break;
case RPCConsole::CMD_REQUEST: return QColor(128,128,128); break; case RPCConsole::CMD_ERROR: return "cmd-error"; break;
case RPCConsole::CMD_REPLY: return QColor(128,255,128); break; default: return "misc";
case RPCConsole::CMD_ERROR: return QColor(255,128,128); break;
default: return QColor(0,0,0);
} }
} }
void RPCConsole::clear() void RPCConsole::clear()
{ {
ui->messagesWidget->clear(); ui->messagesWidget->clear();
ui->messagesWidget->setRowCount(0);
ui->lineEdit->clear(); ui->lineEdit->clear();
ui->lineEdit->setFocus(); ui->lineEdit->setFocus();
message(CMD_REPLY, tr("Welcome to the bitcoin RPC console.")+"\n"+ // Add smoothly scaled icon images.
tr("Use up and down arrows to navigate history, and Ctrl-L to clear screen.")+"\n"+ // (when using width/height on an img, Qt uses nearest instead of linear interpolation)
tr("Type \"help\" for an overview of available commands.")); for(int i=0; ICON_MAPPING[i].url; ++i)
}
void RPCConsole::message(int category, const QString &message)
{ {
// Add row to messages widget ui->messagesWidget->document()->addResource(
int row = ui->messagesWidget->rowCount(); QTextDocument::ImageResource,
ui->messagesWidget->setRowCount(row+1); QUrl(ICON_MAPPING[i].url),
QImage(ICON_MAPPING[i].source).scaled(ICON_SIZE, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
}
QTime time = QTime::currentTime(); // Set default style sheet
QTableWidgetItem *newTime = new QTableWidgetItem(time.toString()); ui->messagesWidget->document()->setDefaultStyleSheet(
newTime->setData(Qt::DecorationRole, categoryColor(category)); "table { }"
newTime->setForeground(QColor(128,128,128)); "td.time { color: #808080; padding-top: 3px; } "
newTime->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); // make non-editable "td.message { font-family: Monospace; font-size: 12px; } "
"td.cmd-request { color: #006060; } "
int numLines = message.count("\n") + 1; "td.cmd-error { color: red; } "
// As Qt doesn't like very tall cells (they break scrolling) keep only short messages in "b { color: #006060; } "
// the cell text, longer messages trigger a display widget with scroll bar );
if(numLines < 5)
{ message(CMD_REPLY, tr("Welcome to the Bitcoin RPC console.<br>"
QTableWidgetItem *newItem = new QTableWidgetItem(message); "Use up and down arrows to navigate history, and <b>Ctrl-L</b> to clear screen.<br>"
newItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); // make non-editable "Type <b>help</b> for an overview of available commands."), true);
if(category == CMD_ERROR) // Coloring error messages in red
newItem->setForeground(QColor(255,16,16));
ui->messagesWidget->setItem(row, 1, newItem);
} else {
QTextEdit *newWidget = new QTextEdit;
newWidget->setText(message);
newWidget->setMaximumHeight(100);
newWidget->setReadOnly(true);
ui->messagesWidget->setCellWidget(row, 1, newWidget);
} }
ui->messagesWidget->setItem(row, 0, newTime); void RPCConsole::message(int category, const QString &message, bool html)
ui->messagesWidget->resizeRowToContents(row); {
// Preserve only limited scrollback buffer QTime time = QTime::currentTime();
while(ui->messagesWidget->rowCount() > CONSOLE_SCROLLBACK) QString timeString = time.toString();
ui->messagesWidget->removeRow(0); QString out;
// Scroll to bottom after table is updated out += "<table><tr><td class=\"time\" width=\"65\">" + timeString + "</td>";
QTimer::singleShot(0, ui->messagesWidget, SLOT(scrollToBottom())); out += "<td class=\"icon\" width=\"32\"><img src=\"" + categoryClass(category) + "\"></td>";
out += "<td class=\"message " + categoryClass(category) + "\" valign=\"middle\">";
if(html)
out += message;
else
out += GUIUtil::HtmlEscape(message, true);
out += "</td></tr></table>";
ui->messagesWidget->append(out);
} }
void RPCConsole::setNumConnections(int count) void RPCConsole::setNumConnections(int count)
@ -298,24 +296,10 @@ void RPCConsole::startExecutor()
thread->start(); thread->start();
} }
void RPCConsole::copyMessage()
{
GUIUtil::copyEntryData(ui->messagesWidget, 1, Qt::EditRole);
}
void RPCConsole::on_tabWidget_currentChanged(int index) void RPCConsole::on_tabWidget_currentChanged(int index)
{ {
if(ui->tabWidget->widget(index) == ui->tab_console) if(ui->tabWidget->widget(index) == ui->tab_console)
{ {
if(firstLayout)
{
// Work around QTableWidget issue:
// Call resizeRowsToContents on first Layout request with widget visible,
// to make sure multiline messages that were added before the console was shown
// have the right height.
firstLayout = false;
ui->messagesWidget->resizeRowsToContents();
}
ui->lineEdit->setFocus(); ui->lineEdit->setFocus();
} }
} }

5
src/qt/rpcconsole.h

@ -37,15 +37,13 @@ private slots:
public slots: public slots:
void clear(); void clear();
void message(int category, const QString &message); void message(int category, const QString &message, bool html = false);
/** Set number of connections shown in the UI */ /** Set number of connections shown in the UI */
void setNumConnections(int count); void setNumConnections(int count);
/** Set number of blocks shown in the UI */ /** Set number of blocks shown in the UI */
void setNumBlocks(int count); void setNumBlocks(int count);
/** Go forward or back in history */ /** Go forward or back in history */
void browseHistory(int offset); void browseHistory(int offset);
/** Copy currently selected message to clipboard */
void copyMessage();
signals: signals:
// For RPC command executor // For RPC command executor
@ -55,7 +53,6 @@ signals:
private: private:
Ui::RPCConsole *ui; Ui::RPCConsole *ui;
ClientModel *clientModel; ClientModel *clientModel;
bool firstLayout;
QStringList history; QStringList history;
int historyPtr; int historyPtr;

Loading…
Cancel
Save