diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-11 02:37:39 -0600 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-11 02:37:39 -0600 |
| commit | 7ee008e150bc5bcf76082d726f719ee0fdfda982 (patch) | |
| tree | 27fb39be241fdb5ac2734c574de678977d1856d0 /libs/installer_manual/src | |
Fluorine Manager: full Linux port of Mod Organizer 2
Complete native Linux port with FUSE-based virtual filesystem,
Proton/umu-run integration, and Flatpak packaging.
Key features:
- FUSE VFS replacing Windows USVFS (in-process + standalone helper for Flatpak)
- Proton/GE-Proton/umu-run launcher with env var forwarding
- Flatpak support (sandbox-aware VFS, NXM handler, umu-run)
- Wine prefix management UI
- Case-insensitive path resolution for Linux filesystems
- QSettings-safe INI handling (avoids Bethesda INI corruption)
- Portable instance support with auto-generated launcher scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'libs/installer_manual/src')
| -rw-r--r-- | libs/installer_manual/src/CMakeLists.txt | 17 | ||||
| -rw-r--r-- | libs/installer_manual/src/archivetree.cpp | 486 | ||||
| -rw-r--r-- | libs/installer_manual/src/archivetree.h | 178 | ||||
| -rw-r--r-- | libs/installer_manual/src/installdialog.cpp | 215 | ||||
| -rw-r--r-- | libs/installer_manual/src/installdialog.h | 124 | ||||
| -rw-r--r-- | libs/installer_manual/src/installdialog.ui | 162 | ||||
| -rw-r--r-- | libs/installer_manual/src/installer_manual_en.ts | 164 | ||||
| -rw-r--r-- | libs/installer_manual/src/installermanual.cpp | 136 | ||||
| -rw-r--r-- | libs/installer_manual/src/installermanual.h | 72 |
9 files changed, 1554 insertions, 0 deletions
diff --git a/libs/installer_manual/src/CMakeLists.txt b/libs/installer_manual/src/CMakeLists.txt new file mode 100644 index 0000000..2254977 --- /dev/null +++ b/libs/installer_manual/src/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.16) + +file(GLOB installer_manual_SOURCES CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/*.h + ${CMAKE_CURRENT_SOURCE_DIR}/*.ui + ${CMAKE_CURRENT_SOURCE_DIR}/*.qrc +) + +add_library(installer_manual SHARED ${installer_manual_SOURCES}) +mo2_configure_plugin(installer_manual NO_SOURCES WARNINGS 4) +target_include_directories(installer_manual PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} +) +target_link_libraries(installer_manual PRIVATE mo2::uibase) +mo2_install_plugin(installer_manual) diff --git a/libs/installer_manual/src/archivetree.cpp b/libs/installer_manual/src/archivetree.cpp new file mode 100644 index 0000000..ad9319e --- /dev/null +++ b/libs/installer_manual/src/archivetree.cpp @@ -0,0 +1,486 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "archivetree.h" + +#include <QDebug> +#include <QDragMoveEvent> +#include <QMessageBox> + +#include <uibase/ifiletree.h> +#include <uibase/log.h> +#include <uibase/report.h> + +using namespace MOBase; + +// Implementation details for the ArchiveTree widget: +// +// The ArchiveTreeWidget presents to the user the underlying IFileTree, but in order +// to increase performance, the tree is populated dynamically when required. Populating +// the tree is currently required: +// 1) when a branch of the tree widget is expanded, +// 2) when an item is moved to a tree, +// 3) when a directory is created, +// 4) when a directory is "set as data root". +// +// Case 1 is handled automatically in the setExpanded method of ArchiveTreeWidget. Cases +// 2 and 3 could be dealt with differently, but populating the tree before inserting an +// item makes everything else easier (not that populating the widget is different from +// populating the IFileTree which is done automatically). Case 4 is handled manually in +// setDataRoot. +// +// Another specificity of the implementation is the treeCheckStateChanged() signal +// emitted by the ArchiveTreeWidget. This signal is used to avoid having to connect to +// the itemChanged() signal or overriding the dataChanged() method which are called much +// more often than those. The treeCheckStateChanged() signal is send only for the item +// that has actually been changed by the user. While the interface is automatically +// updated by Qt, we need to update the underlying tree manually. This is done by doing +// the following things: +// 1) When an item is unchecked: +// - We detach the corresponding entry from its parent, and recursively detach the +// empty +// parents (or the ones that become empty). +// - If the entry is a directory and the item has been populated, we recursively +// detach +// all the child entries for all the child items that have been populated (no +// need to do it for non-populated items)> +// 2) When an item is checked, we do the same process but we re-attach parents and +// re-insert +// children. +// +// Detaching or re-attaching parents is also done when a directory is created (if the +// directory is created in an empty directory, we need to re-attach), or when an item is +// moved (if the directory the item comes from is now empty or if the target directory +// was empty). +// + +ArchiveTreeWidgetItem::ArchiveTreeWidgetItem(QString dataName) + : QTreeWidgetItem(QStringList(dataName)), m_Entry(nullptr) +{ + setFlags(flags() & ~Qt::ItemIsUserCheckable); + setExpanded(true); + m_Populated = true; +} + +ArchiveTreeWidgetItem::ArchiveTreeWidgetItem( + std::shared_ptr<MOBase::FileTreeEntry> entry) + : QTreeWidgetItem(QStringList(entry->name())), m_Entry(entry) +{ + if (entry->isDir()) { + setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); + setFlags(flags() | Qt::ItemIsUserCheckable | Qt::ItemIsAutoTristate); + } else { + setFlags(flags() | Qt::ItemIsUserCheckable | Qt::ItemNeverHasChildren); + } + setCheckState(0, Qt::Checked); + setToolTip(0, entry->path()); +} + +void ArchiveTreeWidgetItem::setData(int column, int role, const QVariant& value) +{ + ArchiveTreeWidget* tree = static_cast<ArchiveTreeWidget*>(treeWidget()); + if (tree != nullptr && tree->m_Emitter == nullptr) { + tree->m_Emitter = this; + } + QTreeWidgetItem::setData(column, role, value); + if (tree != nullptr && tree->m_Emitter == this) { + tree->m_Emitter = nullptr; + if (role == Qt::CheckStateRole) { + tree->onTreeCheckStateChanged(this); + } + } +} + +void ArchiveTreeWidgetItem::populate(bool force) +{ + + // Only populates once: + if (isPopulated() && !force) { + return; + } + + // Should never happen: + if (entry()->isFile()) { + return; + } + + // We go in reverse of the tree because we want to insert the original + // entries at the beginning (the item can only contains children if a + // directory has been created under it or if entries has been moved under + // it): + for (auto& entry : *entry()->astree()) { + auto newItem = new ArchiveTreeWidgetItem(entry); + newItem->setCheckState(0, flags().testFlag(Qt::ItemIsUserCheckable) ? checkState(0) + : Qt::Checked); + addChild(newItem); + } + + // If the item is unchecked, we need to clear it because it has not been cleared + // before: + if (flags().testFlag(Qt::ItemIsUserCheckable) && checkState(0) == Qt::Unchecked) { + entry()->astree()->clear(); + } + + m_Populated = true; +} + +ArchiveTreeWidget::ArchiveTreeWidget(QWidget* parent) : QTreeWidget(parent) +{ + setAutoExpandDelay(1000); + setDragDropOverwriteMode(true); + connect(this, &ArchiveTreeWidget::itemExpanded, this, + &ArchiveTreeWidget::populateItem); +} + +void ArchiveTreeWidget::setup(QString dataFolderName) +{ + m_ViewRoot = new ArchiveTreeWidgetItem("<" + dataFolderName + ">"); + m_DataRoot = nullptr; + addTopLevelItem(m_ViewRoot); +} + +void ArchiveTreeWidget::populateItem(QTreeWidgetItem* item) +{ + static_cast<ArchiveTreeWidgetItem*>(item)->populate(); +} + +void ArchiveTreeWidget::setDataRoot(ArchiveTreeWidgetItem* const root) +{ + if (root != m_DataRoot) { + if (m_DataRoot != nullptr) { + m_DataRoot->addChildren(m_ViewRoot->takeChildren()); + } + + // Force populate: + root->populate(); + + m_DataRoot = root; + m_ViewRoot->setEntry(m_DataRoot->entry()); + m_ViewRoot->addChildren(m_DataRoot->takeChildren()); + m_ViewRoot->setExpanded(true); + } + + emit treeChanged(); +} + +void ArchiveTreeWidget::detachParents(ArchiveTreeWidgetItem* item) +{ + auto entry = item->entry(); + auto parent = entry->parent(); + entry->detach(); + while (parent != nullptr && parent->empty()) { + auto tmp = parent->parent(); + parent->detach(); + parent = tmp; + } +} + +void ArchiveTreeWidget::attachParents(ArchiveTreeWidgetItem* item) +{ + while (item->parent() != nullptr) { + auto parent = static_cast<ArchiveTreeWidgetItem*>(item->parent()); + auto parentEntry = parent->entry(); + if (parentEntry != nullptr) { + parentEntry->astree()->insert(item->entry()); + } + item = parent; + } +} + +void ArchiveTreeWidget::recursiveInsert(ArchiveTreeWidgetItem* item) +{ + if (item->isPopulated()) { + auto tree = item->entry()->astree(); + for (int i = 0; i < item->childCount(); ++i) { + auto child = static_cast<ArchiveTreeWidgetItem*>(item->child(i)); + tree->insert(child->entry()); + if (child->entry()->isDir()) { + recursiveInsert(child); + } + } + } +} + +void ArchiveTreeWidget::recursiveDetach(ArchiveTreeWidgetItem* item) +{ + if (item->isPopulated()) { + for (int i = 0; i < item->childCount(); ++i) { + auto child = static_cast<ArchiveTreeWidgetItem*>(item->child(i)); + if (child->entry()->isDir()) { + recursiveDetach(child); + } + } + item->entry()->astree()->clear(); + } +} + +ArchiveTreeWidgetItem* ArchiveTreeWidget::addDirectory(ArchiveTreeWidgetItem* item, + QString name) +{ + auto tree = item->entry()->astree(); + auto* newItem = new ArchiveTreeWidgetItem(tree->addDirectory(name)); + + // find the insert position + auto it = std::find_if(tree->begin(), tree->end(), [name](auto&& entry) { + return entry->compare(name) == 0; + }); + int index = it - tree->begin(); + MOBase::log::debug("insert at: {}", index); + item->insertChild(index, newItem); + + newItem->setCheckState(0, Qt::Checked); + attachParents(item); + emit treeChanged(); + + return newItem; +} + +void ArchiveTreeWidget::moveItem(ArchiveTreeWidgetItem* source, + ArchiveTreeWidgetItem* target) +{ + // just insert the source in the target. + auto tree = target->entry()->astree(); + + detachParents(source); + + // check if an entry exists with the same name, we check + // in the tree widget to find unchecked items + for (int i = 0; i < target->childCount(); ++i) { + auto* child = target->child(i); + if (child->entry()->compare(source->entry()->name()) == 0) { + // remove existing file and force check existing directory + if (child->entry()->isFile()) { + target->removeChild(child); + } else { + child->setCheckState(0, Qt::Checked); + } + break; + } + } + + tree->insert(source->entry(), IFileTree::InsertPolicy::MERGE); + + attachParents(target); + + emit treeChanged(); +} + +void ArchiveTreeWidget::onTreeCheckStateChanged(ArchiveTreeWidgetItem* item) +{ + + auto entry = item->entry(); + + // If the entry is a directory, we need to either detach or re-attach all the + // children. It is not possible to only detach the directory because if the + // user uncheck a directory and then check a file under it, the other files would + // still be attached. + // + // The two recursive methods only go down to the expanded (based on isPopulated() + // tree, for two reasons: + // 1. If a tree item has not been populated, then detaching an entry from its parent + // will + // delete it since there would be no remaining shared pointers. + // 2. If the tree has not been populated yet, all the entries under it are still + // attached, + // so there is no need to process them differently. Detaching a non-expanded item + // can be done by simply detaching the tree, no need to detach all the children. + if (entry->isDir()) { + if (item->checkState(0) == Qt::Checked && item->isPopulated()) { + recursiveInsert(item); + } else if (item->checkState(0) == Qt::Unchecked && item->isPopulated()) { + recursiveDetach(item); + } + } + + // Unchecked: we go up the parent chain removing all trees that are now empty: + if (item->checkState(0) == Qt::Unchecked) { + detachParents(item); + } + // Otherwize, we need to-reattach the parent: + else { + attachParents(item); + } + + emit treeChanged(); +} + +bool ArchiveTreeWidget::testMovePossible(ArchiveTreeWidgetItem* source, + ArchiveTreeWidgetItem* target) +{ + if (target == nullptr || source == nullptr) { + return false; + } + + if (target->flags().testFlag(Qt::ItemNeverHasChildren)) { + return false; + } + + if (source == target || source->parent() == target) { + return false; + } + + return true; +} + +void ArchiveTreeWidget::dragEnterEvent(QDragEnterEvent* event) +{ + QTreeWidgetItem* source = this->currentItem(); + if ((source == nullptr) || (source->parent() == nullptr)) { + // can't change top level + event->ignore(); + return; + } else { + QTreeWidget::dragEnterEvent(event); + } +} + +void ArchiveTreeWidget::dragMoveEvent(QDragMoveEvent* event) +{ + if (!testMovePossible( + static_cast<ArchiveTreeWidgetItem*>(currentItem()), + static_cast<ArchiveTreeWidgetItem*>(itemAt(event->position().toPoint())))) { + event->ignore(); + } else { + QTreeWidget::dragMoveEvent(event); + } +} + +static bool isAncestor(const QTreeWidgetItem* ancestor, const QTreeWidgetItem* item) +{ + QTreeWidgetItem* iter = item->parent(); + while (iter != nullptr) { + if (iter == ancestor) { + return true; + } + iter = iter->parent(); + } + return false; +} + +void ArchiveTreeWidget::refreshItem(ArchiveTreeWidgetItem* item) +{ + if (!item->isPopulated() || item->flags().testFlag(Qt::ItemNeverHasChildren)) { + return; + } + + // at this point, all child items are checked for we only remember the ones + // that were expanded to re-expand them + std::map<QString, bool, MOBase::FileNameComparator> expanded; + while (item->childCount() > 0) { + auto* child = item->child(0); + expanded[child->entry()->name()] = child->isExpanded(); + item->removeChild(child); + } + + item->populate(true); + + for (int i = 0; i < item->childCount(); ++i) { + auto* child = item->child(i); + if (expanded[child->entry()->name()]) { + child->setExpanded(true); + } + } +} + +void ArchiveTreeWidget::dropEvent(QDropEvent* event) +{ + event->ignore(); + + // target widget (should be a directory) + auto* target = + static_cast<ArchiveTreeWidgetItem*>(itemAt(event->position().toPoint())); + + // this should not really happen because it is prevent by dragMoveEvent + if (target->flags().testFlag(Qt::ItemNeverHasChildren)) { + + // this should really not happen, how should a file get to the top level? + if (target->parent() == nullptr) { + return; + } + + target = target->parent(); + } + + // populate target if required + target->populate(); + + auto sourceItems = this->selectedItems(); + + // check the selected items - we do not want to move only + // some items so we check everything first and then move + for (auto* source : sourceItems) { + + auto* aSource = static_cast<ArchiveTreeWidgetItem*>(source); + + // do not allow element to be dropped into one of its + // own child + if (isAncestor(source, target)) { + event->accept(); + QMessageBox::warning(parentWidget(), tr("Cannot drop"), + tr("Cannot drop '%1' into one of its subfolder.") + .arg(aSource->entry()->name())); + return; + } + + auto sourceEntry = aSource->entry(); + auto targetEntry = target->entry()->astree()->find(sourceEntry->name()); + if (targetEntry && targetEntry->fileType() != sourceEntry->fileType()) { + event->accept(); + QMessageBox::warning(parentWidget(), tr("Cannot drop"), + targetEntry->isFile() + ? tr("A file '%1' already exists in folder '%2'.") + .arg(sourceEntry->name()) + .arg(target->entry()->name()) + : tr("A folder '%1' already exists in folder '%2'.") + .arg(sourceEntry->name()) + .arg(target->entry()->name())); + return; + } + } + + for (auto* source : sourceItems) { + + auto* aSource = static_cast<ArchiveTreeWidgetItem*>(source); + + // this only check dropping an item on itself or dropping an item in + // its parent so it is ok, it just does not do anything + if (source->parent() == nullptr || !testMovePossible(aSource, target)) { + continue; + } + + // force expand item that are going to be merged + for (int i = 0; i < target->childCount(); ++i) { + auto* child = target->child(i); + if (child->entry()->compare(aSource->entry()->name()) == 0 && + !child->flags().testFlag(Qt::ItemNeverHasChildren)) { + child->setExpanded(true); + } + } + + // remove the source from its parent + source->parent()->removeChild(source); + + // actually perform the move on the underlying tree model + moveItem(aSource, target); + } + + // refresh the target item - this assumes that itemMoved is called synchronously + // and perform the FileTree changes + refreshItem(target); +} diff --git a/libs/installer_manual/src/archivetree.h b/libs/installer_manual/src/archivetree.h new file mode 100644 index 0000000..156966c --- /dev/null +++ b/libs/installer_manual/src/archivetree.h @@ -0,0 +1,178 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef ARCHIVETREE_H +#define ARCHIVETREE_H + +#include <QTreeWidget> + +#include <uibase/ifiletree.h> + +class ArchiveTreeWidget; + +// custom tree widget that holds a shared pointer to the file tree entry +// they represent +// +class ArchiveTreeWidgetItem : public QTreeWidgetItem +{ +public: + ArchiveTreeWidgetItem(QString dataName); + ArchiveTreeWidgetItem(std::shared_ptr<MOBase::FileTreeEntry> entry); + +public: + // populate this tree widget item if it has not been populated yet + // or if force is true + // + void populate(bool force = false); + + // check if this item has already been populated + // + bool isPopulated() const { return m_Populated; } + + // replace the entry corresponding to this item + // + void setEntry(std::shared_ptr<MOBase::FileTreeEntry> entry) { m_Entry = entry; } + + // retrieve the entry corresponding to this item + // + std::shared_ptr<MOBase::FileTreeEntry> entry() const { return m_Entry; } + + // overriden method to avoid propagating dataChanged events + // + void setData(int column, int role, const QVariant& value) override; + + ArchiveTreeWidgetItem* parent() const + { + return static_cast<ArchiveTreeWidgetItem*>(QTreeWidgetItem::parent()); + } + + ArchiveTreeWidgetItem* child(int index) const + { + return static_cast<ArchiveTreeWidgetItem*>(QTreeWidgetItem::child(index)); + } + +protected: + std::shared_ptr<MOBase::FileTreeEntry> m_Entry; + bool m_Populated = false; + + friend class ArchiveTreeWidget; +}; + +// Qt tree widget used to display the content of an archive in the manual installation +// dialog +class ArchiveTreeWidget : public QTreeWidget +{ + + Q_OBJECT + +public: + explicit ArchiveTreeWidget(QWidget* parent = 0); + void setup(QString dataFolderName); + +public: + // set the data root widget + // + void setDataRoot(ArchiveTreeWidgetItem* const root); + + // create a directory under the given tree item, without + // performing any check + // + ArchiveTreeWidgetItem* addDirectory(ArchiveTreeWidgetItem* treeItem, QString name); + + // return the root of the tree (the item corresponding to <data>) + // + ArchiveTreeWidgetItem* root() const { return m_ViewRoot; } + +signals: + + // emitted when the tree has been modified + // + void treeChanged(); + +public slots: + +protected: + // detach the entry of this item from its parent, and recursively detach + // all of its parent if they become + // + void detachParents(ArchiveTreeWidgetItem* item); + + // re-attach the entry of this item to its parent, and recursively attach + // all of its parent if they were empty (and thus detached) + // + void attachParents(ArchiveTreeWidgetItem* item); + + // recursively re-insert all the entries below the given item in their + // corresponding parents + // + // this method does not recurse in items that have not been populated yet + // + void recursiveInsert(ArchiveTreeWidgetItem* item); + + // recursively detach all the entries below the given item from their + // corresponding parents + // + // this method does not recurse in items that have not been populated yet + // + void recursiveDetach(ArchiveTreeWidgetItem* item); + + // slot that trigger the given item to be populated if it has not already + // been + // + void populateItem(QTreeWidgetItem* item); + + // move the source under the target + // + void moveItem(ArchiveTreeWidgetItem* source, ArchiveTreeWidgetItem* target); + + // called when the state of the item changed - unlike the standard QTreeWidget, + // this is only called for the actual item, not its parent/children + // + void onTreeCheckStateChanged(ArchiveTreeWidgetItem* item); + + void dragEnterEvent(QDragEnterEvent* event) override; + void dragMoveEvent(QDragMoveEvent* event) override; + void dropEvent(QDropEvent* event) override; + +private: + bool testMovePossible(ArchiveTreeWidgetItem* source, ArchiveTreeWidgetItem* target); + + // refresh the given item (after a drop) + // + void refreshItem(ArchiveTreeWidgetItem* item); + + // the widget item that emitted the dataChanged event + ArchiveTreeWidgetItem* m_Emitter = nullptr; + + // IMPORTANT: if you intend to work on this and understand this, read the detailed + // explanation at the beginning of the archivetree.cpp file + // + // - the data root is the real widget of the current data, this widget + // is not the real root that is added to the tree + // - the view root is the actual tree in the widget (should be const but cannot be + // since + // the parent tree cannot be consstructed in the member initializer list) + // + ArchiveTreeWidgetItem* m_DataRoot; + ArchiveTreeWidgetItem* m_ViewRoot; + + friend class ArchiveTreeWidgetItem; +}; + +#endif // ARCHIVETREE_H diff --git a/libs/installer_manual/src/installdialog.cpp b/libs/installer_manual/src/installdialog.cpp new file mode 100644 index 0000000..a6e96b4 --- /dev/null +++ b/libs/installer_manual/src/installdialog.cpp @@ -0,0 +1,215 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "installdialog.h" +#include "ui_installdialog.h" + +#include <QCompleter> +#include <QInputDialog> +#include <QMenu> +#include <QMessageBox> +#include <QMetaType> + +#include <uibase/log.h> +#include <uibase/report.h> +#include <uibase/utility.h> + +using namespace MOBase; + +InstallDialog::InstallDialog( + std::shared_ptr<IFileTree> tree, const GuessedValue<QString>& modName, + std::shared_ptr<const MOBase::ModDataChecker> modDataChecker, + const QString& dataName, QWidget* parent) + : TutorableDialog("InstallDialog", parent), ui(new Ui::InstallDialog), + m_Checker(modDataChecker), m_DataFolderName(dataName) +{ + + ui->setupUi(this); + + for (auto iter = modName.variants().begin(); iter != modName.variants().end(); + ++iter) { + ui->nameCombo->addItem(*iter); + } + + ui->nameCombo->setCurrentIndex(ui->nameCombo->findText(modName)); + ui->nameCombo->completer()->setCaseSensitivity(Qt::CaseSensitive); + + m_ProblemLabel = ui->problemLabel; + + m_Tree = ui->treeContent; + m_TreeRoot = new ArchiveTreeWidgetItem(tree); + m_Tree->setup(m_DataFolderName); + connect(m_Tree, &ArchiveTreeWidget::treeChanged, [this] { + updateProblems(); + }); + + m_Tree->setDataRoot(m_TreeRoot); +} + +InstallDialog::~InstallDialog() +{ + delete ui; +} + +QString InstallDialog::getModName() const +{ + return ui->nameCombo->currentText(); +} + +/** + * @brief Retrieve the user-modified directory structure. + * + * @return the new tree represented by this dialog, which can be a new + * tree or a subtree of the original tree. + **/ +std::shared_ptr<MOBase::IFileTree> InstallDialog::getModifiedTree() const +{ + return m_Tree->root()->entry()->astree(); +} + +bool InstallDialog::testForProblem() +{ + if (!m_Checker) { + return true; + } + return m_Checker->dataLooksValid(m_Tree->root()->entry()->astree()) == + ModDataChecker::CheckReturn::VALID; +} + +void InstallDialog::updateProblems() +{ + if (!m_Checker) { + m_Tree->setStyleSheet("QTreeWidget { border: none; }"); + m_ProblemLabel->setText( + tr("Cannot check the content of <%1>.").arg(m_DataFolderName)); + m_ProblemLabel->setToolTip(tr("The plugin for the current game does not provide a " + "way to check the content of <%1>.") + .arg(m_DataFolderName)); + m_ProblemLabel->setStyleSheet("color: darkYellow;"); + } else if (testForProblem()) { + m_Tree->setStyleSheet( + "QTreeWidget { border: 1px solid darkGreen; border-radius: 2px; }"); + m_ProblemLabel->setText( + tr("The content of <%1> looks valid.").arg(m_DataFolderName)); + m_ProblemLabel->setToolTip( + tr("The content of <%1> seems valid for the current game.") + .arg(m_DataFolderName)); + m_ProblemLabel->setStyleSheet("color: darkGreen;"); + } else { + m_Tree->setStyleSheet("QTreeWidget { border: 1px solid red; border-radius: 2px; }"); + m_ProblemLabel->setText( + tr("The content of <%1> does not look valid.").arg(m_DataFolderName)); + m_ProblemLabel->setToolTip( + tr("The content of <%1> is probably not valid for the current game.") + .arg(m_DataFolderName)); + m_ProblemLabel->setStyleSheet("color: red;"); + } +} + +void InstallDialog::createDirectoryUnder(ArchiveTreeWidgetItem* item) +{ + // Should never happen if we customize the context menu depending + // on the item: + if (!item->entry()->isDir()) { + reportError(tr("Cannot create directory under a file.")); + return; + } + + // Retrieve the directory: + auto fileTree = item->entry()->astree(); + + bool ok = false; + QString result = QInputDialog::getText(this, tr("Enter a directory name"), tr("Name"), + QLineEdit::Normal, QString(), &ok); + result = result.trimmed(); + + if (ok && !result.isEmpty()) { + + // If a file with this name already exists: + if (fileTree->exists(result)) { + reportError(tr("A directory or file with that name already exists.")); + return; + } + + item->setExpanded(true); + auto* newItem = m_Tree->addDirectory(item, result); + m_Tree->scrollToItem(newItem); + } +} + +void InstallDialog::on_treeContent_customContextMenuRequested(QPoint pos) +{ + ArchiveTreeWidgetItem* selectedItem = + static_cast<ArchiveTreeWidgetItem*>(m_Tree->itemAt(pos)); + if (selectedItem == nullptr) { + return; + } + + QMenu menu; + + if (selectedItem != m_Tree->root() && selectedItem->entry()->isDir()) { + menu.addAction(tr("Set as <%1> directory").arg(m_DataFolderName), + [this, selectedItem]() { + m_Tree->setDataRoot(selectedItem); + }); + } + + if (m_Tree->root()->entry() != m_TreeRoot->entry()) { + menu.addAction(tr("Unset <%1> directory").arg(m_DataFolderName), [this]() { + m_Tree->setDataRoot(m_TreeRoot); + }); + } + + // Add a separator if not empty: + if (!menu.isEmpty()) { + menu.addSeparator(); + } + + if (selectedItem->entry()->isDir()) { + menu.addAction(tr("Create directory..."), [this, selectedItem]() { + createDirectoryUnder(selectedItem); + }); + } else { + menu.addAction(tr("&Open"), [this, selectedItem]() { + emit openFile(selectedItem->entry().get()); + }); + } + menu.exec(m_Tree->mapToGlobal(pos)); +} + +void InstallDialog::on_okButton_clicked() +{ + if (!testForProblem()) { + if (QMessageBox::question( + this, tr("Continue?"), + tr("This mod was probably NOT set up correctly, most likely it will NOT " + "work. " + "You should first correct the directory layout using the content-tree."), + QMessageBox::Ignore | QMessageBox::Cancel, + QMessageBox::Cancel) == QMessageBox::Cancel) { + return; + } + } + this->accept(); +} + +void InstallDialog::on_cancelButton_clicked() +{ + this->reject(); +} diff --git a/libs/installer_manual/src/installdialog.h b/libs/installer_manual/src/installdialog.h new file mode 100644 index 0000000..96e4085 --- /dev/null +++ b/libs/installer_manual/src/installdialog.h @@ -0,0 +1,124 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef INSTALLDIALOG_H +#define INSTALLDIALOG_H + +#include <QDialog> +#include <QProgressDialog> +#include <QTreeWidgetItem> +#include <QUuid> + +#include <uibase/game_features/moddatachecker.h> +#include <uibase/guessedvalue.h> +#include <uibase/ifiletree.h> +#include <uibase/iplugingame.h> +#include <uibase/tutorabledialog.h> + +#include "archivetree.h" + +namespace Ui +{ +class InstallDialog; +} + +/** + * a dialog presented to manually define how a mod is to be installed. It provides + * a tree view of the file contents that can modified directly + **/ +class InstallDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + /** + * @brief Create a new install dialog for the given tree. The tree + * is "own" by the dialog, i.e., any change made by the user is immediately + * reflected to the given tree, except for the changes to the root. + * + * @param tree Tree structure describing the original archive structure. + * @param modName Name of the mod. The name can be modified through the dialog. + * @param modDataChecker The mod data checker to use to check. + * @param dataName The name of the data folder for the game. + * @param parent Parent widget. + **/ + explicit InstallDialog(std::shared_ptr<MOBase::IFileTree> tree, + const MOBase::GuessedValue<QString>& modName, + std::shared_ptr<const MOBase::ModDataChecker> modDataChecker, + const QString& dataName, QWidget* parent = 0); + ~InstallDialog(); + + /** + * @brief retrieve the (modified) mod name + * + * @return updated mod name + **/ + QString getModName() const; + + /** + * @brief Retrieve the user-modified directory structure. + * + * @return the new tree represented by this dialog, which can be a new + * tree or a subtree of the original tree. + **/ + std::shared_ptr<MOBase::IFileTree> getModifiedTree() const; + +signals: + + /** + * @brief Signal emitted when user request the file corresponding + * to the given entry to be opened. + * + * @param entry Entry corresponding to the file to open. + */ + void openFile(const MOBase::FileTreeEntry* entry); + +private: + bool testForProblem(); + void updateProblems(); + void createDirectoryUnder(ArchiveTreeWidgetItem* treeItem); + +private slots: + + // Automatic slots that are directly bound to the UI: + void on_treeContent_customContextMenuRequested(QPoint pos); + void on_cancelButton_clicked(); + void on_okButton_clicked(); + +private: + Ui::InstallDialog* ui; + + std::shared_ptr<const MOBase::ModDataChecker> m_Checker; + + // Name of the "data" directory: + QString m_DataFolderName; + + // the tree root is the initial root that will never change (should be const + // but cannot be since the parent tree cannot be constructed in the member + // initializer list) + // + // the tree root is not actually added to the tree, but is used to maintain + // the state of the tree and not lose entries when unsetting data root + // + ArchiveTreeWidget* m_Tree; + ArchiveTreeWidgetItem* m_TreeRoot; + QLabel* m_ProblemLabel; +}; + +#endif // INSTALLDIALOG_H diff --git a/libs/installer_manual/src/installdialog.ui b/libs/installer_manual/src/installdialog.ui new file mode 100644 index 0000000..49a7fdc --- /dev/null +++ b/libs/installer_manual/src/installdialog.ui @@ -0,0 +1,162 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>InstallDialog</class> + <widget class="QDialog" name="InstallDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>516</width> + <height>407</height> + </rect> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>16777215</height> + </size> + </property> + <property name="windowTitle"> + <string>Install Mods</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="acceptDrops"> + <bool>false</bool> + </property> + <property name="toolTip"> + <string/> + </property> + <property name="title"> + <string/> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3" stretch="1,2"> + <item> + <widget class="QLabel" name="label"> + <property name="minimumSize"> + <size> + <width>50</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Name</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="nameCombo"> + <property name="editable"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Content</string> + </property> + </widget> + </item> + <item> + <widget class="ArchiveTreeWidget" name="treeContent"> + <property name="contextMenuPolicy"> + <enum>Qt::CustomContextMenu</enum> + </property> + <property name="toolTip"> + <string>Content of the archive. You can change the directory structure by using drag&drop. Hint: Also try right clicking...</string> + </property> + <property name="whatsThis"> + <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html></string> + </property> + <property name="locale"> + <locale language="English" country="UnitedStates"/> + </property> + <property name="dragEnabled"> + <bool>true</bool> + </property> + <property name="dragDropMode"> + <enum>QAbstractItemView::DragDrop</enum> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::ExtendedSelection</enum> + </property> + <attribute name="headerVisible"> + <bool>false</bool> + </attribute> + <column> + <property name="text"> + <string notr="true">1</string> + </property> + </column> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="problemLabel"> + <property name="font"> + <font> + <pointsize>8</pointsize> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Placeholder</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="okButton"> + <property name="text"> + <string>OK</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="cancelButton"> + <property name="text"> + <string>Cancel</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ArchiveTreeWidget</class> + <extends>QTreeWidget</extends> + <header>archivetree.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/libs/installer_manual/src/installer_manual_en.ts b/libs/installer_manual/src/installer_manual_en.ts new file mode 100644 index 0000000..3bc22c1 --- /dev/null +++ b/libs/installer_manual/src/installer_manual_en.ts @@ -0,0 +1,164 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="en_US"> +<context> + <name>ArchiveTreeWidget</name> + <message> + <location filename="archivetree.cpp" line="435"/> + <location filename="archivetree.cpp" line="445"/> + <source>Cannot drop</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="archivetree.cpp" line="436"/> + <source>Cannot drop '%1' into one of its subfolder.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="archivetree.cpp" line="447"/> + <source>A file '%1' already exists in folder '%2'.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="archivetree.cpp" line="450"/> + <source>A folder '%1' already exists in folder '%2'.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>InstallDialog</name> + <message> + <location filename="installdialog.ui" line="20"/> + <source>Install Mods</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installdialog.ui" line="46"/> + <location filename="installdialog.cpp" line="138"/> + <source>Name</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installdialog.ui" line="62"/> + <source>Content</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installdialog.ui" line="72"/> + <source>Content of the archive. You can change the directory structure by using drag&drop. Hint: Also try right clicking...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installdialog.ui" line="75"/> + <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installdialog.ui" line="118"/> + <source>Placeholder</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installdialog.ui" line="138"/> + <source>OK</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installdialog.ui" line="145"/> + <source>Cancel</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installdialog.cpp" line="138"/> + <source>Enter a directory name</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installdialog.cpp" line="100"/> + <source>Cannot check the content of <%1>.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installdialog.cpp" line="101"/> + <source>The plugin for the current game does not provide a way to check the content of <%1>.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installdialog.cpp" line="109"/> + <source>The content of <%1> looks valid.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installdialog.cpp" line="111"/> + <source>The content of <%1> seems valid for the current game.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installdialog.cpp" line="117"/> + <source>The content of <%1> does not look valid.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installdialog.cpp" line="119"/> + <source>The content of <%1> is probably not valid for the current game.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installdialog.cpp" line="130"/> + <source>Cannot create directory under a file.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installdialog.cpp" line="146"/> + <source>A directory or file with that name already exists.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installdialog.cpp" line="167"/> + <source>Set as <%1> directory</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installdialog.cpp" line="174"/> + <source>Unset <%1> directory</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installdialog.cpp" line="185"/> + <source>Create directory...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installdialog.cpp" line="189"/> + <source>&Open</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installdialog.cpp" line="200"/> + <source>Continue?</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installdialog.cpp" line="201"/> + <source>This mod was probably NOT set up correctly, most likely it will NOT work. You should first correct the directory layout using the content-tree.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>InstallerManual</name> + <message> + <location filename="installermanual.cpp" line="52"/> + <source>Manual Installer</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installermanual.cpp" line="62"/> + <source>Fallback installer for mods that can be extracted but can't be handled by another installer</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/libs/installer_manual/src/installermanual.cpp b/libs/installer_manual/src/installermanual.cpp new file mode 100644 index 0000000..278b5f2 --- /dev/null +++ b/libs/installer_manual/src/installermanual.cpp @@ -0,0 +1,136 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "installermanual.h" + +#include <QDialog> +#include <QDesktopServices> +#include <QUrl> +#include <QtPlugin> + +#include <uibase/game_features/igamefeatures.h> +#include <uibase/game_features/moddatachecker.h> +#include <uibase/iinstallationmanager.h> +#include <uibase/iplugingame.h> +#include <uibase/utility.h> + +#include "installdialog.h" + +using namespace MOBase; + +InstallerManual::InstallerManual() : m_MOInfo(nullptr) {} + +bool InstallerManual::init(IOrganizer* moInfo) +{ + m_MOInfo = moInfo; + return true; +} + +QString InstallerManual::name() const +{ + return "Manual Installer"; +} + +QString InstallerManual::localizedName() const +{ + return tr("Manual Installer"); +} + +QString InstallerManual::author() const +{ + return "Tannin, Holt59"; +} + +QString InstallerManual::description() const +{ + return tr("Fallback installer for mods that can be extracted but can't be handled by " + "another installer"); +} + +VersionInfo InstallerManual::version() const +{ + return VersionInfo(1, 0, 1, VersionInfo::RELEASE_FINAL); +} + +QList<PluginSetting> InstallerManual::settings() const +{ + return QList<PluginSetting>(); +} + +unsigned int InstallerManual::priority() const +{ + return 0; +} + +bool InstallerManual::isManualInstaller() const +{ + return true; +} + +bool InstallerManual::isArchiveSupported(std::shared_ptr<const MOBase::IFileTree>) const +{ + return true; +} + +void InstallerManual::openFile(const FileTreeEntry* entry) +{ + QString tempName = manager()->extractFile(entry->shared_from_this()); + +#ifdef _WIN32 + SHELLEXECUTEINFOW execInfo; + memset(&execInfo, 0, sizeof(SHELLEXECUTEINFOW)); + execInfo.cbSize = sizeof(SHELLEXECUTEINFOW); + execInfo.fMask = SEE_MASK_NOCLOSEPROCESS; + execInfo.lpVerb = L"open"; + std::wstring fileNameW = ToWString(tempName); + execInfo.lpFile = fileNameW.c_str(); + execInfo.nShow = SW_SHOWNORMAL; + if (!::ShellExecuteExW(&execInfo)) { + qCritical("failed to spawn %s: %d", qUtf8Printable(tempName), ::GetLastError()); + } +#else + if (!QDesktopServices::openUrl(QUrl::fromLocalFile(tempName))) { + qCritical("failed to open %s", qUtf8Printable(tempName)); + } +#endif +} + +IPluginInstaller::EInstallResult +InstallerManual::install(GuessedValue<QString>& modName, + std::shared_ptr<MOBase::IFileTree>& tree, QString&, int&) +{ + qDebug("offering installation dialog"); + InstallDialog dialog( + tree, modName, m_MOInfo->gameFeatures()->gameFeature<ModDataChecker>(), + m_MOInfo->managedGame()->dataDirectory().dirName().toLower(), parentWidget()); + connect(&dialog, &InstallDialog::openFile, this, &InstallerManual::openFile); + if (dialog.exec() == QDialog::Accepted) { + modName.update(dialog.getModName(), GUESS_USER); + + // TODO probably more complicated than necessary + tree = dialog.getModifiedTree(); + return IPluginInstaller::RESULT_SUCCESS; + } else { + return IPluginInstaller::RESULT_CANCELED; + } +} + +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) +Q_EXPORT_PLUGIN2(installerManual, InstallerManual) +#endif diff --git a/libs/installer_manual/src/installermanual.h b/libs/installer_manual/src/installermanual.h new file mode 100644 index 0000000..78bbd7f --- /dev/null +++ b/libs/installer_manual/src/installermanual.h @@ -0,0 +1,72 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef INSTALLERMANUAL_H +#define INSTALLERMANUAL_H + +#include <uibase/imoinfo.h> +#include <uibase/iplugininstallersimple.h> + +class InstallerManual : public MOBase::IPluginInstallerSimple +{ + Q_OBJECT + Q_INTERFACES(MOBase::IPlugin MOBase::IPluginInstaller MOBase::IPluginInstallerSimple) +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + Q_PLUGIN_METADATA(IID "org.tannin.InstallerManual") +#endif + +public: + InstallerManual(); + + virtual bool init(MOBase::IOrganizer* moInfo) override; + virtual QString name() const override; + virtual QString localizedName() const override; + virtual QString author() const override; + virtual QString description() const override; + virtual MOBase::VersionInfo version() const override; + virtual QList<MOBase::PluginSetting> settings() const override; + + virtual unsigned int priority() const; + virtual bool isManualInstaller() const; + + virtual bool isArchiveSupported(std::shared_ptr<const MOBase::IFileTree> tree) const; + virtual EInstallResult install(MOBase::GuessedValue<QString>& modName, + std::shared_ptr<MOBase::IFileTree>& tree, + QString& version, int& modID); + +private: + bool + isSimpleArchiveTopLayer(const std::shared_ptr<const MOBase::IFileTree> tree) const; + std::shared_ptr<const MOBase::IFileTree> + getSimpleArchiveBase(const std::shared_ptr<const MOBase::IFileTree> tree) const; + +private slots: + + /** + * @brief Opens a file from the archive in the (system-)default editor/viewer. + * + * @param entry Entry corresponding to the file to open. + */ + void openFile(const MOBase::FileTreeEntry* entry); + +private: + const MOBase::IOrganizer* m_MOInfo; +}; + +#endif // INSTALLERMANUAL_H |
