NOISSUE use model/view for Minecraft log data
This commit is contained in:
		@@ -210,6 +210,8 @@ SET(MULTIMC_SOURCES
 | 
			
		||||
	widgets/LabeledToolButton.h
 | 
			
		||||
	widgets/LineSeparator.cpp
 | 
			
		||||
	widgets/LineSeparator.h
 | 
			
		||||
	widgets/LogView.cpp
 | 
			
		||||
	widgets/LogView.h
 | 
			
		||||
	widgets/MCModInfoFrame.cpp
 | 
			
		||||
	widgets/MCModInfoFrame.h
 | 
			
		||||
	widgets/ModListView.cpp
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,118 @@
 | 
			
		||||
#include "GuiUtil.h"
 | 
			
		||||
#include <ColorCache.h>
 | 
			
		||||
 | 
			
		||||
class LogFormatProxyModel : public QIdentityProxyModel
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	LogFormatProxyModel(QObject* parent = nullptr) : QIdentityProxyModel(parent)
 | 
			
		||||
	{
 | 
			
		||||
	}
 | 
			
		||||
	QVariant data(const QModelIndex &index, int role) const override
 | 
			
		||||
	{
 | 
			
		||||
		switch(role)
 | 
			
		||||
		{
 | 
			
		||||
			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);
 | 
			
		||||
			}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void setFont(QFont font)
 | 
			
		||||
	{
 | 
			
		||||
		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
 | 
			
		||||
		{
 | 
			
		||||
			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();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			int from = start.row();
 | 
			
		||||
			int to = rowCount(parentIndex);
 | 
			
		||||
 | 
			
		||||
			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<LogColorCache> m_colors;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
LogPage::LogPage(InstancePtr instance, QWidget *parent)
 | 
			
		||||
	: QWidget(parent), ui(new Ui::LogPage), m_instance(instance)
 | 
			
		||||
{
 | 
			
		||||
	ui->setupUi(this);
 | 
			
		||||
	ui->tabWidget->tabBar()->hide();
 | 
			
		||||
 | 
			
		||||
	// create the format and set its font
 | 
			
		||||
	m_proxy = new LogFormatProxyModel(this);
 | 
			
		||||
	// set up text colors in the log proxy and adapt them to the current theme foreground and background
 | 
			
		||||
	{
 | 
			
		||||
		auto origForeground = ui->text->palette().color(ui->text->foregroundRole());
 | 
			
		||||
		auto origBackground = ui->text->palette().color(ui->text->backgroundRole());
 | 
			
		||||
		m_proxy->setColors(new LogColorCache(origForeground, origBackground));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// set up fonts in the log proxy
 | 
			
		||||
	{
 | 
			
		||||
		defaultFormat = new QTextCharFormat(ui->text->currentCharFormat());
 | 
			
		||||
		QString fontFamily = MMC->settings()->get("ConsoleFont").toString();
 | 
			
		||||
		bool conversionOk = false;
 | 
			
		||||
		int fontSize = MMC->settings()->get("ConsoleFontSize").toInt(&conversionOk);
 | 
			
		||||
@@ -28,23 +131,10 @@ LogPage::LogPage(InstancePtr instance, QWidget *parent)
 | 
			
		||||
		{
 | 
			
		||||
			fontSize = 11;
 | 
			
		||||
		}
 | 
			
		||||
		defaultFormat->setFont(QFont(fontFamily, fontSize));
 | 
			
		||||
		m_proxy->setFont(QFont(fontFamily, fontSize));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// ensure we don't eat all the RAM
 | 
			
		||||
	{
 | 
			
		||||
		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;
 | 
			
		||||
		}
 | 
			
		||||
		ui->text->setMaximumBlockCount(maxLines);
 | 
			
		||||
 | 
			
		||||
		m_stopOnOverflow = MMC->settings()->get("ConsoleOverflowStop").toBool();
 | 
			
		||||
	}
 | 
			
		||||
	ui->text->setModel(m_proxy);
 | 
			
		||||
 | 
			
		||||
	// set up instance and launch process recognition
 | 
			
		||||
	{
 | 
			
		||||
@@ -53,16 +143,10 @@ LogPage::LogPage(InstancePtr instance, QWidget *parent)
 | 
			
		||||
		{
 | 
			
		||||
			on_InstanceLaunchTask_changed(launchTask);
 | 
			
		||||
		}
 | 
			
		||||
		connect(m_instance.get(), &BaseInstance::launchTaskChanged,
 | 
			
		||||
			this, &LogPage::on_InstanceLaunchTask_changed);
 | 
			
		||||
		connect(m_instance.get(), &BaseInstance::launchTaskChanged, this, &LogPage::on_InstanceLaunchTask_changed);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// set up text colors and adapt them to the current theme foreground and background
 | 
			
		||||
	{
 | 
			
		||||
		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));
 | 
			
		||||
	}
 | 
			
		||||
	ui->text->setWordWrap(true);
 | 
			
		||||
 | 
			
		||||
	auto findShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this);
 | 
			
		||||
	connect(findShortcut, SIGNAL(activated()), SLOT(findActivated()));
 | 
			
		||||
@@ -76,20 +160,33 @@ LogPage::LogPage(InstancePtr instance, QWidget *parent)
 | 
			
		||||
LogPage::~LogPage()
 | 
			
		||||
{
 | 
			
		||||
	delete ui;
 | 
			
		||||
	delete defaultFormat;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LogPage::on_InstanceLaunchTask_changed(std::shared_ptr<LaunchTask> proc)
 | 
			
		||||
{
 | 
			
		||||
	if(m_process)
 | 
			
		||||
	{
 | 
			
		||||
		disconnect(m_process.get(), &LaunchTask::log, this, &LogPage::write);
 | 
			
		||||
	}
 | 
			
		||||
	m_process = proc;
 | 
			
		||||
	if(m_process)
 | 
			
		||||
	{
 | 
			
		||||
		ui->text->clear();
 | 
			
		||||
		connect(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());
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		m_proxy->setSourceModel(nullptr);
 | 
			
		||||
		m_model.reset();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -100,38 +197,45 @@ 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->text->scrollToBottom();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LogPage::on_trackLogCheckbox_clicked(bool checked)
 | 
			
		||||
@@ -141,27 +245,24 @@ void LogPage::on_trackLogCheckbox_clicked(bool checked)
 | 
			
		||||
 | 
			
		||||
void LogPage::on_wrapCheckbox_clicked(bool checked)
 | 
			
		||||
{
 | 
			
		||||
	if(checked)
 | 
			
		||||
	{
 | 
			
		||||
		ui->text->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		ui->text->setWordWrapMode(QTextOption::WrapMode::NoWrap);
 | 
			
		||||
	}
 | 
			
		||||
	ui->text->setWordWrap(checked);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LogPage::on_findButton_clicked()
 | 
			
		||||
{
 | 
			
		||||
	auto modifiers = QApplication::keyboardModifiers();
 | 
			
		||||
	if (modifiers & Qt::ShiftModifier)
 | 
			
		||||
	{
 | 
			
		||||
		findPreviousActivated();
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		findNextActivated();
 | 
			
		||||
	}
 | 
			
		||||
	bool reverse = modifiers & Qt::ShiftModifier;
 | 
			
		||||
	ui->text->findNext(ui->searchBar->text(), reverse);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LogPage::findNextActivated()
 | 
			
		||||
{
 | 
			
		||||
	ui->text->findNext(ui->searchBar->text(), false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LogPage::findPreviousActivated()
 | 
			
		||||
{
 | 
			
		||||
	ui->text->findNext(ui->searchBar->text(), true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LogPage::findActivated()
 | 
			
		||||
@@ -169,118 +270,12 @@ void LogPage::findActivated()
 | 
			
		||||
	// 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();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LogPage::findNextActivated()
 | 
			
		||||
{
 | 
			
		||||
	auto toSearch = ui->searchBar->text();
 | 
			
		||||
	if (toSearch.size())
 | 
			
		||||
	{
 | 
			
		||||
		ui->text->find(toSearch);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LogPage::findPreviousActivated()
 | 
			
		||||
{
 | 
			
		||||
	auto toSearch = ui->searchBar->text();
 | 
			
		||||
	if (toSearch.size())
 | 
			
		||||
	{
 | 
			
		||||
		ui->text->find(toSearch, QTextDocument::FindBackward);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LogPage::setParentContainer(BasePageContainer * container)
 | 
			
		||||
{
 | 
			
		||||
	m_parentContainer = container;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LogPage::write(QString data, MessageLevel::Enum mode)
 | 
			
		||||
{
 | 
			
		||||
	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<QString> 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();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (isVisible())
 | 
			
		||||
	{
 | 
			
		||||
		if (m_scroll_active)
 | 
			
		||||
		{
 | 
			
		||||
			bar->setValue(bar->maximum());
 | 
			
		||||
		}
 | 
			
		||||
		m_last_scroll_value = bar->value();
 | 
			
		||||
	}
 | 
			
		||||
	ui->text->setCursor(savedCursor);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,13 +21,13 @@
 | 
			
		||||
#include "launch/LaunchTask.h"
 | 
			
		||||
#include "BasePage.h"
 | 
			
		||||
#include <MultiMC.h>
 | 
			
		||||
#include <ColorCache.h>
 | 
			
		||||
 | 
			
		||||
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();
 | 
			
		||||
@@ -88,8 +81,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<LogColorCache> m_colors;
 | 
			
		||||
	LogFormatProxyModel * m_proxy;
 | 
			
		||||
	shared_qobject_ptr <LogModel> m_model;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@
 | 
			
		||||
      </attribute>
 | 
			
		||||
      <layout class="QGridLayout" name="gridLayout">
 | 
			
		||||
       <item row="1" column="0" colspan="5">
 | 
			
		||||
        <widget class="QPlainTextEdit" name="text">
 | 
			
		||||
        <widget class="LogView" name="text">
 | 
			
		||||
         <property name="undoRedoEnabled">
 | 
			
		||||
          <bool>false</bool>
 | 
			
		||||
         </property>
 | 
			
		||||
@@ -159,6 +159,13 @@
 | 
			
		||||
   </item>
 | 
			
		||||
  </layout>
 | 
			
		||||
 </widget>
 | 
			
		||||
 <customwidgets>
 | 
			
		||||
  <customwidget>
 | 
			
		||||
   <class>LogView</class>
 | 
			
		||||
   <extends>QPlainTextEdit</extends>
 | 
			
		||||
   <header>widgets/LogView.h</header>
 | 
			
		||||
  </customwidget>
 | 
			
		||||
 </customwidgets>
 | 
			
		||||
 <tabstops>
 | 
			
		||||
  <tabstop>tabWidget</tabstop>
 | 
			
		||||
  <tabstop>trackLogCheckbox</tabstop>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										143
									
								
								application/widgets/LogView.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								application/widgets/LogView.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,143 @@
 | 
			
		||||
#include "LogView.h"
 | 
			
		||||
#include <QTextBlock>
 | 
			
		||||
#include <QScrollBar>
 | 
			
		||||
 | 
			
		||||
LogView::LogView(QWidget* parent) : QPlainTextEdit(parent)
 | 
			
		||||
{
 | 
			
		||||
	setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
 | 
			
		||||
	m_defaultFormat = new QTextCharFormat(currentCharFormat());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LogView::~LogView()
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LogView::setWordWrap(bool wrapping)
 | 
			
		||||
{
 | 
			
		||||
	if(wrapping)
 | 
			
		||||
	{
 | 
			
		||||
		setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
 | 
			
		||||
		setLineWrapMode(QPlainTextEdit::WidgetWidth);
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
 | 
			
		||||
		setLineWrapMode(QPlainTextEdit::NoWrap);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LogView::setModel(QAbstractItemModel* model)
 | 
			
		||||
{
 | 
			
		||||
	if(m_model)
 | 
			
		||||
	{
 | 
			
		||||
		disconnect(m_model, &QAbstractItemModel::modelReset, this, &LogView::repopulate);
 | 
			
		||||
		disconnect(m_model, &QAbstractItemModel::rowsInserted, this, &LogView::rowsInserted);
 | 
			
		||||
		disconnect(m_model, &QAbstractItemModel::rowsAboutToBeInserted, this, &LogView::rowsAboutToBeInserted);
 | 
			
		||||
		disconnect(m_model, &QAbstractItemModel::rowsRemoved, this, &LogView::rowsRemoved);
 | 
			
		||||
	}
 | 
			
		||||
	m_model = model;
 | 
			
		||||
	if(m_model)
 | 
			
		||||
	{
 | 
			
		||||
		connect(m_model, &QAbstractItemModel::modelReset, this, &LogView::repopulate);
 | 
			
		||||
		connect(m_model, &QAbstractItemModel::rowsInserted, this, &LogView::rowsInserted);
 | 
			
		||||
		connect(m_model, &QAbstractItemModel::rowsAboutToBeInserted, this, &LogView::rowsAboutToBeInserted);
 | 
			
		||||
		connect(m_model, &QAbstractItemModel::rowsRemoved, this, &LogView::rowsRemoved);
 | 
			
		||||
		connect(m_model, &QAbstractItemModel::destroyed, this, &LogView::modelDestroyed);
 | 
			
		||||
	}
 | 
			
		||||
	repopulate();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QAbstractItemModel * LogView::model() const
 | 
			
		||||
{
 | 
			
		||||
	return m_model;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LogView::modelDestroyed(QObject* model)
 | 
			
		||||
{
 | 
			
		||||
	if(m_model == model)
 | 
			
		||||
	{
 | 
			
		||||
		setModel(nullptr);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LogView::repopulate()
 | 
			
		||||
{
 | 
			
		||||
	auto doc = document();
 | 
			
		||||
	doc->clear();
 | 
			
		||||
	if(!m_model)
 | 
			
		||||
	{
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	rowsInserted(QModelIndex(), 0, m_model->rowCount() - 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LogView::rowsAboutToBeInserted(const QModelIndex& parent, int first, int last)
 | 
			
		||||
{
 | 
			
		||||
	Q_UNUSED(parent)
 | 
			
		||||
	Q_UNUSED(first)
 | 
			
		||||
	Q_UNUSED(last)
 | 
			
		||||
	QScrollBar *bar = verticalScrollBar();
 | 
			
		||||
	int max_bar = bar->maximum();
 | 
			
		||||
	int val_bar = bar->value();
 | 
			
		||||
	if (m_scroll)
 | 
			
		||||
	{
 | 
			
		||||
		m_scroll = (max_bar - val_bar) <= 1;
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		m_scroll = val_bar == max_bar;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LogView::rowsInserted(const QModelIndex& parent, int first, int last)
 | 
			
		||||
{
 | 
			
		||||
	for(int i = first; i <= last; i++)
 | 
			
		||||
	{
 | 
			
		||||
		auto idx = m_model->index(i, 0, parent);
 | 
			
		||||
		auto text = m_model->data(idx, Qt::DisplayRole).toString();
 | 
			
		||||
		QTextCharFormat format(*m_defaultFormat);
 | 
			
		||||
		auto font = m_model->data(idx, Qt::FontRole);
 | 
			
		||||
		if(font.isValid())
 | 
			
		||||
		{
 | 
			
		||||
			format.setFont(font.value<QFont>());
 | 
			
		||||
		}
 | 
			
		||||
		auto fg = m_model->data(idx, Qt::TextColorRole);
 | 
			
		||||
		if(fg.isValid())
 | 
			
		||||
		{
 | 
			
		||||
			format.setForeground(fg.value<QColor>());
 | 
			
		||||
		}
 | 
			
		||||
		auto bg = m_model->data(idx, Qt::BackgroundRole);
 | 
			
		||||
		if(bg.isValid())
 | 
			
		||||
		{
 | 
			
		||||
			format.setBackground(bg.value<QColor>());
 | 
			
		||||
		}
 | 
			
		||||
		auto workCursor = textCursor();
 | 
			
		||||
		workCursor.movePosition(QTextCursor::End);
 | 
			
		||||
		workCursor.insertText(text, format);
 | 
			
		||||
		workCursor.insertBlock();
 | 
			
		||||
	}
 | 
			
		||||
	if(m_scroll && !m_scrolling)
 | 
			
		||||
	{
 | 
			
		||||
		m_scrolling = true;
 | 
			
		||||
		QMetaObject::invokeMethod( this, "scrollToBottom", Qt::QueuedConnection);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LogView::rowsRemoved(const QModelIndex& parent, int first, int last)
 | 
			
		||||
{
 | 
			
		||||
	// TODO: some day... maybe
 | 
			
		||||
	Q_UNUSED(parent)
 | 
			
		||||
	Q_UNUSED(first)
 | 
			
		||||
	Q_UNUSED(last)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LogView::scrollToBottom()
 | 
			
		||||
{
 | 
			
		||||
	m_scrolling = false;
 | 
			
		||||
	verticalScrollBar()->setSliderPosition(verticalScrollBar()->maximum());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LogView::findNext(const QString& what, bool reverse)
 | 
			
		||||
{
 | 
			
		||||
	find(what, reverse ? QTextDocument::FindFlag::FindBackward : QTextDocument::FindFlag(0));
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								application/widgets/LogView.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								application/widgets/LogView.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include <QPlainTextEdit>
 | 
			
		||||
#include <QAbstractItemView>
 | 
			
		||||
 | 
			
		||||
class QAbstractItemModel;
 | 
			
		||||
 | 
			
		||||
class LogView: public QPlainTextEdit
 | 
			
		||||
{
 | 
			
		||||
	Q_OBJECT
 | 
			
		||||
public:
 | 
			
		||||
	explicit LogView(QWidget *parent = nullptr);
 | 
			
		||||
	virtual ~LogView();
 | 
			
		||||
 | 
			
		||||
	virtual void setModel(QAbstractItemModel *model);
 | 
			
		||||
	QAbstractItemModel *model() const;
 | 
			
		||||
 | 
			
		||||
public slots:
 | 
			
		||||
	void setWordWrap(bool wrapping);
 | 
			
		||||
	void findNext(const QString & what, bool reverse);
 | 
			
		||||
	void scrollToBottom();
 | 
			
		||||
 | 
			
		||||
protected slots:
 | 
			
		||||
	void repopulate();
 | 
			
		||||
	// note: this supports only appending
 | 
			
		||||
	void rowsInserted(const QModelIndex &parent, int first, int last);
 | 
			
		||||
	void rowsAboutToBeInserted(const QModelIndex &parent, int first, int last);
 | 
			
		||||
	// note: this supports only removing from front
 | 
			
		||||
	void rowsRemoved(const QModelIndex &parent, int first, int last);
 | 
			
		||||
	void modelDestroyed(QObject * model);
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
	QAbstractItemModel *m_model = nullptr;
 | 
			
		||||
	QTextCharFormat *m_defaultFormat = nullptr;
 | 
			
		||||
	bool m_scroll = false;
 | 
			
		||||
	bool m_scrolling = false;
 | 
			
		||||
};
 | 
			
		||||
		Reference in New Issue
	
	Block a user