Здравствуйте, добрые люди.
Читая этот заголовок, читатели могут подумать: зачем смешивать консольные и графические приложения — в GUI-приложении консоль не нужна.
Но нет, осмелюсь сказать.
Иногда объединение функциональной консоли с полным набором команд и графическим дисплеем для удобной навигации и просмотра данных может привести к созданию мощного инструмента.
И у меня есть пример.
Начав использовать для своих проектов быстрое хранилище данных типа «ключ-значение» Redis, я обнаружил, что на данный момент не существует ни одного вменяемого десктопного приложения для просмотра, редактирования и администрирования баз данных Redis. Есть только консоль от разработчиков, веб-интерфейс Redis Admin UI, для работы которого требуется .
NET (что само по себе пугает) и пара Ruby-приложений, сделанных как будто на скорую руку, на коленке.
Хотелось бы иметь что-то удобное и быстрое, например саму базу данных Redis. Поэтому я решил восполнить этот пробел и написать такой инструмент. Раз нужен быстрый, то C++; раз уж нужна кроссплатформенная, то Qt.
В связи с тем, что все возможности базы данных невозможно реализовать, а новые могут появляться каждый день, в графический интерфейс пришлось добавить консоль.
На основании того, каким виджетом в Qt это моделировать и как, я хочу вам рассказать.
От беззакония к тотальному контролю
В качестве базового виджета консоли я выбрал QPlainTextEdit. Во-первых, он включает в себя расширенные возможности редактирования текста, которые могут нам понадобиться, а во-вторых, позволяет добавить форматирование: было бы неплохо выделить разные элементы цветом.Итак, давайте создадим дочерний элемент QPlainTextEdit.
Несмотря на то, что QPlainTextEdit — это упрощенная версия QTextEdit, она позволяет пользователю делать множество вещей, которые не может сделать приличная консоль.class Console : public QPlainTextEdit{};
Поэтому первое, что мы сделаем, — это ограничим все, что можем.
Давайте перейдем от полного беззакония к тотальному контролю.
Для этого давайте переопределим встроенные слоты, которые воспринимают нажатия клавиш и щелчки мыши: void Console::keyPressEvent(QKeyEvent *){}
void Console::mousePressEvent(QMouseEvent *){}
void Console::mouseDoubleClickEvent(QMouseEvent *){}
void Console::contextMenuEvent(QContextMenuEvent *){}
После этих строк пользователь не сможет ввести символ в поле виджета, выделить фрагмент текста или удалить строку — полная блокировка.
Эстадия либерализации
Теперь перейдем от тотального запрета к разумной демократии, попутно разрешив все, что нужно.
Первое, что мы сделаем, это определим строку приглашения: // class definition
QString prompt;
// contructor
prompt = "redis> ";
И выведите строку приглашения на консоль: // constructor
insertPrompt(false);
// source
void Console::insertPrompt(bool insertNewBlock)
{
if(insertNewBlock)
textCursor().
insertBlock(); textCursor().
insertText(prompt);
}
Это необходимо, чтобы при нажатии мыши нельзя было перемещать курсор, но можно было сделать консоль активной: void Console::mousePressEvent(QMouseEvent *)
{
setFocus();
}
При вводе обычных букв, цифр и других полезных символов их следует добавлять в командную строку: void Console::keyPressEvent(QKeyEvent *event)
{
// …
if(event->key() >= 0x20 && event->key() <= 0x7e
&& (event->modifiers() == Qt::NoModifier || event->modifiers() == Qt::ShiftModifier))
QPlainTextEdit::keyPressEvent(event);
// …
}
Символы можно стереть с помощью клавиши Backspace, но не все, а только до определенного момента — чтобы строка приглашения, не дай Бог, не стерлась: void Console::keyPressEvent(QKeyEvent *event)
{
// …
if(event->key() == Qt::Key_Backspace
&& event->modifiers() == Qt::NoModifier
&& textCursor().
positionInBlock() > prompt.length())
QPlainTextEdit::keyPressEvent(event);
// …
}
Определим реакцию виджета на ввод команды (при нажатии клавиши Enter): void Console::keyPressEvent(QKeyEvent *event)
{
// …
if(event->key() == Qt::Key_Return && event->modifiers() == Qt::NoModifier)
onEnter();
// …
}
Когда мы вводим команду, мы вырезаем кусок текста от строки приглашения до конца текстового блока и выдаем сигнал, к которому можно прикрепить слот: void Console::onEnter()
{
if(textCursor().
positionInBlock() == prompt.length()) { insertPrompt(); return; } QString cmd = textCursor().
block().
text().
mid(prompt.length());
emit onCommand(cmd);
}
Также, пока приложение обрабатывает команду, установите флаг блокировки текстового поля.
void Console::onEnter()
{
// …
isLocked = true;
}
void Console::keyPressEvent(QKeyEvent *event)
{
if(isLocked)
return;
// …
}
Родительское приложение виджета обработает команду и передаст результат выполнения на консоль, тем самым разблокировав его: void Console::output(QString s)
{
textCursor().
insertBlock(); textCursor().
insertText(s);
insertPrompt();
isLocked = false;
}
История команды
Хотелось бы, чтобы история всех вводимых команд сохранялась и при нажатии клавиш вверх/вниз можно было перемещаться по ней: // class definition
QStringList *history;
int historyPos;
// source
void Console::keyPressEvent(QKeyEvent *event)
{
// …
if(event->key() == Qt::Key_Up && event->modifiers() == Qt::NoModifier)
historyBack();
if(event->key() == Qt::Key_Down && event->modifiers() == Qt::NoModifier)
historyForward();
}
void Console::onEnter()
{
// …
historyAdd(cmd);
// …
}
void Console::historyAdd(QString cmd)
{
history->append(cmd);
historyPos = history->length();
}
void Console::historyBack()
{
if(!historyPos)
return;
QTextCursor cursor = textCursor();
cursor.movePosition(QTextCursor::StartOfBlock);
cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
cursor.removeSelectedText();
cursor.insertText(prompt + history->at(historyPos-1));
setTextCursor(cursor);
historyPos--;
}
void Console::historyForward()
{
if(historyPos == history->length())
return;
QTextCursor cursor = textCursor();
cursor.movePosition(QTextCursor::StartOfBlock);
cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
cursor.removeSelectedText();
if(historyPos == history->length() - 1)
cursor.insertText(prompt);
else
cursor.insertText(prompt + history->at(historyPos + 1));
setTextCursor(cursor);
historyPos++;
}
Делаем красиво: раскраска консоли
Для этого в конструкторе виджета определяем общую цветовую схему консоли - фон черный, буквы вводимой команды зеленые: QPalette p = palette();
p.setColor(QPalette::Base, Qt::black);
p.setColor(QPalette::Text, Qt::green);
setPalette(p);
При отображении строки приглашения сделайте шрифт зеленым:
void Console::insertPrompt(bool insertNewBlock)
{
// …
QTextCharFormat format;
format.setForeground(Qt::green);
textCursor().
setBlockCharFormat(format);
// …
}
И при выводе результата команды делаем шрифт белым:
void Console::output(QString s)
{
// …
QTextCharFormat format;
format.setForeground(Qt::white);
textCursor().
setBlockCharFormat(format);
// …
}
Все вниз!
Я также хотел бы, чтобы полоса прокрутки текстового поля консоли прокручивалась до конца, когда пользователь вводит команду: void Console::insertPrompt(bool insertNewBlock)
{
// …
scrollDown();
}
void Console::scrollDown()
{
QScrollBar *vbar = verticalScrollBar();
vbar->setValue(vbar->maximum());
}
Результат
В результате получилась веселая, красивая и удобная консоль.Мне потребовалось всего 120 строк кода.
Конечно, можно было бы сделать еще много чего, но базовый функционал реализован.
Ссылки
Исходный код проекта RedisConsole на GitHub: https://github.com/ptrofimov/RedisConsole Там вы можете просмотреть класс виджета Консоли и скачать скомпилированный бинарный файл приложения для Windows, нажав кнопку «Загрузки».
Спасибо
Теги: #Qt #виджеты #консоль #RedisConsole #Qt-
Гугл: Блокировка Рекламы
19 Oct, 24 -
Юзабилити Первомайский День
19 Oct, 24 -
Soundpedia — Замена Недоступной Пандоре
19 Oct, 24 -
Советы По Улучшению Push-Уведомлений
19 Oct, 24