aboutsummaryrefslogtreecommitdiff
path: root/libs/installer_bsplugins/src/MOPlugin/MOPanelInterface.cpp
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-04-12 21:04:15 -0500
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-04-12 21:04:15 -0500
commit3949dcfce95af4bd305f258ff5b170d7d50435f6 (patch)
tree4c768be256c40f31794c3311f0a2c22933a6705a /libs/installer_bsplugins/src/MOPlugin/MOPanelInterface.cpp
parent892070f3dec1dc690983fd862880e37e711c2516 (diff)
VFS perf fixes, icon/stylesheet compat, download filename fix
VFS: - Open backing fd at mo2_open time for read-only handles so mo2_read can splice without re-opening the file on every read call. - Bump RLIMIT_NOFILE at mount time to fit the resulting fd pressure when the game keeps hundreds of BSAs open. - Dedicated node_cache_mutex so resolveByInode / mo2_lookup stop racing unordered_map writes while multiple tree_mutex readers are active. - max_read=1MB + matching conn->max_read and raised max_readahead/max_write so the kernel merges Wine's small sequential reads into bigger FUSE requests. - Per-op wall-clock counters in mo2_init() logs so we can distinguish VFS latency from game-side work. Proton launch: - Write dxvk.conf to the prefix and set DXVK_CONFIG_FILE to force dxvk.enableGraphicsPipelineLibrary=False, avoiding long GPL compile stalls on first run. UI / plugin compat: - Clamp IconDelegate's per-icon width to [8, 16] so narrow content columns don't trigger QIcon::pixmap(0,0) returning null and logging "failed to load icon" every repaint. - Pre-create lowercase symlinks in the stylesheet directory so QSS files authored on Windows resolve url(foo.svg) when on-disk files are Foo.svg. - Flip content icons empty-chessboard.png and facegen.png to 8-bit RGBA so Qt's built-in PNG reader loads them uniformly. - Add mobase.Version.canonicalString shim for legacy Python plugins that still call the old VersionInfo method on appVersion()'s return. - BSPluginList: compare normalized plugin name lists instead of byte hashes so the post-run case-fix refresh stops spuriously firing the "load order changed" dialog. - BSPluginList disabled by default. Download manager: - Parse CDN URL with QUrl + QUrlQuery and read the filename from the response-content-disposition query param (RFC 6266 quoted / unquoted / ext-value forms), not QFileInfo on the raw URL. - When the API or URL gives us a CDN object key (no archive extension), prefer the actual Content-Disposition header from the live response in metaDataChanged and downloadFinished. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'libs/installer_bsplugins/src/MOPlugin/MOPanelInterface.cpp')
-rw-r--r--libs/installer_bsplugins/src/MOPlugin/MOPanelInterface.cpp144
1 files changed, 144 insertions, 0 deletions
diff --git a/libs/installer_bsplugins/src/MOPlugin/MOPanelInterface.cpp b/libs/installer_bsplugins/src/MOPlugin/MOPanelInterface.cpp
new file mode 100644
index 0000000..c8bb242
--- /dev/null
+++ b/libs/installer_bsplugins/src/MOPlugin/MOPanelInterface.cpp
@@ -0,0 +1,144 @@
+#include "MOPanelInterface.h"
+
+#include <log.h>
+
+#include <algorithm>
+#include <functional>
+#include <iterator>
+
+using namespace Qt::Literals::StringLiterals;
+
+MOPanelInterface::MOPanelInterface(MOBase::IOrganizer* organizer,
+ QMainWindow* mainWindow)
+ : m_ModList{organizer->modList()}, m_PluginList{organizer->pluginList()},
+ m_ModListView{mainWindow->findChild<QTreeView*>(u"modList"_s)},
+ m_PluginListView{mainWindow->findChild<QTreeView*>(u"espList"_s)}
+{
+ if (m_ModListView) {
+ QObject::connect(m_ModListView, &QTreeView::collapsed, this,
+ &MOPanelInterface::onModSeparatorCollapsed);
+ QObject::connect(m_ModListView, &QTreeView::expanded, this,
+ &MOPanelInterface::onModSeparatorExpanded);
+ QObject::connect(m_ModListView->selectionModel(),
+ &QItemSelectionModel::selectionChanged, this,
+ &MOPanelInterface::onModSelectionChanged);
+ }
+}
+
+MOPanelInterface::~MOPanelInterface() noexcept
+{
+ m_SelectedOriginsChanged.disconnect_all_slots();
+}
+
+void MOPanelInterface::assignWidget(QTabWidget* tabWidget, QWidget* panel)
+{
+ QObject::connect(tabWidget, &QTabWidget::currentChanged,
+ [this, tabWidget, panel](int index) {
+ QWidget* const currentWidget = tabWidget->widget(index);
+ if (currentWidget == panel) {
+ m_PanelActivated();
+ }
+ });
+}
+
+void MOPanelInterface::setSelectedFiles(const QList<QString>& selectedFiles)
+{
+ if (!m_PluginListView) {
+ return;
+ }
+
+ QItemSelection selection;
+ const auto model = m_PluginListView->model();
+ for (int row = 0, count = model->rowCount(); row < count; ++row) {
+ const auto index = model->index(row, 0);
+ if (selectedFiles.contains(index.data(Qt::DisplayRole))) {
+ const auto end = model->index(row, model->columnCount() - 1);
+ selection.select(index, end);
+ }
+ }
+
+ m_PluginListView->selectionModel()->select(selection,
+ QItemSelectionModel::ClearAndSelect);
+}
+
+// FIXME: only works for files in the plugins panel
+void MOPanelInterface::displayOriginInformation(const QString& file)
+{
+ const auto model = m_PluginListView->model();
+ for (int row = 0, count = model->rowCount(); row < count; ++row) {
+ const auto index = model->index(row, 0);
+ const auto other = index.data(Qt::DisplayRole).toString();
+ if (file.compare(other, Qt::CaseInsensitive) == 0) {
+ m_PluginListView->selectionModel()->select(
+ QItemSelection(index, model->index(row, model->columnCount() - 1)),
+ QItemSelectionModel::ClearAndSelect);
+ m_PluginListView->selectionModel()->setCurrentIndex(index,
+ QItemSelectionModel::Current);
+ m_PluginListView->doubleClicked(index);
+ return;
+ }
+ }
+
+ MOBase::log::warn("failed to open origin info for \"{}\"", file);
+}
+
+bool MOPanelInterface::onPanelActivated(const std::function<void()>& func)
+{
+ auto connection = m_PanelActivated.connect(func);
+ return connection.connected();
+}
+
+bool MOPanelInterface::onSelectedOriginsChanged(
+ const std::function<void(const QList<QString>&)>& func)
+{
+ auto connection = m_SelectedOriginsChanged.connect(func);
+ return connection.connected();
+}
+
+void MOPanelInterface::setPluginState(const QString& name, bool enable)
+{
+ const auto model = m_PluginListView->model();
+ for (int i = 0, count = model->rowCount(); i < count; ++i) {
+ const auto index = model->index(i, 0);
+ if (index.data().toString().compare(name, Qt::CaseInsensitive) == 0) {
+ model->setData(index, enable ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole);
+ }
+ }
+}
+
+void MOPanelInterface::onModSeparatorCollapsed(const QModelIndex& index)
+{
+ if (m_ModListView->selectionModel()->isSelected(index)) {
+ onModSelectionChanged();
+ }
+}
+
+void MOPanelInterface::onModSeparatorExpanded(const QModelIndex& index)
+{
+ if (m_ModListView->selectionModel()->isSelected(index)) {
+ onModSelectionChanged();
+ }
+}
+
+void MOPanelInterface::onModSelectionChanged()
+{
+ QList<QString> origins;
+ std::function<void(const QModelIndex&)> addOrigins;
+ addOrigins = [&](const QModelIndex& index) {
+ if (index.model()->hasChildren(index)) {
+ if (m_ModListView->isExpanded(index)) {
+ return;
+ }
+
+ for (int i = 0, count = index.model()->rowCount(index); i < count; ++i) {
+ addOrigins(index.model()->index(i, 0, index));
+ }
+ } else {
+ origins.append(index.data(Qt::DisplayRole).toString());
+ }
+ };
+
+ const auto indexes = m_ModListView->selectionModel()->selectedRows();
+ std::ranges::for_each(indexes, addOrigins);
+ m_SelectedOriginsChanged(origins);
+}