From fc198dd3085a2cd33fbaa7a3d2c95c2c8d3ee31a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Mon, 8 Aug 2016 00:00:11 +0200 Subject: NOISSUE rework of minecraft log Now uses a model and a list view instead of text This lets mmc keep track of the contents regardless of whether the instance windows are open This is currently missing a way to select and copy text from the log. --- application/pages/LogPage.cpp | 350 ++++++++++++++++++++++++------------------ application/pages/LogPage.h | 20 +-- application/pages/LogPage.ui | 48 +++--- 3 files changed, 239 insertions(+), 179 deletions(-) (limited to 'application') diff --git a/application/pages/LogPage.cpp b/application/pages/LogPage.cpp index de4ed4f3..0a76f8b3 100644 --- a/application/pages/LogPage.cpp +++ b/application/pages/LogPage.cpp @@ -12,39 +12,113 @@ #include "GuiUtil.h" #include -LogPage::LogPage(InstancePtr instance, QWidget *parent) - : QWidget(parent), ui(new Ui::LogPage), m_instance(instance) +class LogFormatProxyModel : public QIdentityProxyModel { - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - - // create the format and set its font +public: + LogFormatProxyModel(QObject* parent = nullptr) : QIdentityProxyModel(parent) { - defaultFormat = new QTextCharFormat(ui->text->currentCharFormat()); - QString fontFamily = MMC->settings()->get("ConsoleFont").toString(); - bool conversionOk = false; - int fontSize = MMC->settings()->get("ConsoleFontSize").toInt(&conversionOk); - if(!conversionOk) + } + QVariant data(const QModelIndex &index, int role) const override + { + switch(role) { - fontSize = 11; - } - defaultFormat->setFont(QFont(fontFamily, fontSize)); + case Qt::FontRole: + return m_font; + case Qt::TextColorRole: + { + MessageLevel::Enum level = (MessageLevel::Enum) QIdentityProxyModel::data(index, LogModel::LevelRole).toInt(); + return m_colors->getFront(level); + } + case Qt::BackgroundRole: + { + MessageLevel::Enum level = (MessageLevel::Enum) QIdentityProxyModel::data(index, LogModel::LevelRole).toInt(); + return m_colors->getBack(level); + } + default: + return QIdentityProxyModel::data(index, role); + } } - // ensure we don't eat all the RAM + void setFont(QFont font) { - auto lineSetting = MMC->settings()->getSetting("ConsoleMaxLines"); - bool conversionOk = false; - int maxLines = lineSetting->get().toInt(&conversionOk); - if(!conversionOk) + m_font = font; + } + + void setColors(LogColorCache* colors) + { + m_colors.reset(colors); + } + + QModelIndex find(const QModelIndex &start, const QString &value, bool reverse) const + { + QModelIndex parentIndex = parent(start); + auto compare = [&](int r) -> QModelIndex { - maxLines = lineSetting->defValue().toInt(); - qWarning() << "ConsoleMaxLines has nonsensical value, defaulting to" << maxLines; + QModelIndex idx = index(r, start.column(), parentIndex); + if (!idx.isValid() || idx == start) + { + return QModelIndex(); + } + QVariant v = data(idx, Qt::DisplayRole); + QString t = v.toString(); + if (t.contains(value, Qt::CaseInsensitive)) + return idx; + return QModelIndex(); + }; + if(reverse) + { + int from = start.row(); + int to = 0; + + for (int i = 0; i < 2; ++i) + { + for (int r = from; (r >= to); --r) + { + auto idx = compare(r); + if(idx.isValid()) + return idx; + } + // prepare for the next iteration + from = rowCount() - 1; + to = start.row(); + } } - ui->text->setMaximumBlockCount(maxLines); + else + { + int from = start.row(); + int to = rowCount(parentIndex); - m_stopOnOverflow = MMC->settings()->get("ConsoleOverflowStop").toBool(); + for (int i = 0; i < 2; ++i) + { + for (int r = from; (r < to); ++r) + { + auto idx = compare(r); + if(idx.isValid()) + return idx; + } + // prepare for the next iteration + from = 0; + to = start.row(); + } + } + return QModelIndex(); } +private: + QFont m_font; + std::unique_ptr m_colors; +}; + +LogPage::LogPage(InstancePtr instance, QWidget *parent) + : QWidget(parent), ui(new Ui::LogPage), m_instance(instance) +{ + ui->setupUi(this); + ui->tabWidget->tabBar()->hide(); + + m_proxy = new LogFormatProxyModel(this); + connect(m_proxy, &QAbstractItemModel::rowsAboutToBeInserted, this, &LogPage::rowsAboutToBeInserted); + connect(m_proxy, &QAbstractItemModel::rowsInserted, this, &LogPage::rowsInserted); + + ui->textView->setModel(m_proxy); // set up instance and launch process recognition { @@ -57,13 +131,28 @@ LogPage::LogPage(InstancePtr instance, QWidget *parent) this, &LogPage::on_InstanceLaunchTask_changed); } - // set up text colors and adapt them to the current theme foreground and background + // set up text colors in the log proxy and adapt them to the current theme foreground and background + { + auto origForeground = ui->textView->palette().color(ui->textView->foregroundRole()); + auto origBackground = ui->textView->palette().color(ui->textView->backgroundRole()); + m_proxy->setColors(new LogColorCache(origForeground, origBackground)); + } + + // set up fonts in the log proxy { - auto origForeground = ui->text->palette().color(ui->text->foregroundRole()); - auto origBackground = ui->text->palette().color(ui->text->backgroundRole()); - m_colors.reset(new LogColorCache(origForeground, origBackground)); + QString fontFamily = MMC->settings()->get("ConsoleFont").toString(); + bool conversionOk = false; + int fontSize = MMC->settings()->get("ConsoleFontSize").toInt(&conversionOk); + if(!conversionOk) + { + fontSize = 11; + } + m_proxy->setFont(QFont(fontFamily, fontSize)); } + ui->textView->setWordWrap(true); + ui->textView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + auto findShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this); connect(findShortcut, SIGNAL(activated()), SLOT(findActivated())); auto findNextShortcut = new QShortcut(QKeySequence(QKeySequence::FindNext), this); @@ -76,20 +165,33 @@ LogPage::LogPage(InstancePtr instance, QWidget *parent) LogPage::~LogPage() { delete ui; - delete defaultFormat; } void LogPage::on_InstanceLaunchTask_changed(std::shared_ptr proc) { + m_process = proc; if(m_process) { - disconnect(m_process.get(), &LaunchTask::log, this, &LogPage::write); + m_model = proc->getLogModel(); + auto lineSetting = MMC->settings()->getSetting("ConsoleMaxLines"); + bool conversionOk = false; + int maxLines = lineSetting->get().toInt(&conversionOk); + if(!conversionOk) + { + maxLines = lineSetting->defValue().toInt(); + qWarning() << "ConsoleMaxLines has nonsensical value, defaulting to" << maxLines; + } + m_model->setMaxLines(maxLines); + m_model->setStopOnOverflow(MMC->settings()->get("ConsoleOverflowStop").toBool()); + m_model->setOverflowMessage(tr("MultiMC stopped watching the game log because the log length surpassed %1 lines.\n" + "You may have to fix your mods because the game is still loggging to files and" + " likely wasting harddrive space at an alarming rate!").arg(maxLines)); + m_proxy->setSourceModel(m_model.get()); } - m_process = proc; - if(m_process) + else { - ui->text->clear(); - connect(m_process.get(), &LaunchTask::log, this, &LogPage::write); + m_proxy->setSourceModel(nullptr); + m_model.reset(); } } @@ -100,38 +202,50 @@ bool LogPage::apply() bool LogPage::shouldDisplay() const { - return m_instance->isRunning() || ui->text->blockCount() > 1; + return m_instance->isRunning() || m_proxy->rowCount() > 0; } void LogPage::on_btnPaste_clicked() { + if(!m_model) + return; + //FIXME: turn this into a proper task and move the upload logic out of GuiUtil! - write(tr("MultiMC: Log upload triggered at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date)), MessageLevel::MultiMC); - auto url = GuiUtil::uploadPaste(ui->text->toPlainText(), this); + m_model->append(MessageLevel::MultiMC, tr("MultiMC: Log upload triggered at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date))); + auto url = GuiUtil::uploadPaste(m_model->toPlainText(), this); if(!url.isEmpty()) { - write(tr("MultiMC: Log uploaded to: %1").arg(url), MessageLevel::MultiMC); + m_model->append(MessageLevel::MultiMC, tr("MultiMC: Log uploaded to: %1").arg(url)); } else { - write(tr("MultiMC: Log upload failed!"), MessageLevel::Error); + m_model->append(MessageLevel::Error, tr("MultiMC: Log upload failed!")); } } void LogPage::on_btnCopy_clicked() { - write(QString("Clipboard copy at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date)), MessageLevel::MultiMC); - GuiUtil::setClipboardText(ui->text->toPlainText()); + if(!m_model) + return; + m_model->append(MessageLevel::MultiMC, QString("Clipboard copy at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date))); + GuiUtil::setClipboardText(m_model->toPlainText()); } void LogPage::on_btnClear_clicked() { - ui->text->clear(); + if(!m_model) + return; + m_model->clear(); } void LogPage::on_btnBottom_clicked() { - ui->text->verticalScrollBar()->setSliderPosition(ui->text->verticalScrollBar()->maximum()); + /* + ui->textView->verticalScrollBar()->setSliderPosition(ui->textView->verticalScrollBar()->maximum()); + */ + auto numRows = m_proxy->rowCount(QModelIndex()); + auto lastIndex = m_proxy->index(numRows - 1, 0 , QModelIndex()); + ui->textView->scrollTo(lastIndex, QAbstractItemView::ScrollHint::EnsureVisible); } void LogPage::on_trackLogCheckbox_clicked(bool checked) @@ -143,58 +257,61 @@ void LogPage::on_wrapCheckbox_clicked(bool checked) { if(checked) { - ui->text->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + ui->textView->setWordWrap(true); + ui->textView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } else { - ui->text->setWordWrapMode(QTextOption::WrapMode::NoWrap); + ui->textView->setWordWrap(false); + ui->textView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); } } -void LogPage::on_findButton_clicked() +void LogPage::findImpl(bool reverse) { - auto modifiers = QApplication::keyboardModifiers(); - if (modifiers & Qt::ShiftModifier) - { - findPreviousActivated(); - } - else + auto toSearch = ui->searchBar->text(); + if (toSearch.size()) { - findNextActivated(); + auto index = ui->textView->currentIndex(); + if(!index.isValid()) + { + index = m_proxy->index(0,0); + } + if(!index.isValid()) + { + // just give up + return; + } + auto found = m_proxy->find(index, toSearch, reverse); + if(found.isValid()) + ui->textView->setCurrentIndex(found); } } -void LogPage::findActivated() +void LogPage::on_findButton_clicked() { - // focus the search bar if it doesn't have focus - if (!ui->searchBar->hasFocus()) - { - auto searchForCursor = ui->text->textCursor(); - auto searchForString = searchForCursor.selectedText(); - if (searchForString.size()) - { - ui->searchBar->setText(searchForString); - } - ui->searchBar->setFocus(); - ui->searchBar->selectAll(); - } + auto modifiers = QApplication::keyboardModifiers(); + bool reverse = modifiers & Qt::ShiftModifier; + findImpl(reverse); } void LogPage::findNextActivated() { - auto toSearch = ui->searchBar->text(); - if (toSearch.size()) - { - ui->text->find(toSearch); - } + findImpl(false); } void LogPage::findPreviousActivated() { - auto toSearch = ui->searchBar->text(); - if (toSearch.size()) + findImpl(true); +} + +void LogPage::findActivated() +{ + // focus the search bar if it doesn't have focus + if (!ui->searchBar->hasFocus()) { - ui->text->find(toSearch, QTextDocument::FindBackward); + ui->searchBar->setFocus(); + ui->searchBar->selectAll(); } } @@ -203,84 +320,19 @@ void LogPage::setParentContainer(BasePageContainer * container) m_parentContainer = container; } -void LogPage::write(QString data, MessageLevel::Enum mode) +void LogPage::rowsAboutToBeInserted(const QModelIndex& parent, int first, int last) { - if (!m_write_active) - { - if (mode != MessageLevel::MultiMC) - { - return; - } - } - if(m_stopOnOverflow && m_write_active) - { - if(mode != MessageLevel::MultiMC) - { - if(ui->text->blockCount() >= ui->text->maximumBlockCount()) - { - m_write_active = false; - data = tr("MultiMC stopped watching the game log because the log length surpassed %1 lines.\n" - "You may have to fix your mods because the game is still loggging to files and" - " likely wasting harddrive space at an alarming rate!") - .arg(ui->text->maximumBlockCount()); - mode = MessageLevel::Fatal; - ui->trackLogCheckbox->setCheckState(Qt::Unchecked); - if(!isVisible()) - { - m_parentContainer->selectPage(id()); - } - } - } - } - - // save the cursor so it can be restored. - auto savedCursor = ui->text->cursor(); - - QScrollBar *bar = ui->text->verticalScrollBar(); - int max_bar = bar->maximum(); - int val_bar = bar->value(); - if (isVisible()) - { - if (m_scroll_active) - { - m_scroll_active = (max_bar - val_bar) <= 1; - } - else - { - m_scroll_active = val_bar == max_bar; - } - } - if (data.endsWith('\n')) - data = data.left(data.length() - 1); - QStringList paragraphs = data.split('\n'); - QStringList filtered; - for (QString ¶graph : paragraphs) - { - //TODO: implement filtering here. - filtered.append(paragraph); - } - QListIterator iter(filtered); - QTextCharFormat format(*defaultFormat); - - format.setForeground(m_colors->getFront(mode)); - format.setBackground(m_colors->getBack(mode)); - - while (iter.hasNext()) - { - // append a paragraph/line - auto workCursor = ui->text->textCursor(); - workCursor.movePosition(QTextCursor::End); - workCursor.insertText(iter.next(), format); - workCursor.insertBlock(); - } + auto numRows = m_proxy->rowCount(QModelIndex()); + auto lastIndex = m_proxy->index(numRows - 1, 0 , QModelIndex()); + auto rect = ui->textView->visualRect(lastIndex); + auto viewPortRect = ui->textView->viewport()->rect(); + m_autoScroll = rect.intersects(viewPortRect); +} - if (isVisible()) +void LogPage::rowsInserted(const QModelIndex& parent, int first, int last) +{ + if(m_autoScroll) { - if (m_scroll_active) - { - bar->setValue(bar->maximum()); - } - m_last_scroll_value = bar->value(); + QMetaObject::invokeMethod(this, "on_btnBottom_clicked", Qt::QueuedConnection); } - ui->text->setCursor(savedCursor); } diff --git a/application/pages/LogPage.h b/application/pages/LogPage.h index e902ad13..71e23977 100644 --- a/application/pages/LogPage.h +++ b/application/pages/LogPage.h @@ -21,13 +21,13 @@ #include "launch/LaunchTask.h" #include "BasePage.h" #include -#include namespace Ui { class LogPage; } class QTextCharFormat; +class LogFormatProxyModel; class LogPage : public QWidget, public BasePage { @@ -57,13 +57,6 @@ public: virtual void setParentContainer(BasePageContainer *) override; private slots: - /** - * @brief write a string - * @param data the string - * @param level the @MessageLevel the string should be written under - * lines have to be put through this as a whole! - */ - void write(QString data, MessageLevel::Enum level = MessageLevel::MultiMC); void on_btnPaste_clicked(); void on_btnCopy_clicked(); void on_btnClear_clicked(); @@ -79,6 +72,12 @@ private slots: void on_InstanceLaunchTask_changed(std::shared_ptr proc); + void rowsAboutToBeInserted(const QModelIndex &parent, int first, int last); + void rowsInserted(const QModelIndex &parent, int first, int last); + +private: /* methods */ + void findImpl(bool reverse); + private: Ui::LogPage *ui; InstancePtr m_instance; @@ -88,8 +87,9 @@ private: int m_saved_offset = 0; bool m_write_active = true; bool m_stopOnOverflow = true; + bool m_autoScroll = false; - QTextCharFormat * defaultFormat; BasePageContainer * m_parentContainer; - std::unique_ptr m_colors; + LogFormatProxyModel * m_proxy; + shared_qobject_ptr m_model; }; diff --git a/application/pages/LogPage.ui b/application/pages/LogPage.ui index bf5ec004..18fb654b 100644 --- a/application/pages/LogPage.ui +++ b/application/pages/LogPage.ui @@ -33,25 +33,6 @@ Tab 1 - - - - false - - - true - - - - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - false - - - @@ -153,6 +134,34 @@ + + + + Qt::ScrollBarAlwaysOn + + + Qt::ScrollBarAlwaysOff + + + false + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + QAbstractItemView::ScrollPerPixel + + + true + + + @@ -166,7 +175,6 @@ btnCopy btnPaste btnClear - text searchBar findButton -- cgit v1.2.3