diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..6098e1f
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,41 @@
+---
+# We'll use defaults from the LLVM style, but with 4 columns indentation.
+BasedOnStyle: LLVM
+IndentWidth: 2
+---
+Language: Cpp
+DeriveLineEnding: false
+UseCRLF: true
+DerivePointerAlignment: false
+PointerAlignment: Left
+AlignConsecutiveAssignments: true
+AllowShortFunctionsOnASingleLine: Inline
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLambdasOnASingleLine: Empty
+AlwaysBreakTemplateDeclarations: Yes
+AccessModifierOffset: -2
+AlignTrailingComments: true
+SpacesBeforeTrailingComments: 2
+NamespaceIndentation: Inner
+MaxEmptyLinesToKeep: 1
+BreakBeforeBraces: Custom
+BraceWrapping:
+ AfterCaseLabel: false
+ AfterClass: true
+ AfterControlStatement: false
+ AfterEnum: true
+ AfterFunction: true
+ AfterNamespace: true
+ AfterStruct: true
+ AfterUnion: true
+ AfterExternBlock: true
+ BeforeCatch: false
+ BeforeElse: false
+ BeforeLambdaBody: false
+ BeforeWhile: false
+ IndentBraces: false
+ SplitEmptyFunction: false
+ SplitEmptyRecord: false
+ SplitEmptyNamespace: true
+ColumnLimit: 88
+ForEachMacros: ['Q_FOREACH', 'foreach']
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644
index 0000000..8fb9bf6
--- /dev/null
+++ b/.git-blame-ignore-revs
@@ -0,0 +1 @@
+1d3cd89fc817c47142dc3f6370a37801b4ea3f90
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..f869712
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,7 @@
+# Set the default behavior, in case people don't have core.autocrlf set.
+* text=auto
+
+# Explicitly declare text files you want to always be normalized and converted
+# to native line endings on checkout.
+*.cpp text eol=crlf
+*.h text eol=crlf
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..f36fa7d
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,16 @@
+name: Build Installer Manual Plugin
+
+on:
+ push:
+ branches: master
+ pull_request:
+ types: [opened, synchronize, reopened]
+
+jobs:
+ build:
+ runs-on: windows-2022
+ steps:
+ - name: Build Installer Manual Plugin
+ uses: ModOrganizer2/build-with-mob-action@master
+ with:
+ mo2-dependencies: cmake_common uibase
diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml
new file mode 100644
index 0000000..1b58829
--- /dev/null
+++ b/.github/workflows/linting.yml
@@ -0,0 +1,16 @@
+name: Lint Installer Manual Plugin
+
+on:
+ push:
+ pull_request:
+ types: [opened, synchronize, reopened]
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Check format
+ uses: ModOrganizer2/check-formatting-action@master
+ with:
+ check-path: "."
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 62a550d..0000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,40 +0,0 @@
-version: 1.0.{build}
-skip_branch_with_pr: true
-image: Visual Studio 2019
-environment:
- WEBHOOK_URL:
- secure: gOKbXaZM9ImtMD5XrYITvdyZUW/az082G9OIN1EC1Vbg57wBaeLhi49uGjxPw5GVujHku6kxN6ab89zhbS5GVeluR76GM83IbKV4Sh7udXzoYZZdg6YudtYHzdhCgUeiedpswbuczTq9ceIkkfSEWZuh/lMAAVVwvcGsJAnoPFw=
-build_script:
-- pwsh: >-
- $ErrorActionPreference = 'Stop'
-
- git clone --depth=1 --no-single-branch https://github.com/ModOrganizer2/modorganizer-umbrella.git c:\projects\modorganizer-umbrella
-
- New-Item -ItemType Directory -Path c:\projects\modorganizer-build
-
- cd c:\projects\modorganizer-umbrella
-
- ($env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH -eq $null) ? ($branch = $env:APPVEYOR_REPO_BRANCH) : ($branch = $env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH)
-
- git checkout $(git show-ref --verify --quiet refs/remotes/origin/${branch} || echo '-b') ${branch}
-
- C:\Python37-x64\python.exe unimake.py -d c:\projects\modorganizer-build -s Appveyor_Build=True ${env:APPVEYOR_PROJECT_NAME}
-
- if($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode ) }
-artifacts:
-- path: vsbuild\src\RelWithDebInfo\installer_manual.dll
- name: installer_manual_dll
-- path: vsbuild\src\RelWithDebInfo\installer_manual.pdb
- name: installer_manual_pdb
-- path: vsbuild\src\RelWithDebInfo\installer_manual.lib
- name: installer_manual_lib
-on_success:
- - ps: Set-Location -Path $env:APPVEYOR_BUILD_FOLDER
- - ps: Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1
- - ps: ./send.ps1 success $env:WEBHOOK_URL
-on_failure:
- - ps: Set-Location -Path $env:APPVEYOR_BUILD_FOLDER
- - ps: Push-AppveyorArtifact ${env:APPVEYOR_BUILD_FOLDER}\stdout.log
- - ps: Push-AppveyorArtifact ${env:APPVEYOR_BUILD_FOLDER}\stderr.log
- - ps: Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1
- - ps: ./send.ps1 failure $env:WEBHOOK_URL
\ No newline at end of file
diff --git a/src/archivetree.cpp b/src/archivetree.cpp
index f20cd59..cccd79a 100644
--- a/src/archivetree.cpp
+++ b/src/archivetree.cpp
@@ -1,468 +1,484 @@
-/*
-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 .
-*/
-
-#include "archivetree.h"
-
-#include
-#include
-#include
-
-#include
-#include
-#include
-
-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 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(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(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(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(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(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(currentItem()),
- static_cast(itemAt(event->pos())))) {
- 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 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(itemAt(event->pos()));
-
- // 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(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(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);
-
-}
+/*
+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 .
+*/
+
+#include "archivetree.h"
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+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 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(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(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(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(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(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(currentItem()),
+ static_cast(itemAt(event->pos())))) {
+ 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 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(itemAt(event->pos()));
+
+ // 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(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(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/src/archivetree.h b/src/archivetree.h
index 69b3ec6..9984bea 100644
--- a/src/archivetree.h
+++ b/src/archivetree.h
@@ -1,187 +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 .
-*/
-
-#ifndef ARCHIVETREE_H
-#define ARCHIVETREE_H
-
-#include
-
-#include "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 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 entry) {
- m_Entry = entry;
- }
-
- // retrieve the entry corresponding to this item
- //
- std::shared_ptr 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(QTreeWidgetItem::parent());
- }
-
- ArchiveTreeWidgetItem* child(int index) const {
- return static_cast(QTreeWidgetItem::child(index));
- }
-
-protected:
-
- std::shared_ptr 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 )
- //
- 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
+/*
+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 .
+*/
+
+#ifndef ARCHIVETREE_H
+#define ARCHIVETREE_H
+
+#include
+
+#include "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 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 entry) { m_Entry = entry; }
+
+ // retrieve the entry corresponding to this item
+ //
+ std::shared_ptr 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(QTreeWidgetItem::parent());
+ }
+
+ ArchiveTreeWidgetItem* child(int index) const
+ {
+ return static_cast(QTreeWidgetItem::child(index));
+ }
+
+protected:
+ std::shared_ptr 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 )
+ //
+ 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/src/installdialog.cpp b/src/installdialog.cpp
index 426c02d..1861d6f 100644
--- a/src/installdialog.cpp
+++ b/src/installdialog.cpp
@@ -1,193 +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 .
-*/
-
-#include "installdialog.h"
-#include "ui_installdialog.h"
-
-#include "report.h"
-#include "utility.h"
-#include "log.h"
-
-#include
-#include
-#include
-#include
-#include
-
-using namespace MOBase;
-
-
-InstallDialog::InstallDialog(std::shared_ptr tree, const GuessedValue &modName, std::shared_ptr 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 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(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();
-}
+/*
+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 .
+*/
+
+#include "installdialog.h"
+#include "ui_installdialog.h"
+
+#include "log.h"
+#include "report.h"
+#include "utility.h"
+
+#include
+#include
+#include
+#include
+#include
+
+using namespace MOBase;
+
+InstallDialog::InstallDialog(
+ std::shared_ptr tree, const GuessedValue& modName,
+ std::shared_ptr 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 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(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/src/installdialog.h b/src/installdialog.h
index 5321180..d49afa7 100644
--- a/src/installdialog.h
+++ b/src/installdialog.h
@@ -1,126 +1,126 @@
-/*
-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 .
-*/
-
-#ifndef INSTALLDIALOG_H
-#define INSTALLDIALOG_H
-
-#include "archivetree.h"
-#include "tutorabledialog.h"
-#include
-#include
-
-#include
-#include
-
-#include
-#include
-#include
-#include
-#define WIN32_LEAN_AND_MEAN
-#include
-
-
-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 tree, const MOBase::GuessedValue &modName, std::shared_ptr 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 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 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
+/*
+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 .
+*/
+
+#ifndef INSTALLDIALOG_H
+#define INSTALLDIALOG_H
+
+#include "archivetree.h"
+#include "tutorabledialog.h"
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#define WIN32_LEAN_AND_MEAN
+#include
+
+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 tree,
+ const MOBase::GuessedValue& modName,
+ std::shared_ptr 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 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 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/src/installermanual.cpp b/src/installermanual.cpp
index 661c3f1..25d302d 100644
--- a/src/installermanual.cpp
+++ b/src/installermanual.cpp
@@ -1,138 +1,130 @@
-/*
-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 .
-*/
-
-#include "installermanual.h"
-
-#include
-#include
-#include
-#include
-#include
-
-#include
-#include
-
-#include
-
-#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 InstallerManual::settings() const
-{
- return QList();
-}
-
-unsigned int InstallerManual::priority() const
-{
- return 0;
-}
-
-
-bool InstallerManual::isManualInstaller() const
-{
- return true;
-}
-
-
-bool InstallerManual::isArchiveSupported(std::shared_ptr) const
-{
- return true;
-}
-
-
-void InstallerManual::openFile(const FileTreeEntry* entry)
-{
- QString tempName = manager()->extractFile(entry->shared_from_this());
-
- 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());
- }
-}
-
-
-IPluginInstaller::EInstallResult InstallerManual::install(
- GuessedValue& modName, std::shared_ptr& tree, QString&, int&)
-{
- qDebug("offering installation dialog");
- InstallDialog dialog(tree, modName, m_MOInfo->gameFeatures()->gameFeature(),
- 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
-
+/*
+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 .
+*/
+
+#include "installermanual.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+
+#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 InstallerManual::settings() const
+{
+ return QList();
+}
+
+unsigned int InstallerManual::priority() const
+{
+ return 0;
+}
+
+bool InstallerManual::isManualInstaller() const
+{
+ return true;
+}
+
+bool InstallerManual::isArchiveSupported(std::shared_ptr) const
+{
+ return true;
+}
+
+void InstallerManual::openFile(const FileTreeEntry* entry)
+{
+ QString tempName = manager()->extractFile(entry->shared_from_this());
+
+ 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());
+ }
+}
+
+IPluginInstaller::EInstallResult
+InstallerManual::install(GuessedValue& modName,
+ std::shared_ptr& tree, QString&, int&)
+{
+ qDebug("offering installation dialog");
+ InstallDialog dialog(
+ tree, modName, m_MOInfo->gameFeatures()->gameFeature(),
+ 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/src/installermanual.h b/src/installermanual.h
index 333047c..14f16d3 100644
--- a/src/installermanual.h
+++ b/src/installermanual.h
@@ -1,75 +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 .
-*/
-
-#ifndef INSTALLERMANUAL_H
-#define INSTALLERMANUAL_H
-
-#include
-#include
-
-
-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" FILE "installermanual.json")
-#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 settings() const override;
-
- virtual unsigned int priority() const;
- virtual bool isManualInstaller() const;
-
- virtual bool isArchiveSupported(std::shared_ptr tree) const;
- virtual EInstallResult install(MOBase::GuessedValue &modName, std::shared_ptr &tree,
- QString &version, int &modID);
-
-private:
-
- bool isSimpleArchiveTopLayer(const std::shared_ptr tree) const;
- std::shared_ptr getSimpleArchiveBase(const std::shared_ptr 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
+/*
+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 .
+*/
+
+#ifndef INSTALLERMANUAL_H
+#define INSTALLERMANUAL_H
+
+#include
+#include
+
+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" FILE "installermanual.json")
+#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 settings() const override;
+
+ virtual unsigned int priority() const;
+ virtual bool isManualInstaller() const;
+
+ virtual bool isArchiveSupported(std::shared_ptr tree) const;
+ virtual EInstallResult install(MOBase::GuessedValue& modName,
+ std::shared_ptr& tree,
+ QString& version, int& modID);
+
+private:
+ bool
+ isSimpleArchiveTopLayer(const std::shared_ptr tree) const;
+ std::shared_ptr
+ getSimpleArchiveBase(const std::shared_ptr 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