diff options
| author | Tannin <devnull@localhost> | 2014-01-23 00:05:46 +0100 |
|---|---|---|
| committer | Tannin <devnull@localhost> | 2014-01-23 00:05:46 +0100 |
| commit | e597823337c858f2985c615ee5147f47567991ee (patch) | |
| tree | e42647cf1114ebd4d42e4a8c15a91b941961c3df /src | |
| parent | e69210b3a78c4a6c63086d84e6bdb2c3b47d8944 (diff) | |
- boss integration
- plugin list can now also display multiple flags for a file (like the mod list)
- changed some compiler&linker settings to produce smaller binaries
Diffstat (limited to 'src')
| -rw-r--r-- | src/ModOrganizer.pro | 3 | ||||
| -rw-r--r-- | src/aboutdialog.cpp | 2 | ||||
| -rw-r--r-- | src/icondelegate.cpp | 30 | ||||
| -rw-r--r-- | src/icondelegate.h | 5 | ||||
| -rw-r--r-- | src/lockeddialog.cpp | 104 | ||||
| -rw-r--r-- | src/lockeddialog.h | 96 | ||||
| -rw-r--r-- | src/logbuffer.cpp | 3 | ||||
| -rw-r--r-- | src/main.cpp | 5 | ||||
| -rw-r--r-- | src/mainwindow.cpp | 26 | ||||
| -rw-r--r-- | src/mainwindow.h | 1 | ||||
| -rw-r--r-- | src/mainwindow.ui | 82 | ||||
| -rw-r--r-- | src/modflagicondelegate.cpp | 50 | ||||
| -rw-r--r-- | src/modflagicondelegate.h | 18 | ||||
| -rw-r--r-- | src/organizer.pro | 32 | ||||
| -rw-r--r-- | src/pdll.h | 402 | ||||
| -rw-r--r-- | src/pluginflagicondelegate.cpp | 24 | ||||
| -rw-r--r-- | src/pluginflagicondelegate.h | 17 | ||||
| -rw-r--r-- | src/pluginlist.cpp | 270 | ||||
| -rw-r--r-- | src/pluginlist.h | 95 | ||||
| -rw-r--r-- | src/pluginlistsortproxy.cpp | 5 | ||||
| -rw-r--r-- | src/profile.cpp | 34 | ||||
| -rw-r--r-- | src/resources.qrc | 2 | ||||
| -rw-r--r-- | src/safewritefile.cpp | 48 | ||||
| -rw-r--r-- | src/safewritefile.h | 51 |
24 files changed, 1179 insertions, 226 deletions
diff --git a/src/ModOrganizer.pro b/src/ModOrganizer.pro index dbf6ab6e..05b05855 100644 --- a/src/ModOrganizer.pro +++ b/src/ModOrganizer.pro @@ -13,11 +13,12 @@ SUBDIRS = bsatk \ nxmhandler \
BossDummy \
pythonRunner \
+ boss_modified \
esptk
plugins.depends = pythonRunner
hookdll.depends = shared
-organizer.depends = shared uibase plugins
+organizer.depends = shared uibase plugins boss_modified
CONFIG(debug, debug|release) {
DESTDIR = outputd
diff --git a/src/aboutdialog.cpp b/src/aboutdialog.cpp index c7f95640..a8f9b4db 100644 --- a/src/aboutdialog.cpp +++ b/src/aboutdialog.cpp @@ -44,6 +44,8 @@ AboutDialog::AboutDialog(const QString &version, QWidget *parent) addLicense("7-zip", LICENSE_LGPL3);
addLicense("ZLib", LICENSE_ZLIB);
addLicense("NIF File Format Library", LICENSE_BSD3);
+ addLicense("BOSS (modified)", LICENSE_GPL3);
+ addLicense("Alphanum Algorithm", LICENSE_ZLIB);
ui->nameLabel->setText(QString("<span style=\"font-size:12pt; font-weight:600;\">%1 %2</span>").arg(ui->nameLabel->text()).arg(version));
#ifdef HGID
diff --git a/src/icondelegate.cpp b/src/icondelegate.cpp index 2540e1d5..048b09f9 100644 --- a/src/icondelegate.cpp +++ b/src/icondelegate.cpp @@ -29,38 +29,17 @@ IconDelegate::IconDelegate(QObject *parent) } -QIcon IconDelegate::getFlagIcon(ModInfo::EFlag flag) const -{ - switch (flag) { - case ModInfo::FLAG_BACKUP: return QIcon(":/MO/gui/emblem_backup"); - case ModInfo::FLAG_INVALID: return QIcon(":/MO/gui/emblem_problem"); - case ModInfo::FLAG_NOTENDORSED: return QIcon(":/MO/gui/emblem_notendorsed"); - case ModInfo::FLAG_NOTES: return QIcon(":/MO/gui/emblem_notes"); - case ModInfo::FLAG_CONFLICT_OVERWRITE: return QIcon(":/MO/gui/emblem_conflict_overwrite"); - case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return QIcon(":/MO/gui/emblem_conflict_overwritten"); - case ModInfo::FLAG_CONFLICT_MIXED: return QIcon(":/MO/gui/emblem_conflict_mixed"); - case ModInfo::FLAG_CONFLICT_REDUNDANT: return QIcon(":MO/gui/emblem_conflict_redundant"); - default: return QIcon(); - } -} - - void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyledItemDelegate::paint(painter, option, index); - QVariant modid = index.data(Qt::UserRole + 1); - if (!modid.isValid()) { - return; - } - ModInfo::Ptr info = ModInfo::getByIndex(modid.toInt()); - std::vector<ModInfo::EFlag> flags = info->getFlags(); + + QList<QIcon> icons = getIcons(index); int x = 4; painter->save(); painter->translate(option.rect.topLeft()); - for (auto iter = flags.begin(); iter != flags.end(); ++iter) { - QIcon temp = getFlagIcon(*iter); - painter->drawPixmap(x, 2, 16, 16, temp.pixmap(QSize(16, 16))); + for (auto iter = icons.begin(); iter != icons.end(); ++iter) { + painter->drawPixmap(x, 2, 16, 16, iter->pixmap(QSize(16, 16))); x += 20; } @@ -70,6 +49,7 @@ void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, QSize IconDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex &modelIndex) const { + int count = getNumIcons(modelIndex); unsigned int index = modelIndex.data(Qt::UserRole + 1).toInt(); if (index < ModInfo::getNumMods()) { ModInfo::Ptr info = ModInfo::getByIndex(index); diff --git a/src/icondelegate.h b/src/icondelegate.h index dd9f9dfc..cf292443 100644 --- a/src/icondelegate.h +++ b/src/icondelegate.h @@ -34,13 +34,16 @@ public: virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + signals: public slots: private: - QIcon getFlagIcon(ModInfo::EFlag flag) const; + virtual QList<QIcon> getIcons(const QModelIndex &index) const = 0; + virtual size_t getNumIcons(const QModelIndex &index) const = 0; + private: diff --git a/src/lockeddialog.cpp b/src/lockeddialog.cpp index 4c19c615..065d9270 100644 --- a/src/lockeddialog.cpp +++ b/src/lockeddialog.cpp @@ -17,51 +17,59 @@ 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 "lockeddialog.h"
-#include "ui_lockeddialog.h"
-#include <QResizeEvent>
-
-
-LockedDialog::LockedDialog(QWidget *parent) :
- QDialog(parent),
- ui(new Ui::LockedDialog), m_UnlockClicked(false)
-{
- ui->setupUi(this);
-
- this->setWindowFlags(this->windowFlags() | Qt::ToolTip | Qt::FramelessWindowHint);
-
- if (parent != NULL) {
- QPoint position = parent->mapToGlobal(QPoint(parent->width() / 2, parent->height() / 2));
- position.rx() -= this->width() / 2;
- position.ry() -= this->height() / 2;
- move(position);
- }
-}
-
-LockedDialog::~LockedDialog()
-{
- delete ui;
-}
-
-
-void LockedDialog::setProcessName(const QString &name)
-{
- ui->processLabel->setText(name);
-}
-
-
-void LockedDialog::resizeEvent(QResizeEvent *event)
-{
- QWidget *par = parentWidget();
- if (par != NULL) {
- QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height() / 2));
- position.rx() -= event->size().width() / 2;
- position.ry() -= event->size().height() / 2;
- move(position);
- }
-}
-
-void LockedDialog::on_unlockButton_clicked()
-{
- m_UnlockClicked = true;
-}
+#include "lockeddialog.h" +#include "ui_lockeddialog.h" +#include <QResizeEvent> + + +LockedDialog::LockedDialog(QWidget *parent, const QString &text, bool unlockButton) + : QDialog(parent) + , ui(new Ui::LockedDialog) + , m_UnlockClicked(false) +{ + ui->setupUi(this); + + this->setWindowFlags(this->windowFlags() | Qt::ToolTip | Qt::FramelessWindowHint); + + if (parent != NULL) { + QPoint position = parent->mapToGlobal(QPoint(parent->width() / 2, parent->height() / 2)); + position.rx() -= this->width() / 2; + position.ry() -= this->height() / 2; + move(position); + } + + if (text.length() > 0) { + ui->label->setText(text); + } + if (!unlockButton) { + ui->unlockButton->hide(); + } +} + +LockedDialog::~LockedDialog() +{ + delete ui; +} + + +void LockedDialog::setProcessName(const QString &name) +{ + ui->processLabel->setText(name); +} + + +void LockedDialog::resizeEvent(QResizeEvent *event) +{ + QWidget *par = parentWidget(); + if (par != NULL) { + QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height() / 2)); + position.rx() -= event->size().width() / 2; + position.ry() -= event->size().height() / 2; + move(position); + } +} + +void LockedDialog::on_unlockButton_clicked() +{ + m_UnlockClicked = true; +} diff --git a/src/lockeddialog.h b/src/lockeddialog.h index 5be398bc..0f3b29b0 100644 --- a/src/lockeddialog.h +++ b/src/lockeddialog.h @@ -17,51 +17,51 @@ 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 LOCKEDDIALOG_H
-#define LOCKEDDIALOG_H
-
-#include <QDialog>
-
-namespace Ui {
- class LockedDialog;
-}
-
-/**
- * a small borderless dialog displayed while the Mod Organizer UI is locked
- * The dialog contains only a label and a button to force the UI to be unlocked
- *
- * The UI gets locked while running external applications since they may modify the
- * data on which Mod Organizer works. After the UI is unlocked (manually or after the
- * external application closed) MO will refresh all of its data sources
- **/
-class LockedDialog : public QDialog
-{
- Q_OBJECT
-
-public:
- explicit LockedDialog(QWidget *parent = 0);
- ~LockedDialog();
-
- /**
- * @brief see if the user clicked the unlock-button
- *
- * @return true if the user clicked the unlock button
- **/
- bool unlockClicked() const { return m_UnlockClicked; }
-
- void setProcessName(const QString &name);
-
-protected:
-
- virtual void resizeEvent(QResizeEvent *event);
-
-private slots:
-
- void on_unlockButton_clicked();
-
-private:
- Ui::LockedDialog *ui;
- bool m_UnlockClicked;
-};
-
-#endif // LOCKEDDIALOG_H
+#ifndef LOCKEDDIALOG_H +#define LOCKEDDIALOG_H + +#include <QDialog> + +namespace Ui { + class LockedDialog; +} + +/** + * a small borderless dialog displayed while the Mod Organizer UI is locked + * The dialog contains only a label and a button to force the UI to be unlocked + * + * The UI gets locked while running external applications since they may modify the + * data on which Mod Organizer works. After the UI is unlocked (manually or after the + * external application closed) MO will refresh all of its data sources + **/ +class LockedDialog : public QDialog +{ + Q_OBJECT + +public: + explicit LockedDialog(QWidget *parent = 0, const QString &text = "", bool unlockButton = true); + ~LockedDialog(); + + /** + * @brief see if the user clicked the unlock-button + * + * @return true if the user clicked the unlock button + **/ + bool unlockClicked() const { return m_UnlockClicked; } + + void setProcessName(const QString &name); + +protected: + + virtual void resizeEvent(QResizeEvent *event); + +private slots: + + void on_unlockButton_clicked(); + +private: + Ui::LockedDialog *ui; + bool m_UnlockClicked; +}; + +#endif // LOCKEDDIALOG_H diff --git a/src/logbuffer.cpp b/src/logbuffer.cpp index 4f72e551..689d2b55 100644 --- a/src/logbuffer.cpp +++ b/src/logbuffer.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "report.h" #include <QMutexLocker> #include <QFile> +#include <QDateTime> #include <Windows.h> QScopedPointer<LogBuffer> LogBuffer::s_Instance; @@ -124,8 +125,6 @@ char LogBuffer::msgTypeID(QtMsgType type) } } -#include <QDateTime> - void LogBuffer::log(QtMsgType type, const char *message) { QMutexLocker guard(&s_Mutex); diff --git a/src/main.cpp b/src/main.cpp index 8b74ba83..05d68ec7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -119,7 +119,8 @@ bool bootstrap() QDir::fromNativeSeparators(ToQString(gameInfo.getDownloadDir())), QDir::fromNativeSeparators(ToQString(gameInfo.getOverwriteDir())), QDir::fromNativeSeparators(ToQString(gameInfo.getLogDir())), - QDir::fromNativeSeparators(ToQString(gameInfo.getTutorialDir())) + QDir::fromNativeSeparators(ToQString(gameInfo.getTutorialDir())), + QDir::fromNativeSeparators(ToQString(gameInfo.getOrganizerDirectory()) + "/boss") }; static const int NUM_DIRECTORIES = sizeof(dirNames) / sizeof(QString); @@ -284,7 +285,7 @@ int main(int argc, char *argv[]) SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); - LogBuffer::init(20, QtDebugMsg, application.applicationDirPath() + "/logs/mo_interface.log"); + LogBuffer::init(200, QtDebugMsg, application.applicationDirPath() + "/logs/mo_interface.log"); qDebug("Working directory: %s", qPrintable(QDir::toNativeSeparators(QDir::currentPath()))); qDebug("MO at: %s", qPrintable(QDir::toNativeSeparators(application.applicationDirPath()))); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 582bc283..0f98989c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -48,7 +48,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "filedialogmemory.h" #include "questionboxmemory.h" #include "tutorialmanager.h" -#include "icondelegate.h" +#include "modflagicondelegate.h" +#include "pluginflagicondelegate.h" #include "credentialsdialog.h" #include "selectiondialog.h" #include "csvbuilder.h" @@ -104,6 +105,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QtConcurrentRun> #include <QCoreApplication> #include <scopeguard.h> +#include <boost/thread.hpp> #ifdef TEST_MODELS @@ -186,7 +188,7 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget ui->modList->setModel(m_ModListSortProxy); ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); - ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new IconDelegate(ui->modList)); + ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new ModFlagIconDelegate(ui->modList)); //ui->modList->setAcceptDrops(true); ui->modList->header()->installEventFilter(&m_ModList); if (initSettings.contains("mod_list_state")) { @@ -206,6 +208,7 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget ui->espList->setModel(m_PluginListSortProxy); ui->espList->sortByColumn(PluginList::COL_PRIORITY, Qt::AscendingOrder); + ui->espList->setItemDelegateForColumn(PluginList::COL_FLAGS, new PluginFlagIconDelegate(ui->espList)); if (initSettings.contains("plugin_list_state")) { ui->espList->header()->restoreState(initSettings.value("plugin_list_state").toByteArray()); } @@ -1314,6 +1317,8 @@ HANDLE MainWindow::spawnBinaryDirect(const QFileInfo &binary, const QString &arg QCoreApplication::processEvents(); } + m_CurrentProfile->writeModlistNow(true); + // TODO: should also pass arguments if (m_AboutToRun(binary.absoluteFilePath())) { return startBinary(binary, arguments, profileName, m_Settings.logLevel(), currentDirectory, true); @@ -1778,7 +1783,7 @@ void MainWindow::refreshESPList() m_CurrentProfile->getLoadOrderFileName(), m_CurrentProfile->getLockedOrderFileName()); } catch (const std::exception &e) { - reportError(tr("Failed to refresh list of esps: %s").arg(e.what())); + reportError(tr("Failed to refresh list of esps: %1").arg(e.what())); } } @@ -5123,3 +5128,18 @@ void MainWindow::on_showHiddenBox_toggled(bool checked) m_DownloadManager.setShowHidden(checked); } +void MainWindow::on_bossButton_clicked() +{ + try { + LockedDialog dialog(this, tr("BOSS working"), false); + dialog.show(); + qApp->processEvents(); + m_PluginList.bossSort(); + + savePluginList(); + dialog.hide(); + } catch (const std::exception &e) { + reportError(tr("failed to run boss: %1").arg(e.what())); + ui->bossButton->setEnabled(false); + } +} diff --git a/src/mainwindow.h b/src/mainwindow.h index c01ce767..b247b924 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -564,6 +564,7 @@ private slots: // ui slots void on_linkButton_pressed(); void on_showHiddenBox_toggled(bool checked); void on_bsaList_itemChanged(QTreeWidgetItem *item, int column); + void on_bossButton_clicked(); }; #endif // MAINWINDOW_H diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 5c89a7e0..f3bb425c 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -31,7 +31,16 @@ <property name="spacing">
<number>4</number>
</property>
- <property name="margin">
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
<number>6</number>
</property>
<item>
@@ -705,14 +714,25 @@ p, li { white-space: pre-wrap; } </widget>
</item>
<item>
- <widget class="MOBase::LineEditClear" name="espFilterEdit">
- <property name="text">
- <string/>
- </property>
- <property name="placeholderText">
- <string>Namefilter</string>
- </property>
- </widget>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="MOBase::LineEditClear" name="espFilterEdit">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="placeholderText">
+ <string>Namefilter</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="bossButton">
+ <property name="text">
+ <string>Sort</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
</layout>
</widget>
@@ -721,7 +741,16 @@ p, li { white-space: pre-wrap; } <string notr="true">Archives</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_9">
- <property name="margin">
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
<number>6</number>
</property>
<item>
@@ -801,7 +830,16 @@ BSAs checked here are loaded in such a way that your installation order is obeye <string>Data</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_5">
- <property name="margin">
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
<number>6</number>
</property>
<item>
@@ -871,7 +909,16 @@ BSAs checked here are loaded in such a way that your installation order is obeye <string>Saves</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
- <property name="margin">
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
<number>6</number>
</property>
<item>
@@ -900,7 +947,16 @@ p, li { white-space: pre-wrap; } <string>Downloads</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_7">
- <property name="margin">
+ <property name="leftMargin">
+ <number>2</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>2</number>
+ </property>
+ <property name="bottomMargin">
<number>2</number>
</property>
<item>
diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp new file mode 100644 index 00000000..db69eb52 --- /dev/null +++ b/src/modflagicondelegate.cpp @@ -0,0 +1,50 @@ +#include "modflagicondelegate.h"
+#include <QList>
+
+
+ModFlagIconDelegate::ModFlagIconDelegate(QObject *parent)
+ : IconDelegate(parent)
+{
+}
+
+QList<QIcon> ModFlagIconDelegate::getIcons(const QModelIndex &index) const
+{
+ QList<QIcon> result;
+ QVariant modid = index.data(Qt::UserRole + 1);
+ if (modid.isValid()) {
+ ModInfo::Ptr info = ModInfo::getByIndex(modid.toInt());
+ std::vector<ModInfo::EFlag> flags = info->getFlags();
+
+ for (auto iter = flags.begin(); iter != flags.end(); ++iter) {
+ result.append(getFlagIcon(*iter));
+ }
+ }
+ return result;
+}
+
+QIcon ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag) const
+{
+ switch (flag) {
+ case ModInfo::FLAG_BACKUP: return QIcon(":/MO/gui/emblem_backup");
+ case ModInfo::FLAG_INVALID: return QIcon(":/MO/gui/emblem_problem");
+ case ModInfo::FLAG_NOTENDORSED: return QIcon(":/MO/gui/emblem_notendorsed");
+ case ModInfo::FLAG_NOTES: return QIcon(":/MO/gui/emblem_notes");
+ case ModInfo::FLAG_CONFLICT_OVERWRITE: return QIcon(":/MO/gui/emblem_conflict_overwrite");
+ case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return QIcon(":/MO/gui/emblem_conflict_overwritten");
+ case ModInfo::FLAG_CONFLICT_MIXED: return QIcon(":/MO/gui/emblem_conflict_mixed");
+ case ModInfo::FLAG_CONFLICT_REDUNDANT: return QIcon(":MO/gui/emblem_conflict_redundant");
+ default: return QIcon();
+ }
+}
+
+size_t ModFlagIconDelegate::getNumIcons(const QModelIndex &index) const
+{
+ unsigned int modIdx = index.data(Qt::UserRole + 1).toInt();
+ if (modIdx < ModInfo::getNumMods()) {
+ ModInfo::Ptr info = ModInfo::getByIndex(modIdx);
+ return info->getFlags().size();
+ } else {
+ return 0;
+ }
+}
+
diff --git a/src/modflagicondelegate.h b/src/modflagicondelegate.h new file mode 100644 index 00000000..800e2741 --- /dev/null +++ b/src/modflagicondelegate.h @@ -0,0 +1,18 @@ +#ifndef MODFLAGICONDELEGATE_H
+#define MODFLAGICONDELEGATE_H
+
+#include "icondelegate.h"
+
+class ModFlagIconDelegate : public IconDelegate
+{
+public:
+ explicit ModFlagIconDelegate(QObject *parent = 0);
+private:
+ virtual QList<QIcon> getIcons(const QModelIndex &index) const;
+ virtual size_t getNumIcons(const QModelIndex &index) const;
+
+ QIcon getFlagIcon(ModInfo::EFlag flag) const;
+
+};
+
+#endif // MODFLAGICONDELEGATE_H
diff --git a/src/organizer.pro b/src/organizer.pro index fd72dea3..9a9d0da3 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -81,7 +81,10 @@ SOURCES += \ previewgenerator.cpp \
previewdialog.cpp \
aboutdialog.cpp \
- json.cpp
+ json.cpp \
+ safewritefile.cpp \
+ modflagicondelegate.cpp \
+ pluginflagicondelegate.cpp
HEADERS += \
@@ -152,7 +155,11 @@ HEADERS += \ previewgenerator.h \
previewdialog.h \
aboutdialog.h \
- json.h
+ json.h \
+ safewritefile.h\
+ pdll.h \
+ modflagicondelegate.h \
+ pluginflagicondelegate.h
FORMS += \
transfersavesdialog.ui \
@@ -185,24 +192,28 @@ FORMS += \ previewdialog.ui \
aboutdialog.ui
-INCLUDEPATH += ../shared ../archive ../uibase ../bsatk ../esptk "$(BOOSTPATH)"
+INCLUDEPATH += ../shared ../archive ../uibase ../bsatk ../esptk ../boss_modified/boss-api "$(BOOSTPATH)"
LIBS += -L"$(BOOSTPATH)/stage/lib"
CONFIG(debug, debug|release) {
OUTDIR = $$OUT_PWD/debug
DSTDIR = $$PWD/../../outputd
- LIBS += -L$$OUT_PWD/../shared/debug -L$$OUT_PWD/../bsatk/debug
- LIBS += -L$$OUT_PWD/../uibase/debug
- LIBS += -lDbgHelp
+ LIBS += -L$$OUT_PWD/../shared/debug
+ LIBS += -L$$OUT_PWD/../bsatk/debug
+ LIBS += -L$$OUT_PWD/../uibase/debug
+ LIBS += -L$$OUT_PWD/../boss_modified/debug
+ LIBS += -lDbgHelp
} else {
OUTDIR = $$OUT_PWD/release
DSTDIR = $$PWD/../../output
- LIBS += -L$$OUT_PWD/../shared/release -L$$OUT_PWD/../bsatk/release
+ LIBS += -L$$OUT_PWD/../shared/release
+ LIBS += -L$$OUT_PWD/../bsatk/release
LIBS += -L$$OUT_PWD/../uibase/release
- QMAKE_CXXFLAGS += /Zi
+ LIBS += -L$$OUT_PWD/../boss_modified/release
+ QMAKE_CXXFLAGS += /Zi /GL
# QMAKE_CXXFLAGS -= -O2
- QMAKE_LFLAGS += /DEBUG
+ QMAKE_LFLAGS += /DEBUG /LTCG /OPT:REF /OPT:ICF
}
#QMAKE_CXXFLAGS_WARN_ON -= -W3
@@ -299,9 +310,6 @@ OTHER_FILES += \ tutorials/tutorial_window_installer.js \
tutorials/tutorials_installdialog.qml
-INCLUDEPATH += "$(ZLIBPATH)" "$(ZLIBPATH)/build" "$(BOOSTPATH)"
-LIBS += -L"$(ZLIBPATH)/build" -lzlibstatic
-
# leak detection with vld
#INCLUDEPATH += "E:/Visual Leak Detector/include"
diff --git a/src/pdll.h b/src/pdll.h new file mode 100644 index 00000000..6e1d06f3 --- /dev/null +++ b/src/pdll.h @@ -0,0 +1,402 @@ +////////////////////////////////////////////////////////////////////////////////////////////////////
+// Class: PDll //
+// Authors: MicHael Galkovsky //
+// Date: April 14, 1998 //
+// Company: Pervasive Software //
+// Purpose: Base class to wrap dynamic use of dll //
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+#if !defined (_PDLL_H_)
+#define _PDLL_H_
+
+#include <windows.h>
+#include <winbase.h>
+
+#include <windows_error.h>
+#include <tchar.h>
+#include <sstream>
+
+
+#define FUNC_LOADED 3456
+
+//function declarations according to the number of parameters
+//define the type
+//declare a variable of that type
+//declare a member function by the same name as the dll function
+//check for dll handle
+//if this is the first call to the function then try to load it
+//if not then if the function was loaded successfully make a call to it
+//otherwise return a NULL cast to the return parameter.
+
+#define DECLARE_FUNCTION0(CallType, retVal, FuncName) \
+ typedef retVal (CallType* TYPE_##FuncName)(); \
+ TYPE_##FuncName m_##FuncName; \
+ short m_is##FuncName; \
+ retVal FuncName() \
+ { \
+ if (m_dllHandle) \
+ { \
+ if (FUNC_LOADED != m_is##FuncName) \
+ {\
+ m_##FuncName = NULL; \
+ m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \
+ m_is##FuncName = FUNC_LOADED;\
+ }\
+ if (NULL != m_##FuncName) \
+ return m_##FuncName(); \
+ else \
+ return (retVal)NULL; \
+ } \
+ else \
+ return (retVal)NULL; \
+ }
+
+#define DECLARE_FUNCTION1(CallType,retVal, FuncName, Param1) \
+ typedef retVal (CallType* TYPE_##FuncName)(Param1); \
+ TYPE_##FuncName m_##FuncName; \
+ short m_is##FuncName;\
+ retVal FuncName(Param1 p1) \
+ { \
+ if (m_dllHandle) \
+ { \
+ if (FUNC_LOADED != m_is##FuncName) \
+ {\
+ m_##FuncName = NULL; \
+ m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \
+ m_is##FuncName = FUNC_LOADED;\
+ }\
+ if (NULL != m_##FuncName) \
+ return m_##FuncName(p1); \
+ else \
+ return (retVal)NULL; \
+ } \
+ else \
+ return (retVal)NULL; \
+ }
+
+#define DECLARE_FUNCTION2(CallType,retVal, FuncName, Param1, Param2) \
+ typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2); \
+ TYPE_##FuncName m_##FuncName; \
+ short m_is##FuncName;\
+ retVal FuncName (Param1 p1, Param2 p2) \
+ {\
+ if (m_dllHandle)\
+ {\
+ if (FUNC_LOADED != m_is##FuncName) \
+ {\
+ m_##FuncName = NULL; \
+ m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \
+ m_is##FuncName = FUNC_LOADED;\
+ }\
+ if (NULL != m_##FuncName) \
+ return m_##FuncName(p1, p2); \
+ else \
+ return (retVal)NULL; \
+ } \
+ else\
+ return (retVal)NULL; \
+ }
+
+#define DECLARE_FUNCTION3(CallType,retVal, FuncName, Param1, Param2, Param3) \
+ typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2, Param3); \
+ TYPE_##FuncName m_##FuncName; \
+ short m_is##FuncName;\
+ retVal FuncName (Param1 p1, Param2 p2, Param3 p3) \
+ {\
+ if (m_dllHandle)\
+ {\
+ if (FUNC_LOADED != m_is##FuncName) \
+ {\
+ m_##FuncName = NULL; \
+ m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \
+ m_is##FuncName = FUNC_LOADED; \
+ }\
+ if (NULL != m_##FuncName) \
+ return m_##FuncName(p1, p2, p3);\
+ else \
+ return (retVal)NULL; \
+ } \
+ else\
+ return (retVal)NULL; \
+ }
+
+#define DECLARE_FUNCTION4(CallType,retVal, FuncName, Param1, Param2, Param3, Param4) \
+ typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2, Param3, Param4); \
+ TYPE_##FuncName m_##FuncName; \
+ short m_is##FuncName;\
+ retVal FuncName (Param1 p1, Param2 p2, Param3 p3, Param4 p4) \
+ {\
+ if (m_dllHandle)\
+ {\
+ if (FUNC_LOADED != m_is##FuncName) \
+ {\
+ m_##FuncName = NULL; \
+ m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \
+ m_is##FuncName = FUNC_LOADED;\
+ }\
+ if (NULL != m_##FuncName) \
+ return m_##FuncName(p1, p2, p3, p4);\
+ else \
+ return (retVal)NULL; \
+ } \
+ else\
+ return (retVal)NULL; \
+ }
+
+#define DECLARE_FUNCTION5(CallType,retVal, FuncName, Param1, Param2, Param3, Param4, Param5) \
+ typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2, Param3, Param4, Param5); \
+ TYPE_##FuncName m_##FuncName; \
+ short m_is##FuncName; \
+ retVal FuncName (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) \
+ {\
+ if (m_dllHandle)\
+ {\
+ if (FUNC_LOADED != m_is##FuncName) \
+ {\
+ m_##FuncName = NULL; \
+ m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \
+ m_is##FuncName = FUNC_LOADED;\
+ }\
+ if (NULL != m_##FuncName) \
+ return m_##FuncName(p1, p2, p3, p4, p5);\
+ else \
+ return (retVal)NULL; \
+ } \
+ else\
+ return (retVal)NULL; \
+ }
+
+#define DECLARE_FUNCTION6(CallType,retVal, FuncName, Param1, Param2, Param3, Param4, Param5, Param6) \
+ typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2, Param3, Param4, Param5, Param6); \
+ TYPE_##FuncName m_##FuncName; \
+ short m_is##FuncName;\
+ retVal FuncName (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) \
+ {\
+ if (m_dllHandle)\
+ {\
+ if (FUNC_LOADED != m_is##FuncName) \
+ {\
+ m_##FuncName = NULL; \
+ m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \
+ m_is##FuncName = FUNC_LOADED;\
+ }\
+ if (NULL != m_##FuncName) \
+ return m_##FuncName(p1, p2, p3, p4, p5, p6);\
+ else \
+ return (retVal)NULL; \
+ } \
+ else\
+ return (retVal)NULL; \
+ }
+
+#define DECLARE_FUNCTION7(CallType,retVal, FuncName, Param1, Param2, Param3, Param4, Param5, Param6, Param7) \
+ typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2, Param3, Param4, Param5, Param6, Param7); \
+ TYPE_##FuncName m_##FuncName; \
+ short m_is##FuncName;\
+ retVal FuncName (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) \
+ {\
+ if (m_dllHandle)\
+ {\
+ if (FUNC_LOADED != m_is##FuncName) \
+ {\
+ m_##FuncName = NULL; \
+ m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \
+ m_is##FuncName = FUNC_LOADED;\
+ }\
+ if (NULL != m_##FuncName) \
+ return m_##FuncName(p1, p2, p3, p4, p5, p6, p7);\
+ else \
+ return (retVal)NULL; \
+ } \
+ else\
+ return (retVal)NULL; \
+ }
+
+#define DECLARE_FUNCTION8(CallType,retVal, FuncName, Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8) \
+ typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8); \
+ TYPE_##FuncName m_##FuncName; \
+ short m_is##FuncName;\
+ retVal FuncName (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) \
+ {\
+ if (m_dllHandle)\
+ {\
+ if (FUNC_LOADED != m_is##FuncName) \
+ {\
+ m_##FuncName = NULL; \
+ m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \
+ m_is##FuncName = FUNC_LOADED;\
+ }\
+ if (NULL != m_##FuncName) \
+ return m_##FuncName(p1, p2, p3, p4, p5, p6, p7, p8);\
+ else \
+ return (retVal)NULL; \
+ }\
+ else\
+ return (retVal)NULL; \
+ }
+
+#define DECLARE_FUNCTION9(CallType,retVal, FuncName, Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8, Param9) \
+ typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8, Param9); \
+ TYPE_##FuncName m_##FuncName; \
+ short m_is##FuncName; \
+ retVal FuncName (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8, Param9 p9) \
+ {\
+ if (m_dllHandle)\
+ {\
+ if (FUNC_NAME != m_is##FuncName) \
+ {\
+ m_##FuncName = NULL; \
+ m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \
+ m_is##FuncName = FUNC_LOADED;\
+ }\
+ if (NULL != m_##FuncName) \
+ return m_##FuncName(p1, p2, p3, p4, p5, p6, p7, p8, p9);\
+ else \
+ return (retVal)NULL; \
+ }\
+ else\
+ return (retVal)NULL; \
+ }
+
+#define DECLARE_FUNCTION10(CallType,retVal, FuncName, Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8, Param9, Param10) \
+ typedef retVal (CallType* TYPE_##FuncName)FuncName(Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8, Param9, Param10); \
+ TYPE_##FuncName m_##FuncName; \
+ short m_is##FuncName;\
+ retVal FuncName (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8, Param9 p9, Param10 p10) \
+ {\
+ if (m_dllHandle)\
+ {\
+ if (FUNC_LOADED != m_is##FuncName) \
+ {\
+ m_##FuncName = NULL; \
+ m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \
+ m_is##FuncName = FUNC_LOADED;\
+ }\
+ if (NULL != m_##FuncName) \
+ return m_##FuncName(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10);\
+ else \
+ return (retVal)NULL; \
+ }\
+ else \
+ return (retVal)NULL;\
+ }
+
+//declare constructors and LoadFunctions
+#define DECLARE_CLASS(ClassName) \
+ public: \
+ ClassName (LPCTSTR name){LoadDll(name);} \
+ ClassName () {PDLL();}
+
+class PDLL
+{
+protected:
+ HINSTANCE m_dllHandle;
+private:
+ LPTSTR m_dllName;
+ int m_refCount;
+
+public:
+
+ PDLL()
+ {
+ m_dllHandle = NULL;
+ m_dllName = NULL;
+ m_refCount = 0;
+ }
+
+ //A NULL here means the name has already been set
+ void LoadDll(LPCTSTR name, bool showMsg = true)
+ {
+ if (name)
+ SetDllName(name);
+
+ //try to load
+ m_dllHandle = LoadLibrary(m_dllName);
+
+ if (m_dllHandle == NULL && showMsg)
+ {
+ std::ostringstream message;
+ message << "failed to load dll: " << ::GetLastError();
+ throw std::runtime_error(message.str().c_str());
+ }
+ }
+
+ bool SetDllName(LPCTSTR newName)
+ {
+ bool retVal = false;
+
+ //we allow name resets only if the current DLL handle is invalid
+ //once they've hooked into a DLL, the name cannot be changed
+ if (!m_dllHandle)
+ {
+ if (m_dllName)
+ {
+ delete []m_dllName;
+ m_dllName = NULL;
+ }
+
+ //They may be setting this null (e.g., uninitialize)
+ if (newName)
+ {
+ m_dllName = new TCHAR[_tcslen(newName) + 1];
+ _tcscpy(m_dllName, newName);
+ }
+ retVal = true;
+ }
+ return retVal;
+ }
+
+ virtual bool Initialize(short showMsg = 1)
+ {
+
+ bool retVal = false;
+
+ //Add one to our internal reference counter
+ m_refCount++;
+
+ if (m_refCount == 1 && m_dllName) //if this is first time, load the DLL
+ {
+ //we are assuming the name is already set
+ LoadDll(NULL, showMsg);
+ retVal = (m_dllHandle != NULL);
+ }
+ return retVal;
+ }
+
+ virtual void Uninitialize(void)
+ {
+ //If we're already completely unintialized, early exit
+ if (!m_refCount)
+ return;
+ //if this is the last time this instance has been unitialized,
+ //then do a full uninitialization
+ m_refCount--;
+
+ if (m_refCount < 1)
+ {
+ if (m_dllHandle)
+ {
+ FreeLibrary(m_dllHandle);
+ m_dllHandle = NULL;
+ }
+
+ SetDllName(NULL); //clear out the name & free memory
+ }
+ }
+
+ virtual ~PDLL()
+ {
+ //force this to be a true uninitialize
+ m_refCount = 1;
+ Uninitialize();
+
+ //free name
+ if (m_dllName)
+ {
+ delete [] m_dllName;
+ m_dllName = NULL;
+ }
+ }
+
+};
+#endif
diff --git a/src/pluginflagicondelegate.cpp b/src/pluginflagicondelegate.cpp new file mode 100644 index 00000000..6c0bb29e --- /dev/null +++ b/src/pluginflagicondelegate.cpp @@ -0,0 +1,24 @@ +#include "pluginflagicondelegate.h"
+#include "pluginlist.h"
+#include <QList>
+
+
+PluginFlagIconDelegate::PluginFlagIconDelegate(QObject *parent)
+ : IconDelegate(parent)
+{
+}
+
+QList<QIcon> PluginFlagIconDelegate::getIcons(const QModelIndex &index) const
+{
+ QList<QIcon> result;
+ foreach (const QVariant &var, index.data(Qt::UserRole + 1).toList()) {
+ result.append(var.value<QIcon>());
+ }
+ return result;
+}
+
+size_t PluginFlagIconDelegate::getNumIcons(const QModelIndex &index) const
+{
+ return index.data(Qt::UserRole + 1).toList().count();
+}
+
diff --git a/src/pluginflagicondelegate.h b/src/pluginflagicondelegate.h new file mode 100644 index 00000000..554e968b --- /dev/null +++ b/src/pluginflagicondelegate.h @@ -0,0 +1,17 @@ +#ifndef PLUGINFLAGICONDELEGATE_H
+#define PLUGINFLAGICONDELEGATE_H
+
+#include "icondelegate.h"
+
+class PluginFlagIconDelegate : public IconDelegate
+{
+public:
+ PluginFlagIconDelegate(QObject *parent = NULL);
+
+ // IconDelegate interface
+private:
+ virtual QList<QIcon> getIcons(const QModelIndex &index) const;
+ virtual size_t getNumIcons(const QModelIndex &index) const;
+};
+
+#endif // PLUGINFLAGICONDELEGATE_H
diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 85137390..c2e14182 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -20,8 +20,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "pluginlist.h" #include "report.h" #include "inject.h" -#include <utility.h> #include "settings.h" +#include "safewritefile.h" +#include "scopeguard.h" +#include <utility.h> #include <gameinfo.h> #include <espfile.h> #include <windows_error.h> @@ -39,7 +41,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QKeyEvent> #include <QSortFilterProxyModel> -#include <tchar.h> #include <ctime> #include <algorithm> #include <stdexcept> @@ -74,8 +75,10 @@ bool ByDate(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) { } PluginList::PluginList(QObject *parent) - : QAbstractTableModel(parent), - m_FontMetrics(QFont()), m_SaveTimer(this) + : QAbstractTableModel(parent) + , m_FontMetrics(QFont()) + , m_SaveTimer(this) + , m_BOSS(NULL) { m_SaveTimer.setSingleShot(true); connect(&m_SaveTimer, SIGNAL(timeout()), this, SIGNAL(saveTimer())); @@ -92,6 +95,12 @@ PluginList::PluginList(QObject *parent) PluginList::~PluginList() { + if (m_BOSS != NULL) { + m_BOSS->DestroyBossDb(m_BOSSDB); + m_BOSS->CleanUpAPI(); + delete m_BOSS; + m_BOSS = NULL; + } } @@ -101,6 +110,7 @@ QString PluginList::getColumnName(int column) case COL_NAME: return tr("Name"); case COL_PRIORITY: return tr("Priority"); case COL_MODINDEX: return tr("Mod Index"); + case COL_FLAGS: return tr("Flags"); default: return tr("unknown"); } } @@ -205,7 +215,7 @@ void PluginList::refresh(const QString &profileName, const DirectoryEntry &baseD emit layoutChanged(); refreshLoadOrder(); - emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), 1)); + emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), columnCount())); m_Refreshed(); } @@ -375,18 +385,16 @@ void PluginList::readLockedOrderFrom(const QString &fileName) } + void PluginList::writePlugins(const QString &fileName, bool writeUnchecked) const { - QFile file(fileName); - if (!file.open(QIODevice::WriteOnly)) { - throw MyException(tr("failed to open output file: %1").arg(fileName)); - } + SafeWriteFile file(fileName); QTextCodec *textCodec = writeUnchecked ? m_Utf8Codec : m_LocalCodec; - file.resize(0); + file->resize(0); - file.write(textCodec->fromUnicode("# This file was automatically generated by Mod Organizer.\r\n")); + file->write(textCodec->fromUnicode("# This file was automatically generated by Mod Organizer.\r\n")); bool invalidFileNames = false; int writtenCount = 0; @@ -398,36 +406,34 @@ void PluginList::writePlugins(const QString &fileName, bool writeUnchecked) cons invalidFileNames = true; qCritical("invalid plugin name %s", m_ESPs[priority].m_Name.toUtf8().constData()); } else { - file.write(textCodec->fromUnicode(m_ESPs[priority].m_Name)); + file->write(textCodec->fromUnicode(m_ESPs[priority].m_Name)); } - file.write("\r\n"); + file->write("\r\n"); ++writtenCount; } } - file.close(); if (invalidFileNames) { reportError(tr("Some of your plugins have invalid names! These plugins can not be loaded by the game. " "Please see mo_interface.log for a list of affected plugins and rename them.")); } + file.commit(); + qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData()); } void PluginList::writeLockedOrder(const QString &fileName) const { - QFile file(fileName); - if (!file.open(QIODevice::WriteOnly)) { - throw MyException(tr("failed to open output file: %1").arg(fileName)); - } + SafeWriteFile file(fileName); - file.resize(0); - file.write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); + file->resize(0); + file->write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); for (auto iter = m_LockedOrder.begin(); iter != m_LockedOrder.end(); ++iter) { - file.write(QString("%1|%2\r\n").arg(iter->first).arg(iter->second).toUtf8()); + file->write(QString("%1|%2\r\n").arg(iter->first).arg(iter->second).toUtf8()); } - file.close(); + file.commit(); } @@ -590,6 +596,175 @@ void PluginList::refreshLoadOrder() emit layoutChanged(); } + +class boss_exception : public std::runtime_error { +public: + boss_exception(const std::string &message) : std::runtime_error(message) {} +}; + +#define THROW_BOSS_ERROR(obj) \ + uint8_t *message; \ + obj->GetLastErrorDetails(&message); \ + throw boss_exception(std::string(reinterpret_cast<char*>(message))); + +#define U8(text) reinterpret_cast<const uint8_t*>(text) + + +void outputBossLog(const QString &filename) +{ + QFile file(filename); + if (file.open(QIODevice::ReadOnly)) { + while (!file.atEnd()) { + QByteArray line = file.readLine().trimmed(); + if (line.length() < 1) + continue; + + int endFirstWord = line.indexOf(':'); + if (endFirstWord < 0) { + endFirstWord = 0; + } + QString firstWord = line.mid(0, endFirstWord); + if (firstWord == "DEBUG") { + qDebug("(boss) %s", line.mid(endFirstWord + 2).constData()); + } else { + qWarning("(boss) %s", line.mid(endFirstWord + 2).constData()); + } + } + } + file.resize(0); +} + + +void PluginList::initBoss() +{ + m_BOSS = new BossDLL(TEXT("dlls\\boss.dll")); + + if (!m_BOSS->IsCompatibleVersion(2,1,1)) { + throw MyException(tr("BOSS dll incompatible")); + } + uint8_t *versionString; + if (m_BOSS->GetVersionString(&versionString) != BossDLL::RESULT_OK) { + THROW_BOSS_ERROR(m_BOSS) + } + qDebug("using boss version %s", versionString); + + m_TempFile.open(); m_TempFile.close(); // yeah, stupid, but open is required to generate the name + m_BOSS->SetLoggerOutput(m_TempFile.fileName().toLocal8Bit().constData(), 4); + + if (m_BOSS->CreateBossDb(&m_BOSSDB, BossDLL::SKYRIM, NULL) != BossDLL::RESULT_OK) { + uint8_t *message; + m_BOSS->GetLastErrorDetails(&message); + std::string messageCopy(reinterpret_cast<const char*>(message)); + delete m_BOSS; + m_BOSS = NULL; + throw boss_exception(messageCopy); + } + qApp->processEvents(); + + QString masterlistName = QDir::toNativeSeparators(qApp->applicationDirPath() + "/boss/masterlist.txt"); + + uint32_t res = m_BOSS->UpdateMasterlist(m_BOSSDB, U8(masterlistName.toUtf8().constData())); + qApp->processEvents(); + if (res == BossDLL::RESULT_OK) { + qDebug("boss masterlist updated"); + } else if (res == BossDLL::RESULT_NO_UPDATE_NECESSARY) { + qDebug("boss masterlist already up-to-date"); + } else { + THROW_BOSS_ERROR(m_BOSS) + } + if (m_BOSS->Load(m_BOSSDB, + U8(masterlistName.toUtf8().constData()), + U8(QDir::toNativeSeparators(qApp->applicationDirPath() + "/boss/userlist.txt").toUtf8().constData())) != BossDLL::RESULT_OK) { + THROW_BOSS_ERROR(m_BOSS) + } +} + +void PluginList::convertPluginListForBoss(boost::ptr_vector<uint8_t> &inputPlugins, std::vector<uint8_t*> &activePlugins) +{ + foreach (int idx, m_ESPsByPriority) { + QString fileName = m_ESPs[idx].m_Name; + QByteArray name = fileName.toUtf8(); + + uint8_t *nameU8 = new uint8_t[name.length() + 1]; + memcpy(nameU8, name.constData(), name.length() + 1); + if (m_ESPs[idx].m_Enabled) { + activePlugins.push_back(nameU8); + } + inputPlugins.push_back(nameU8); + } + if (m_BOSS->SetActivePluginsDumb(m_BOSSDB, &activePlugins[0], activePlugins.size()) != BossDLL::RESULT_OK) { + THROW_BOSS_ERROR(m_BOSS) + } +} + +void PluginList::applyBOSSSorting(uint8_t **pluginList, size_t size, int &priority, bool recognized, const char *extension) +{ + for (size_t i= 0; i < size; ++i) { + QString name = QString::fromUtf8(reinterpret_cast<const char*>(pluginList[i])).toLower(); + if (name.endsWith(extension)) { + auto iter = m_ESPsByName.find(name); + if (iter == m_ESPsByName.end()) { + throw MyException("boss returned invalid data"); + } + BossMessage *message; + size_t numMessages = 0; + m_BOSS->GetPluginMessages(m_BOSSDB, pluginList[i], &message, &numMessages); + m_ESPs[iter->second].m_BOSSMessages.clear(); + for (size_t im = 0; im < numMessages; ++im) { + m_ESPs[iter->second].m_BOSSMessages.append(QString::fromUtf8(reinterpret_cast<const char*>(message[im].message))); + } + m_ESPs[iter->second].m_Priority = priority++; + m_ESPs[iter->second].m_BOSSUnrecognized = !recognized; + } + } +} + +void PluginList::bossSort() +{ + if (m_BOSS == NULL) { + // first run, check boss compatibility and update + initBoss(); + } + + // create a boss-compatible representation of our current mod list. + boost::ptr_vector<uint8_t> inputPlugins; + std::vector<uint8_t*> activePlugins; + convertPluginListForBoss(inputPlugins, activePlugins); + + // sort mods in-memory + uint8_t **sortedPlugins; + uint8_t **unrecognizedPlugins; + size_t sizeSorted, sizeUnrecognized; + + if (m_BOSS->SortCustomMods(m_BOSSDB, + inputPlugins.c_array(), inputPlugins.size(), + &sortedPlugins, &sizeSorted, + &unrecognizedPlugins, &sizeUnrecognized) != BossDLL::RESULT_OK) { + THROW_BOSS_ERROR(m_BOSS) + } + + // output the log from boss to our own log to make it visible + outputBossLog(m_TempFile.fileName()); + + qDebug("%d sorted, %d unrecognized", sizeSorted, sizeUnrecognized); + + emit layoutAboutToBeChanged(); + + int priority = 0; + applyBOSSSorting(sortedPlugins, sizeSorted, priority, true, "esm"); + applyBOSSSorting(unrecognizedPlugins, sizeUnrecognized, priority, false, "esm"); + applyBOSSSorting(sortedPlugins, sizeSorted, priority, true, "esp"); + applyBOSSSorting(unrecognizedPlugins, sizeUnrecognized, priority, false, "esp"); + + // inform view of the changed data + updateIndices(); + emit layoutChanged(); + syncLoadOrder(); + + emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), columnCount())); + m_Refreshed(); +} + IPluginList::PluginState PluginList::state(const QString &name) const { auto iter = m_ESPsByName.find(name.toLower()); @@ -670,7 +845,7 @@ int PluginList::rowCount(const QModelIndex &parent) const int PluginList::columnCount(const QModelIndex &) const { - return 3; + return COL_LASTCOLUMN + 1; } @@ -724,18 +899,6 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const return QVariant(); } break; } - } else if ((role == Qt::DecorationRole) && (modelIndex.column() == 0)) { - if (m_ESPs[index].m_MasterUnset.size() > 0) { - return QIcon(":/MO/gui/warning"); - } else if (m_LockedOrder.find(m_ESPs[index].m_Name.toLower()) != m_LockedOrder.end()) { - return QIcon(":/MO/gui/locked"); - } else if (m_ESPs[index].m_HasIni) { - return QIcon(":/MO/gui/attachment"); - } else if (m_ESPs[index].m_IsDummy && m_ESPs[index].m_Enabled && !m_ESPs[index].m_HasIni) { - return QIcon(":/MO/gui/edit_clear"); - } else { - return QVariant(); - } } else if ((role == Qt::CheckStateRole) && (modelIndex.column() == 0)) { return m_ESPs[index].m_Enabled ? Qt::Checked : Qt::Unchecked; } else if (role == Qt::FontRole) { @@ -754,8 +917,15 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); } } else if (role == Qt::ToolTipRole) { + QString toolTip; + if (!m_ESPs[index].m_BOSSMessages.isEmpty()) { + toolTip += m_ESPs[index].m_BOSSMessages.join("<br>") + "<br><hr>"; + } + if (m_ESPs[index].m_BOSSUnrecognized) { + toolTip += "Not recognized by BOSS<br><ht>"; + } if (m_ESPs[index].m_ForceEnabled) { - return tr("This plugin can't be disabled (enforced by the game)"); + toolTip += tr("This plugin can't be disabled (enforced by the game)"); } else { QString text = tr("Origin: %1").arg(m_ESPs[index].m_OriginName); if (m_ESPs[index].m_MasterUnset.size() > 0) { @@ -773,8 +943,30 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const text += "<br>This file is a dummy! It exists only so the bsa with the same name gets loaded. With MO you don't need this: " "If you enable the archive with the same name in the \"Archive\" tab you can disable this plugin."; } - return text; + toolTip += text; + } + return toolTip; + } else if (role == Qt::UserRole + 1) { + QVariantList result; + if (m_ESPs[index].m_MasterUnset.size() > 0) { + result.append(QIcon(":/MO/gui/warning")); + } + if (m_LockedOrder.find(m_ESPs[index].m_Name.toLower()) != m_LockedOrder.end()) { + result.append(QIcon(":/MO/gui/locked")); } + if (!m_ESPs[index].m_BOSSMessages.isEmpty()) { + result.append(QIcon(":/MO/gui/information")); + } + if (m_ESPs[index].m_BOSSUnrecognized) { + result.append(QIcon(":/MO/gui/help")); + } + if (m_ESPs[index].m_HasIni) { + result.append(QIcon(":/MO/gui/attachment")); + } + if (m_ESPs[index].m_IsDummy && m_ESPs[index].m_Enabled && !m_ESPs[index].m_HasIni) { + result.append(QIcon(":/MO/gui/edit_clear")); + } + return result; } else { return QVariant(); } @@ -784,7 +976,6 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int role) { if (role == Qt::CheckStateRole) { -qDebug("uncheck plugin"); m_ESPs[modIndex.row()].m_Enabled = value.toInt() == Qt::Checked; emit dataChanged(modIndex, modIndex); @@ -1036,7 +1227,8 @@ PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled, FILETIME time, const QString &originName, const QString &fullPath, bool hasIni) : m_Name(name), m_Enabled(enabled), m_ForceEnabled(enabled), m_Removed(false), - m_Priority(0), m_LoadOrder(-1), m_Time(time), m_OriginName(originName), m_HasIni(hasIni) + m_Priority(0), m_LoadOrder(-1), m_Time(time), m_OriginName(originName), m_HasIni(hasIni), + m_BOSSUnrecognized(false) { try { ESP::File file(ToWString(fullPath)); diff --git a/src/pluginlist.h b/src/pluginlist.h index 64c914df..bb7428d0 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -25,8 +25,12 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QString> #include <QListWidget> #include <QTimer> +#include <QTemporaryFile> #include <boost/signals2.hpp> +#include <boost/ptr_container/ptr_vector.hpp> #include <vector> +#include <pdll.h> +#include <BOSS-API.h> /** @@ -40,6 +44,7 @@ public: enum EColumn { COL_NAME, + COL_FLAGS, COL_PRIORITY, COL_MODINDEX, @@ -143,6 +148,8 @@ public: void refreshLoadOrder(); + void bossSort(); + public: virtual PluginState state(const QString &name) const; virtual int priority(const QString &name) const; @@ -162,6 +169,7 @@ public: // implementation of the QAbstractTableModel interface virtual Qt::DropActions supportedDropActions() const { return Qt::MoveAction; } virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + void applyBOSSSorting(uint8_t **pluginList, size_t size, int &priority, bool recognized, const char *extension); public slots: /** @@ -194,7 +202,6 @@ private: ESPInfo(const QString &name, bool enabled, FILETIME time, const QString &originName, const QString &fullPath, bool hasIni); QString m_Name; - QString m_FullPath; bool m_Enabled; bool m_ForceEnabled; bool m_Removed; @@ -207,12 +214,91 @@ private: bool m_HasIni; std::set<QString> m_Masters; mutable std::set<QString> m_MasterUnset; + QStringList m_BOSSMessages; + bool m_BOSSUnrecognized; }; friend bool ByName(const ESPInfo& LHS, const ESPInfo& RHS); friend bool ByDate(const ESPInfo& LHS, const ESPInfo& RHS); friend bool ByPriority(const ESPInfo& LHS, const ESPInfo& RHS); + + class BossDLL : public PDLL { + DECLARE_CLASS(BossDLL) + + DECLARE_FUNCTION3(__cdecl, uint32_t, CreateBossDb, boss_db*, const uint32_t, const uint8_t*) + DECLARE_FUNCTION1(__cdecl, void, DestroyBossDb, boss_db) + DECLARE_FUNCTION0(__cdecl, void, CleanUpAPI) + + DECLARE_FUNCTION7(__cdecl, uint32_t, SortCustomMods, boss_db, uint8_t**, size_t, uint8_t***, size_t*, uint8_t***, size_t*) + DECLARE_FUNCTION3(__cdecl, uint32_t, SetActivePluginsDumb, boss_db, uint8_t**, const size_t) + + DECLARE_FUNCTION3(__cdecl, uint32_t, GetActivePluginsDumb , boss_db, uint8_t***, size_t*) + + DECLARE_FUNCTION2(__cdecl, uint32_t, UpdateMasterlist, boss_db, const uint8_t*) + DECLARE_FUNCTION3(__cdecl, uint32_t, Load, boss_db, const uint8_t*, const uint8_t*) + + DECLARE_FUNCTION2(__cdecl, void, SetLoggerOutput, const char*, uint8_t) + DECLARE_FUNCTION1(__cdecl, uint32_t, GetLastErrorDetails, uint8_t**) + + DECLARE_FUNCTION1(__cdecl, uint32_t, GetVersionString, uint8_t**) + DECLARE_FUNCTION3(__cdecl, bool, IsCompatibleVersion, const uint32_t, const uint32_t, const uint32_t) + + DECLARE_FUNCTION4(__cdecl, uint32_t, GetPluginMessages, boss_db, const uint8_t*, BossMessage**, size_t*) + + enum ResultCode { + RESULT_OK = 0, + RESULT_NO_MASTER_FILE = 1, + RESULT_FILE_READ_FAIL = 2, + RESULT_FILE_WRITE_FAIL = 3, + RESULT_FILE_NOT_UTF8 = 4, + RESULT_FILE_NOT_FOUND = 5, + RESULT_FILE_PARSE_FAIL = 6, + RESULT_CONDITION_EVAL_FAIL = 7, + RESULT_REGEX_EVAL_FAIL = 8, + RESULT_NO_GAME_DETECTED = 9, + RESULT_ENCODING_CONVERSION_FAIL = 10, + RESULT_FIND_ONLINE_MASTERLIST_REVISION_FAIL = 11, + RESULT_FIND_ONLINE_MASTERLIST_DATE_FAIL = 12, + RESULT_READ_UPDATE_FILE_LIST_FAIL = 13, + RESULT_FILE_CRC_MISMATCH = 14, + RESULT_FS_FILE_MOD_TIME_READ_FAIL = 15, + RESULT_FS_FILE_MOD_TIME_WRITE_FAIL = 16, + RESULT_FS_FILE_RENAME_FAIL = 17, + RESULT_FS_FILE_DELETE_FAIL = 18, + RESULT_FS_CREATE_DIRECTORY_FAIL = 19, + RESULT_FS_ITER_DIRECTORY_FAIL = 20, + RESULT_CURL_INIT_FAIL = 21, + RESULT_CURL_SET_ERRBUFF_FAIL = 22, + RESULT_CURL_SET_OPTION_FAIL = 23, + RESULT_CURL_SET_PROXY_FAIL = 24, + RESULT_CURL_SET_PROXY_TYPE_FAIL = 25, + RESULT_CURL_SET_PROXY_AUTH_FAIL = 26, + RESULT_CURL_SET_PROXY_AUTH_TYPE_FAIL = 27, + RESULT_CURL_PERFORM_FAIL = 28, + RESULT_CURL_USER_CANCEL = 29, + RESULT_GUI_WINDOW_INIT_FAIL = 30, + RESULT_NO_UPDATE_NECESSARY = 31, + RESULT_LO_MISMATCH = 32, + RESULT_NO_MEM = 33, + RESULT_INVALID_ARGS = 34, + RESULT_NETWORK_FAIL = 35, + RESULT_NO_INTERNET_CONNECTION = 36, + RESULT_NO_TAG_MAP = 37, + RESULT_PLUGINS_FULL = 38, + RESULT_PLUGIN_BEFORE_MASTER = 39, + RESULT_INVALID_SYNTAX = 40 + }; + + enum GameIDs { + AUTODETECT = 0, + OBLIVION = 1, + SKYRIM = 3, + FALLOUT3 = 4, + FALLOUTNV = 5 + }; + }; + private: void syncLoadOrder(); @@ -230,6 +316,9 @@ private: void testMasters(); + void initBoss(); + void convertPluginListForBoss(boost::ptr_vector<uint8_t> &inputPlugins, std::vector<uint8_t*> &activePlugins); + private: std::vector<ESPInfo> m_ESPs; @@ -253,6 +342,10 @@ private: SignalRefreshed m_Refreshed; + BossDLL *m_BOSS; + boss_db m_BOSSDB; + QTemporaryFile m_TempFile; + }; #endif // PLUGINLIST_H diff --git a/src/pluginlistsortproxy.cpp b/src/pluginlistsortproxy.cpp index 5bff24f5..8412fa27 100644 --- a/src/pluginlistsortproxy.cpp +++ b/src/pluginlistsortproxy.cpp @@ -125,11 +125,6 @@ bool PluginListSortProxy::lessThan(const QModelIndex &left, return plugins->getPriority(left.row()) < plugins->getPriority(right.row()); } break; default: { - static bool first = true; - if (first) { - qCritical("invalid sort column %d", left.column()); - first = false; - } return plugins->getPriority(left.row()) < plugins->getPriority(right.row()); } break; } diff --git a/src/profile.cpp b/src/profile.cpp index e075a4e6..c1f1025e 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "windows_error.h" #include "dummybsa.h" #include "modinfo.h" +#include "safewritefile.h" #include <utility.h> #include <util.h> #include <error_report.h> @@ -149,15 +150,10 @@ void Profile::writeModlistNow(bool onlyOnTimer) const #pragma message("right now, this is doing unnecessary saves. Need a flag that says that mod priority, enabled-state or name of a mod has changed") try { - QTemporaryFile file; - - if (!file.open()) { - reportError(tr("failed to open temporary file")); - return; - } + QString fileName = getModlistFileName(); + SafeWriteFile file(fileName); - file.resize(0); - file.write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); + file->write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); if (m_ModStatus.empty()) { return; } @@ -169,29 +165,17 @@ void Profile::writeModlistNow(bool onlyOnTimer) const ModInfo::Ptr modInfo = ModInfo::getByIndex(index); if (modInfo->getFixedPriority() == INT_MIN) { if (m_ModStatus[index].m_Enabled) { - file.write("+"); + file->write("+"); } else { - file.write("-"); + file->write("-"); } - file.write(modInfo->name().toUtf8()); - file.write("\r\n"); + file->write(modInfo->name().toUtf8()); + file->write("\r\n"); } } } - file.close(); - - - QString fileName = getModlistFileName(); - - if (QFile::exists(fileName)) { - shellDeleteQuiet(fileName); - } - - if (!file.copy(fileName)) { - reportError(tr("failed to open \"%1\" for writing").arg(fileName)); - return; - } + file.commit(); qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData()); } catch (const std::exception &e) { diff --git a/src/resources.qrc b/src/resources.qrc index 1582a3f2..73921f64 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -18,7 +18,7 @@ <file alias="profiles">resources/contact-new.png</file> <file alias="settings">resources/preferences-system.png</file> <file alias="executable">resources/application-x-executable.png</file> - <file>resources/dialog-information.png</file> + <file alias="information">resources/dialog-information.png</file> <file alias="locked">resources/emblem-readonly.png</file> <file alias="next">resources/go-next_16.png</file> <file alias="previous">resources/go-previous_16.png</file> diff --git a/src/safewritefile.cpp b/src/safewritefile.cpp new file mode 100644 index 00000000..6df8c2b8 --- /dev/null +++ b/src/safewritefile.cpp @@ -0,0 +1,48 @@ +/*
+Copyright (C) 2014 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 "safewritefile.h"
+#include <QStringList>
+
+
+using namespace MOBase;
+
+
+SafeWriteFile::SafeWriteFile(const QString &fileName)
+: m_FileName(fileName)
+{
+ if (!m_TempFile.open()) {
+ throw MyException(QObject::tr("failed to open temporary file"));
+ }
+}
+
+
+QFile *SafeWriteFile::operator->() {
+ Q_ASSERT(m_TempFile.isOpen());
+ return &m_TempFile;
+}
+
+
+void SafeWriteFile::commit() {
+ shellDeleteQuiet(m_FileName);
+ m_TempFile.rename(m_FileName);
+ m_TempFile.setAutoRemove(false);
+ m_TempFile.close();
+}
diff --git a/src/safewritefile.h b/src/safewritefile.h new file mode 100644 index 00000000..56bd7744 --- /dev/null +++ b/src/safewritefile.h @@ -0,0 +1,51 @@ +/*
+Copyright (C) 2014 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 SAFEWRITEFILE_H
+#define SAFEWRITEFILE_H
+
+
+#include <utility.h>
+#include <QTemporaryFile>
+#include <QString>
+
+/**
+ * @brief a wrapper for QFile that ensures the file is only actually (over-)written if writing was successful
+ */
+class SafeWriteFile {
+public:
+ SafeWriteFile(const QString &fileName);
+
+ QFile *operator->();
+
+ void commit();
+
+private:
+ QString m_FileName;
+ QTemporaryFile m_TempFile;
+};
+
+
+#endif // SAFEWRITEFILE_H
+
+
+
+
+
|
