Квт. Создание Консольного Виджета Для Графического Приложения

Здравствуйте, добрые люди.

Читая этот заголовок, читатели могут подумать: зачем смешивать консольные и графические приложения — в GUI-приложении консоль не нужна.

Но нет, осмелюсь сказать.

Иногда объединение функциональной консоли с полным набором команд и графическим дисплеем для удобной навигации и просмотра данных может привести к созданию мощного инструмента.

И у меня есть пример.

Начав использовать для своих проектов быстрое хранилище данных типа «ключ-значение» Redis, я обнаружил, что на данный момент не существует ни одного вменяемого десктопного приложения для просмотра, редактирования и администрирования баз данных Redis. Есть только консоль от разработчиков, веб-интерфейс Redis Admin UI, для работы которого требуется .

NET (что само по себе пугает) и пара Ruby-приложений, сделанных как будто на скорую руку, на коленке.

Хотелось бы иметь что-то удобное и быстрое, например саму базу данных Redis. Поэтому я решил восполнить этот пробел и написать такой инструмент. Раз нужен быстрый, то C++; раз уж нужна кроссплатформенная, то Qt.

Квт. Создание консольного виджета для графического приложения

В связи с тем, что все возможности базы данных невозможно реализовать, а новые могут появляться каждый день, в графический интерфейс пришлось добавить консоль.

На основании того, каким виджетом в Qt это моделировать и как, я хочу вам рассказать.



От беззакония к тотальному контролю

В качестве базового виджета консоли я выбрал QPlainTextEdit. Во-первых, он включает в себя расширенные возможности редактирования текста, которые могут нам понадобиться, а во-вторых, позволяет добавить форматирование: было бы неплохо выделить разные элементы цветом.

Итак, давайте создадим дочерний элемент QPlainTextEdit.

  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
   

class Console : public QPlainTextEdit{};

Несмотря на то, что QPlainTextEdit — это упрощенная версия QTextEdit, она позволяет пользователю делать множество вещей, которые не может сделать приличная консоль.

Поэтому первое, что мы сделаем, — это ограничим все, что можем.

Давайте перейдем от полного беззакония к тотальному контролю.

Для этого давайте переопределим встроенные слоты, которые воспринимают нажатия клавиш и щелчки мыши:

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
Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.