diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 09beaf59de..ce0649cd64 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -1,17 +1,9 @@ set(LAUNCHER - datafilespage.cpp graphicspage.cpp main.cpp maindialog.cpp playpage.cpp - - model/datafilesmodel.cpp - model/modelitem.cpp - model/esm/esmfile.cpp - - utils/filedialog.cpp - utils/naturalsort.cpp - utils/lineedit.cpp + datafilespage.cpp utils/profilescombobox.cpp utils/textinputdialog.cpp @@ -19,36 +11,20 @@ set(LAUNCHER ) set(LAUNCHER_HEADER - datafilespage.hpp graphicspage.hpp maindialog.hpp playpage.hpp - - model/datafilesmodel.hpp - model/modelitem.hpp - model/esm/esmfile.hpp - - utils/lineedit.hpp - utils/filedialog.hpp - utils/naturalsort.hpp + datafilespage.hpp utils/profilescombobox.hpp utils/textinputdialog.hpp - ) # Headers that must be pre-processed set(LAUNCHER_HEADER_MOC - datafilespage.hpp graphicspage.hpp maindialog.hpp playpage.hpp - - model/datafilesmodel.hpp - model/modelitem.hpp - model/esm/esmfile.hpp - - utils/lineedit.hpp - utils/filedialog.hpp + datafilespage.hpp utils/profilescombobox.hpp utils/textinputdialog.hpp ) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 6b0539c1dd..52b8c9cbeb 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -2,14 +2,17 @@ #include #include +#include +#include +#include +#include -#include "model/datafilesmodel.hpp" -#include "model/esm/esmfile.hpp" +////#include "model/datafilesmodel.hpp" +////#include "model/esm/esmfile.hpp" #include "utils/profilescombobox.hpp" -#include "utils/filedialog.hpp" -#include "utils/lineedit.hpp" -#include "utils/naturalsort.hpp" +////#include "utils/filedialog.hpp" +////#include "utils/naturalsort.hpp" #include "utils/textinputdialog.hpp" #include "datafilespage.hpp" @@ -34,108 +37,11 @@ namespace boost using namespace ESM; using namespace std; -//sort QModelIndexList ascending -bool rowGreaterThan(const QModelIndex &index1, const QModelIndex &index2) -{ - return index1.row() >= index2.row(); -} - -//sort QModelIndexList descending -bool rowSmallerThan(const QModelIndex &index1, const QModelIndex &index2) -{ - return index1.row() <= index2.row(); -} - DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, QWidget *parent) : QWidget(parent) , mCfgMgr(cfg) { - // Models - mMastersModel = new DataFilesModel(this); - mPluginsModel = new DataFilesModel(this); - - mPluginsProxyModel = new QSortFilterProxyModel(); - mPluginsProxyModel->setDynamicSortFilter(true); - mPluginsProxyModel->setSourceModel(mPluginsModel); - - // Filter toolbar - QLabel *filterLabel = new QLabel(tr("&Filter:"), this); - LineEdit *filterLineEdit = new LineEdit(this); - filterLabel->setBuddy(filterLineEdit); - - QToolBar *filterToolBar = new QToolBar(this); - filterToolBar->setMovable(false); - - // Create a container widget and a layout to get the spacer to work - QWidget *filterWidget = new QWidget(this); - QHBoxLayout *filterLayout = new QHBoxLayout(filterWidget); - QSpacerItem *hSpacer1 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - - filterLayout->addItem(hSpacer1); - filterLayout->addWidget(filterLabel); - filterLayout->addWidget(filterLineEdit); - - filterToolBar->addWidget(filterWidget); - - QCheckBox checkBox; - unsigned int height = checkBox.sizeHint().height() + 4; - - mMastersTable = new QTableView(this); - mMastersTable->setModel(mMastersModel); - mMastersTable->setObjectName("MastersTable"); - mMastersTable->setSelectionBehavior(QAbstractItemView::SelectRows); - mMastersTable->setSelectionMode(QAbstractItemView::SingleSelection); - mMastersTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - mMastersTable->setAlternatingRowColors(true); - mMastersTable->horizontalHeader()->setStretchLastSection(true); - mMastersTable->horizontalHeader()->hide(); - - // Set the row height to the size of the checkboxes - mMastersTable->verticalHeader()->setDefaultSectionSize(height); - mMastersTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); - mMastersTable->verticalHeader()->hide(); - mMastersTable->setColumnHidden(1, true); - mMastersTable->setColumnHidden(2, true); - mMastersTable->setColumnHidden(3, true); - mMastersTable->setColumnHidden(4, true); - mMastersTable->setColumnHidden(5, true); - mMastersTable->setColumnHidden(6, true); - mMastersTable->setColumnHidden(7, true); - mMastersTable->setColumnHidden(8, true); - - mPluginsTable = new QTableView(this); - mPluginsTable->setModel(mPluginsProxyModel); - mPluginsTable->setObjectName("PluginsTable"); - mPluginsTable->setContextMenuPolicy(Qt::CustomContextMenu); - mPluginsTable->setSelectionBehavior(QAbstractItemView::SelectRows); - mPluginsTable->setSelectionMode(QAbstractItemView::SingleSelection); - mPluginsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - mPluginsTable->setAlternatingRowColors(true); - mPluginsTable->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); - mPluginsTable->horizontalHeader()->setStretchLastSection(true); - mPluginsTable->horizontalHeader()->hide(); - - mPluginsTable->verticalHeader()->setDefaultSectionSize(height); - mPluginsTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); - mPluginsTable->setColumnHidden(1, true); - mPluginsTable->setColumnHidden(2, true); - mPluginsTable->setColumnHidden(3, true); - mPluginsTable->setColumnHidden(4, true); - mPluginsTable->setColumnHidden(5, true); - mPluginsTable->setColumnHidden(6, true); - mPluginsTable->setColumnHidden(7, true); - mPluginsTable->setColumnHidden(8, true); - - // Add both tables to a splitter - QSplitter *splitter = new QSplitter(this); - splitter->setOrientation(Qt::Horizontal); - splitter->addWidget(mMastersTable); - splitter->addWidget(mPluginsTable); - - // Adjust the default widget widths inside the splitter - QList sizeList; - sizeList << 175 << 200; - splitter->setSizes(sizeList); + mDataFilesList = new DataFilesList(mCfgMgr, this); // Bottom part with profile options QLabel *profileLabel = new QLabel(tr("Current Profile: "), this); @@ -155,24 +61,14 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, QWidget *parent) QVBoxLayout *pageLayout = new QVBoxLayout(this); - pageLayout->addWidget(filterToolBar); - pageLayout->addWidget(splitter); + pageLayout->addWidget(mDataFilesList); pageLayout->addWidget(mProfileToolBar); // Create a dialog for the new profile name input mNewProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this); connect(mNewProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateOkButton(QString))); - - connect(mPluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); - connect(mMastersTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); - - connect(mMastersModel, SIGNAL(checkedItemsChanged(QStringList,QStringList)), mPluginsModel, SLOT(slotcheckedItemsChanged(QStringList,QStringList))); - - connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString))); - - connect(mPluginsTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); - + connect(mProfilesComboBox, SIGNAL(profileRenamed(QString,QString)), this, SLOT(profileRenamed(QString,QString))); connect(mProfilesComboBox, SIGNAL(profileChanged(QString,QString)), this, SLOT(profileChanged(QString,QString))); @@ -202,20 +98,6 @@ void DataFilesPage::createActions() mProfileToolBar->addSeparator(); mProfileToolBar->addAction(mNewProfileAction); mProfileToolBar->addAction(mDeleteProfileAction); - - // Context menu actions - mCheckAction = new QAction(tr("Check selected"), this); - connect(mCheckAction, SIGNAL(triggered()), this, SLOT(check())); - - mUncheckAction = new QAction(tr("Uncheck selected"), this); - connect(mUncheckAction, SIGNAL(triggered()), this, SLOT(uncheck())); - - // Context menu for the plugins table - mContextMenu = new QMenu(this); - - mContextMenu->addAction(mCheckAction); - mContextMenu->addAction(mUncheckAction); - } void DataFilesPage::setupConfig() @@ -267,12 +149,8 @@ void DataFilesPage::setupConfig() void DataFilesPage::readConfig() { - // Don't read the config if no masters are found - if (mMastersModel->rowCount() < 1) - return; - QString profile = mProfilesComboBox->currentText(); - + // Make sure we have no groups open while (!mLauncherConfig->group().isEmpty()) { mLauncherConfig->endGroup(); @@ -290,54 +168,11 @@ void DataFilesPage::readConfig() foreach (const QString &key, childKeys) { const QString keyValue = mLauncherConfig->value(key).toString(); - - if (key.startsWith("Plugin")) { - //QStringList checked = mPluginsModel->checkedItems(); - EsmFile *file = mPluginsModel->findItem(keyValue); - QModelIndex index = mPluginsModel->indexFromItem(file); - - mPluginsModel->setCheckState(index, Qt::Checked); - // Move the row to the top of te view - //mPluginsModel->moveRow(index.row(), checked.count()); - plugins << keyValue; - } - - if (key.startsWith("Master")) { - EsmFile *file = mMastersModel->findItem(keyValue); - mMastersModel->setCheckState(mMastersModel->indexFromItem(file), Qt::Checked); - } + + mDataFilesList->setCheckState(keyValue, Qt::Checked); } qDebug() << plugins; - - -// // Set the checked item positions -// const QStringList checked = mPluginsModel->checkedItems(); -// for (int i = 0; i < plugins.size(); ++i) { -// EsmFile *file = mPluginsModel->findItem(plugins.at(i)); -// QModelIndex index = mPluginsModel->indexFromItem(file); -// mPluginsModel->moveRow(index.row(), i); -// qDebug() << "Moving: " << plugins.at(i) << " from: " << index.row() << " to: " << i << " count: " << checked.count(); - -// } - - // Iterate over the plugins to set their checkstate and position -// for (int i = 0; i < plugins.size(); ++i) { -// const QString plugin = plugins.at(i); - -// const QList pluginList = mPluginsModel->findItems(plugin); - -// if (!pluginList.isEmpty()) -// { -// foreach (const QStandardItem *currentPlugin, pluginList) { -// mPluginsModel->setData(currentPlugin->index(), Qt::Checked, Qt::CheckStateRole); - -// // Move the plugin to the position specified in the config file -// mPluginsModel->insertRow(i, mPluginsModel->takeRow(currentPlugin->row())); -// } -// } -// } - } bool DataFilesPage::showDataFilesWarning() @@ -386,77 +221,51 @@ bool DataFilesPage::setupDataFiles() // We use the Configuration Manager to retrieve the configuration values boost::program_options::variables_map variables; boost::program_options::options_description desc; - + desc.add_options() - ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()) - ("data-local", boost::program_options::value()->default_value("")) - ("fs-strict", boost::program_options::value()->implicit_value(true)->default_value(false)) - ("encoding", boost::program_options::value()->default_value("win1252")); - + ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()) + ("data-local", boost::program_options::value()->default_value("")) + ("fs-strict", boost::program_options::value()->implicit_value(true)->default_value(false)) + ("encoding", boost::program_options::value()->default_value("win1252")); + boost::program_options::notify(variables); - + mCfgMgr.readConfiguration(variables, desc); - + if (variables["data"].empty()) { if (!showDataFilesWarning()) - return false; + return false; } else { mDataDirs = Files::PathContainer(variables["data"].as()); } - - std::string local = variables["data-local"].as(); - if (!local.empty()) { - mDataLocal.push_back(Files::PathContainer::value_type(local)); - } - + + std::string local = variables["data-local"].as(); + if (!local.empty()) { + mDataLocal.push_back(Files::PathContainer::value_type(local)); + } + mCfgMgr.processPaths(mDataDirs); mCfgMgr.processPaths(mDataLocal); - + // Second chance to display the warning, the data= entries are invalid while (mDataDirs.empty()) { if (!showDataFilesWarning()) return false; } - + // Set the charset for reading the esm/esp files QString encoding = QString::fromStdString(variables["encoding"].as()); - if (!encoding.isEmpty() && encoding != QLatin1String("win1252")) { - mMastersModel->setEncoding(encoding); - mPluginsModel->setEncoding(encoding); - } - - // Add the paths to the respective models - for (Files::PathContainer::iterator it = mDataDirs.begin(); it != mDataDirs.end(); ++it) { - QString path = QString::fromStdString(it->string()); - path.remove(QChar('\"')); - mMastersModel->addMasters(path); - mPluginsModel->addPlugins(path); - } - - // Same for the data-local paths - for (Files::PathContainer::iterator it = mDataLocal.begin(); it != mDataLocal.end(); ++it) { - QString path = QString::fromStdString(it->string()); - path.remove(QChar('\"')); - mMastersModel->addMasters(path); - mPluginsModel->addPlugins(path); - } - - mMastersModel->sort(0); - mPluginsModel->sort(0); -// mMastersTable->sortByColumn(3, Qt::AscendingOrder); -// mPluginsTable->sortByColumn(3, Qt::AscendingOrder); - - + + Files::PathContainer paths; + paths.insert(paths.end(), mDataDirs.begin(), mDataDirs.end()); + paths.insert(paths.end(), mDataLocal.begin(), mDataLocal.end()); + mDataFilesList->setupDataFiles(paths, encoding); readConfig(); return true; } void DataFilesPage::writeConfig(QString profile) { - // Don't overwrite the config if no masters are found - if (mMastersModel->rowCount() < 1) - return; - QString pathStr = QString::fromStdString(mCfgMgr.getUserPath().string()); QDir userPath(pathStr); @@ -579,24 +388,19 @@ void DataFilesPage::writeConfig(QString profile) mLauncherConfig->remove(""); // Clear the subgroup // Now write the masters to the configs - const QStringList masters = mMastersModel->checkedItems(); - - // We don't use foreach because we need i - for (int i = 0; i < masters.size(); ++i) { - const QString currentMaster = masters.at(i); - - mLauncherConfig->setValue(QString("Master%0").arg(i), currentMaster); - gameConfig << "master=" << currentMaster << endl; - - } - - // And finally write all checked plugins - const QStringList plugins = mPluginsModel->checkedItems(); - - for (int i = 0; i < plugins.size(); ++i) { - const QString currentPlugin = plugins.at(i); - mLauncherConfig->setValue(QString("Plugin%1").arg(i), currentPlugin); - gameConfig << "plugin=" << currentPlugin << endl; + const QStringList checkedFiles = mDataFilesList->checkedFiles(); + for(int i=0; i < checkedFiles.size(); i++) + { + if (checkedFiles.at(i).lastIndexOf("esm") != -1) + { + mLauncherConfig->setValue(QString("Master%0").arg(i), checkedFiles.at(i)); + gameConfig << "master=" << checkedFiles.at(i) << endl; + } + else + { + mLauncherConfig->setValue(QString("Plugin%1").arg(i), checkedFiles.at(i)); + gameConfig << "plugin=" << checkedFiles.at(i) << endl; + } } file.close(); @@ -670,93 +474,6 @@ void DataFilesPage::deleteProfile() } } -void DataFilesPage::check() -{ - // Check the current selection - if (!mPluginsTable->selectionModel()->hasSelection()) { - return; - } - - QModelIndexList indexes = mPluginsTable->selectionModel()->selectedIndexes(); - - //sort selection ascending because selectedIndexes returns an unsorted list - //qSort(indexes.begin(), indexes.end(), rowSmallerThan); - - foreach (const QModelIndex &index, indexes) { - if (!index.isValid()) - return; - - mPluginsModel->setCheckState(index, Qt::Checked); - } -} - -void DataFilesPage::uncheck() -{ - // uncheck the current selection - if (!mPluginsTable->selectionModel()->hasSelection()) { - return; - } - - QModelIndexList indexes = mPluginsTable->selectionModel()->selectedIndexes(); - - //sort selection ascending because selectedIndexes returns an unsorted list - //qSort(indexes.begin(), indexes.end(), rowSmallerThan); - - foreach (const QModelIndex &index, indexes) { - if (!index.isValid()) - return; - - mPluginsModel->setCheckState(index, Qt::Unchecked); - } -} - -void DataFilesPage::refresh() -{ - mPluginsModel->sort(0); - - - // Refresh the plugins table - mPluginsTable->scrollToTop(); - writeConfig(); - readConfig(); -} - - -void DataFilesPage::setCheckState(QModelIndex index) -{ - if (!index.isValid()) - return; - - QObject *object = QObject::sender(); - - // Not a signal-slot call - if (!object) - return; - - if (object->objectName() == QLatin1String("PluginsTable")) { - QModelIndex sourceIndex = mPluginsProxyModel->mapToSource(index); - - (mPluginsModel->checkState(sourceIndex) == Qt::Checked) - ? mPluginsModel->setCheckState(sourceIndex, Qt::Unchecked) - : mPluginsModel->setCheckState(sourceIndex, Qt::Checked); - } - - if (object->objectName() == QLatin1String("MastersTable")) { - (mMastersModel->checkState(index) == Qt::Checked) - ? mMastersModel->setCheckState(index, Qt::Unchecked) - : mMastersModel->setCheckState(index, Qt::Checked); - } - - return; - -} - -void DataFilesPage::filterChanged(const QString filter) -{ - QRegExp regExp(filter, Qt::CaseInsensitive, QRegExp::FixedString); - mPluginsProxyModel->setFilterRegExp(regExp); -} - void DataFilesPage::profileChanged(const QString &previous, const QString ¤t) { qDebug() << "Profile is changed from: " << previous << " to " << current; @@ -780,8 +497,7 @@ void DataFilesPage::profileChanged(const QString &previous, const QString &curre return; } - mMastersModel->uncheckAll(); - mPluginsModel->uncheckAll(); + mDataFilesList->uncheckAll(); readConfig(); } @@ -810,35 +526,8 @@ void DataFilesPage::profileRenamed(const QString &previous, const QString &curre // Remove the profile from the combobox mProfilesComboBox->removeItem(mProfilesComboBox->findText(previous)); - mMastersModel->uncheckAll(); - mPluginsModel->uncheckAll(); + mDataFilesList->uncheckAll(); + ////mMastersModel->uncheckAll(); + ////mPluginsModel->uncheckAll(); readConfig(); } - -void DataFilesPage::showContextMenu(const QPoint &point) -{ - // Make sure there are plugins in the view - if (!mPluginsTable->selectionModel()->hasSelection()) { - return; - } - - QPoint globalPos = mPluginsTable->mapToGlobal(point); - - QModelIndexList indexes = mPluginsTable->selectionModel()->selectedIndexes(); - - // Show the check/uncheck actions depending on the state of the selected items - mUncheckAction->setEnabled(false); - mCheckAction->setEnabled(false); - - foreach (const QModelIndex &index, indexes) { - if (!index.isValid()) - return; - - (mPluginsModel->checkState(index) == Qt::Checked) - ? mUncheckAction->setEnabled(true) - : mCheckAction->setEnabled(true); - } - - // Show menu - mContextMenu->exec(globalPos); -} diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 13668ec30e..0584de4364 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -17,6 +17,7 @@ class ProfilesComboBox; class DataFilesModel; class TextInputDialog; +class DataFilesList; namespace Files { struct ConfigurationManager; } @@ -34,10 +35,6 @@ public: bool setupDataFiles(); public slots: - void setCheckState(QModelIndex index); - - void filterChanged(const QString filter); - void showContextMenu(const QPoint &point); void profileChanged(const QString &previous, const QString ¤t); void profileRenamed(const QString &previous, const QString ¤t); void updateOkButton(const QString &text); @@ -49,21 +46,11 @@ public slots: // void moveDown(); // void moveTop(); // void moveBottom(); - void check(); - void uncheck(); - void refresh(); private: - DataFilesModel *mMastersModel; - DataFilesModel *mPluginsModel; - - QSortFilterProxyModel *mPluginsProxyModel; - - QTableView *mMastersTable; - QTableView *mPluginsTable; + DataFilesList *mDataFilesList; QToolBar *mProfileToolBar; - QMenu *mContextMenu; QAction *mNewProfileAction; QAction *mDeleteProfileAction; @@ -72,8 +59,6 @@ private: // QAction *mMoveDownAction; // QAction *mMoveTopAction; // QAction *mMoveBottomAction; - QAction *mCheckAction; - QAction *mUncheckAction; Files::ConfigurationManager &mCfgMgr; Files::PathContainer mDataDirs; diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 2c4f3430c5..dee84498c2 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -8,8 +8,7 @@ #include #include #include - -#include "utils/naturalsort.hpp" +#include #include "graphicspage.hpp" diff --git a/apps/launcher/utils/textinputdialog.cpp b/apps/launcher/utils/textinputdialog.cpp index 16cadb6619..6791c49f51 100644 --- a/apps/launcher/utils/textinputdialog.cpp +++ b/apps/launcher/utils/textinputdialog.cpp @@ -5,7 +5,7 @@ #include #include -#include "lineedit.hpp" +#include #include "textinputdialog.hpp" diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index d1cfbea524..8cee3f0f4e 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -21,6 +21,7 @@ opencs_units (model/world idtable idtableproxymodel ) + opencs_units_noqt (model/world universalid data record idcollection commands columnbase ) @@ -40,9 +41,10 @@ opencs_units_noqt (model/tools opencs_units (view/doc - viewmanager view operations operation subview + viewmanager view operations operation subview startup opendialog ) + opencs_units_noqt (view/doc subviewfactory ) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 1632ed220d..e2df365a29 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -11,39 +11,52 @@ CS::Editor::Editor() : mViewManager (mDocumentManager), mNewDocumentIndex (0) { connect (&mViewManager, SIGNAL (newDocumentRequest ()), this, SLOT (createDocument ())); + connect (&mViewManager, SIGNAL (loadDocumentRequest ()), this, SLOT (loadDocument ())); + + connect (&mStartup, SIGNAL (createDocument()), this, SLOT (createDocument ())); + connect (&mStartup, SIGNAL (loadDocument()), this, SLOT (loadDocument ())); + + connect (&mOpenDialog, SIGNAL(accepted()), this, SLOT(openFiles())); } void CS::Editor::createDocument() { + mStartup.hide(); + + /// \todo open the ESX picker instead + std::ostringstream stream; stream << "NewDocument" << (++mNewDocumentIndex); - CSMDoc::Document *document = mDocumentManager.addDocument (stream.str()); + std::vector files; + files.push_back (stream.str()); - static const char *sGlobals[] = - { - "Day", "DaysPassed", "GameHour", "Month", "PCRace", "PCVampire", "PCWerewolf", "PCYear", 0 - }; + CSMDoc::Document *document = mDocumentManager.addDocument (files, true); - for (int i=0; sGlobals[i]; ++i) - { - ESM::Global record; - record.mId = sGlobals[i]; - record.mValue = i==0 ? 1 : 0; - record.mType = ESM::VT_Float; - document->getData().getGlobals().add (record); - } + mViewManager.addView (document); +} - document->getData().merge(); /// \todo remove once proper ESX loading is implemented +void CS::Editor::loadDocument() +{ + mStartup.hide(); + mOpenDialog.show(); + mOpenDialog.raise(); + mOpenDialog.activateWindow(); +} + +void CS::Editor::openFiles() +{ + std::vector paths; + mOpenDialog.getFileList(paths); + CSMDoc::Document *document = mDocumentManager.addDocument(paths, false); mViewManager.addView (document); } int CS::Editor::run() { - /// \todo Instead of creating an empty document, open a small welcome dialogue window with buttons for new/load/recent projects - createDocument(); + mStartup.show(); return QApplication::exec(); } \ No newline at end of file diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index 60f7beaf1d..024475bf01 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -4,7 +4,10 @@ #include #include "model/doc/documentmanager.hpp" + #include "view/doc/viewmanager.hpp" +#include "view/doc/startup.hpp" +#include "view/doc/opendialog.hpp" namespace CS { @@ -16,6 +19,8 @@ namespace CS CSMDoc::DocumentManager mDocumentManager; CSVDoc::ViewManager mViewManager; + CSVDoc::StartupDialogue mStartup; + OpenDialog mOpenDialog; // not implemented Editor (const Editor&); @@ -28,9 +33,12 @@ namespace CS int run(); ///< \return error status - public slots: + private slots: void createDocument(); + + void loadDocument(); + void openFiles(); }; } diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 9d26944832..796135c3ff 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -1,10 +1,66 @@ #include "document.hpp" -CSMDoc::Document::Document (const std::string& name) +#include + +void CSMDoc::Document::load (const std::vector::const_iterator& begin, + const std::vector::const_iterator& end, bool lastAsModified) +{ + assert (begin!=end); + + std::vector::const_iterator end2 (end); + + if (lastAsModified) + --end2; + + for (std::vector::const_iterator iter (begin); iter!=end2; ++iter) + getData().loadFile (*iter, true); + + if (lastAsModified) + getData().loadFile (*end2, false); +} + +void CSMDoc::Document::createBase() +{ + static const char *sGlobals[] = + { + "Day", "DaysPassed", "GameHour", "Month", "PCRace", "PCVampire", "PCWerewolf", "PCYear", 0 + }; + + for (int i=0; sGlobals[i]; ++i) + { + ESM::Global record; + record.mId = sGlobals[i]; + record.mValue = i==0 ? 1 : 0; + record.mType = ESM::VT_Float; + getData().getGlobals().add (record); + } +} + +CSMDoc::Document::Document (const std::vector& files, bool new_) : mTools (mData) { - mName = name; ///< \todo replace with ESX list + if (files.empty()) + throw std::runtime_error ("Empty content file sequence"); + + /// \todo adjust last file name: + /// \li make sure it is located in the data-local directory (adjust path if necessary) + /// \li make sure the extension matches the new scheme (change it if necesarry) + + mName = files.back().filename().string(); + + if (files.size()>1 || !new_) + { + std::vector::const_iterator end = files.end(); + + if (new_) + --end; + + load (files.begin(), end, !new_); + } + + if (new_ && files.size()==1) + createBase(); connect (&mUndoStack, SIGNAL (cleanChanged (bool)), this, SLOT (modificationStateChanged (bool))); @@ -86,10 +142,10 @@ void CSMDoc::Document::saving() if (mSaveCount>15) { - mSaveCount = 0; - mSaveTimer.stop(); - mUndoStack.setClean(); - emit stateChanged (getState(), this); + mSaveCount = 0; + mSaveTimer.stop(); + mUndoStack.setClean(); + emit stateChanged (getState(), this); } } diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index 43e8bba37b..0162681bcb 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -3,6 +3,8 @@ #include +#include + #include #include #include @@ -38,10 +40,15 @@ namespace CSMDoc Document (const Document&); Document& operator= (const Document&); + void load (const std::vector::const_iterator& begin, + const std::vector::const_iterator& end, bool lastAsModified); + ///< \param lastAsModified Store the last file in Modified instead of merging it into Base. + + void createBase(); + public: - Document (const std::string& name); - ///< \todo replace name with ESX list + Document (const std::vector& files, bool new_); QUndoStack& getUndoStack(); diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index 8ae2764f24..740c0b5827 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -14,9 +14,10 @@ CSMDoc::DocumentManager::~DocumentManager() delete *iter; } -CSMDoc::Document *CSMDoc::DocumentManager::addDocument (const std::string& name) +CSMDoc::Document *CSMDoc::DocumentManager::addDocument (const std::vector& files, + bool new_) { - Document *document = new Document (name); + Document *document = new Document (files, new_); mDocuments.push_back (document); diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp index 730c7fae1d..a307b76a57 100644 --- a/apps/opencs/model/doc/documentmanager.hpp +++ b/apps/opencs/model/doc/documentmanager.hpp @@ -4,6 +4,8 @@ #include #include +#include + namespace CSMDoc { class Document; @@ -21,8 +23,11 @@ namespace CSMDoc ~DocumentManager(); - Document *addDocument (const std::string& name); + Document *addDocument (const std::vector& files, bool new_); ///< The ownership of the returned document is not transferred to the caller. + /// + /// \param new_ Do not load the last content file in \a files and instead create in an + /// appropriate way. bool removeDocument (Document *document); ///< \return last document removed? diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index 38b73ee3f5..f1871a6a94 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -28,7 +28,8 @@ namespace CSMWorld { Display_String, Display_Integer, - Display_Float + Display_Float, + Display_Var }; std::string mTitle; diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 1e2de92658..cfbd804a5a 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -17,9 +17,9 @@ namespace CSMWorld virtual void set (Record& record, const QVariant& data) { - ESXRecordT base = record.getBase(); - base.mValue = data.toFloat(); - record.setModified (base); + ESXRecordT record2 = record.get(); + record2.mValue = data.toFloat(); + record.setModified (record2); } virtual bool isEditable() const @@ -91,6 +91,68 @@ namespace CSMWorld return false; } }; + + template + struct VarTypeColumn : public Column + { + VarTypeColumn() : Column ("Type", ColumnBase::Display_Integer) {} + + virtual QVariant get (const Record& record) const + { + return static_cast (record.get().mType); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + record2.mType = static_cast (data.toInt()); + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct VarValueColumn : public Column + { + VarValueColumn() : Column ("Value", ColumnBase::Display_Var) {} + + virtual QVariant get (const Record& record) const + { + switch (record.get().mType) + { + case ESM::VT_String: return record.get().mStr.c_str(); break; + case ESM::VT_Int: return record.get().mI; break; + case ESM::VT_Float: return record.get().mF; break; + + default: return QVariant(); + } + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + switch (record2.mType) + { + case ESM::VT_String: record2.mStr = data.toString().toUtf8().constData(); break; + case ESM::VT_Int: record2.mI = data.toInt(); break; + case ESM::VT_Float: record2.mF = data.toFloat(); break; + + default: break; + } + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; } #endif \ No newline at end of file diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index a3522503e4..f120c75f10 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -5,6 +5,7 @@ #include +#include #include #include "idtable.hpp" @@ -27,7 +28,14 @@ CSMWorld::Data::Data() mGlobals.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Global)); mGlobals.addColumn (new FloatValueColumn); + mGmsts.addColumn (new StringIdColumn); + mGmsts.addColumn (new RecordStateColumn); + mGmsts.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Gmst)); + mGmsts.addColumn (new VarTypeColumn); + mGmsts.addColumn (new VarValueColumn); + addModel (new IdTable (&mGlobals), UniversalId::Type_Globals, UniversalId::Type_Global); + addModel (new IdTable (&mGmsts), UniversalId::Type_Gmsts, UniversalId::Type_Gmst); } CSMWorld::Data::~Data() @@ -59,4 +67,31 @@ QAbstractTableModel *CSMWorld::Data::getTableModel (const UniversalId& id) void CSMWorld::Data::merge() { mGlobals.merge(); +} + +void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base) +{ + ESM::ESMReader reader; + /// \todo set encoder + reader.open (path.string()); + + // Note: We do not need to send update signals here, because at this point the model is not connected + // to any view. + while (reader.hasMoreRecs()) + { + ESM::NAME n = reader.getRecName(); + reader.getRecHeader(); + + switch (n.val) + { + case ESM::REC_GLOB: mGlobals.load (reader, base); break; + case ESM::REC_GMST: mGmsts.load (reader, base); break; + + + default: + + /// \todo throw an exception instead, once all records are implemented + reader.skipRecord(); + } + } } \ No newline at end of file diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index f7748cb5da..519817a3b4 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -4,7 +4,10 @@ #include #include +#include + #include +#include #include "idcollection.hpp" #include "universalid.hpp" @@ -16,6 +19,7 @@ namespace CSMWorld class Data { IdCollection mGlobals; + IdCollection mGmsts; std::vector mModels; std::map mModelIndex; @@ -44,6 +48,9 @@ namespace CSMWorld void merge(); ///< Merge modified into base. + + void loadFile (const boost::filesystem::path& path, bool base); + ///< Merging content of a file into base or modified. }; } diff --git a/apps/opencs/model/world/idcollection.cpp b/apps/opencs/model/world/idcollection.cpp index fc4bb1ef65..5ea9532790 100644 --- a/apps/opencs/model/world/idcollection.cpp +++ b/apps/opencs/model/world/idcollection.cpp @@ -3,4 +3,4 @@ CSMWorld::IdCollectionBase::IdCollectionBase() {} -CSMWorld::IdCollectionBase::~IdCollectionBase() {} \ No newline at end of file +CSMWorld::IdCollectionBase::~IdCollectionBase() {} diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index 963997924a..5a1d21ae4c 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -11,9 +11,12 @@ #include -#include "columnbase.hpp" +#include + #include +#include "columnbase.hpp" + namespace CSMWorld { class IdCollectionBase @@ -67,9 +70,11 @@ namespace CSMWorld virtual std::string getId (const RecordBase& record) const = 0; ///< Return ID for \a record. /// - /// \attention Throw san exception, if the type of \a record does not match. + /// \attention Throws an exception, if the type of \a record does not match. virtual const RecordBase& getRecord (const std::string& id) const = 0; + + virtual void load (ESM::ESMReader& reader, bool base) = 0; }; ///< \brief Collection of ID-based records @@ -136,6 +141,8 @@ namespace CSMWorld virtual const RecordBase& getRecord (const std::string& id) const; + virtual void load (ESM::ESMReader& reader, bool base); + void addColumn (Column *column); }; @@ -309,6 +316,62 @@ namespace CSMWorld return (record2.isModified() ? record2.mModified : record2.mBase).mId; } + template + void IdCollection::load (ESM::ESMReader& reader, bool base) + { + std::string id = reader.getHNOString ("NAME"); + + int index = searchId (id); + + if (reader.isNextSub ("DELE")) + { + reader.skipRecord(); + + if (index==-1) + { + // deleting a record that does not exist + + // ignore it for now + + /// \todo report the problem to the user + } + else if (base) + { + removeRows (index, 1); + } + else + { + mRecords[index].mState = RecordBase::State_Deleted; + } + } + else + { + ESXRecordT record; + record.mId = id; + record.load (reader); + + if (index==-1) + { + // new record + Record record2; + record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; + (base ? record2.mBase : record2.mModified) = record; + + appendRecord (record2); + } + else + { + // old record + Record& record2 = mRecords[index]; + + if (base) + record2.mBase = record; + else + record2.setModified (record); + } + } + } + template const RecordBase& IdCollection::getRecord (const std::string& id) const { diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index d8775643ac..c006852bc6 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -18,6 +18,7 @@ namespace { { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, "empty" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Globals, "Global Variables" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Gmsts, "Game Settings" }, { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0 } // end marker }; @@ -25,6 +26,7 @@ namespace static const TypeData sIdArg[] = { { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Global, "Global Variable" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Gmst, "Game Setting" }, { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0 } // end marker }; diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp index 4a73feb12c..9ff7d17b18 100644 --- a/apps/opencs/model/world/universalid.hpp +++ b/apps/opencs/model/world/universalid.hpp @@ -33,12 +33,12 @@ namespace CSMWorld enum Type { Type_None, - Type_Globals, - Type_Global, + Type_VerificationResults, + Type_Gmsts, + Type_Gmst - Type_VerificationResults }; private: diff --git a/apps/opencs/view/doc/opendialog.cpp b/apps/opencs/view/doc/opendialog.cpp new file mode 100644 index 0000000000..9a5feb23a7 --- /dev/null +++ b/apps/opencs/view/doc/opendialog.cpp @@ -0,0 +1,62 @@ +#include +#include + +#include + +#include "opendialog.hpp" + +OpenDialog::OpenDialog(QWidget * parent) : QDialog(parent) +{ + QVBoxLayout *layout = new QVBoxLayout(this); + mFileSelector = new DataFilesList(mCfgMgr, this); + layout->addWidget(mFileSelector); + + //FIXME - same as DataFilesPage::setupDataFiles + // We use the Configuration Manager to retrieve the configuration values + boost::program_options::variables_map variables; + boost::program_options::options_description desc; + + desc.add_options() + ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()) + ("data-local", boost::program_options::value()->default_value("")) + ("fs-strict", boost::program_options::value()->implicit_value(true)->default_value(false)) + ("encoding", boost::program_options::value()->default_value("win1252")); + + boost::program_options::notify(variables); + + mCfgMgr.readConfiguration(variables, desc); + + Files::PathContainer mDataDirs, mDataLocal; + if (!variables["data"].empty()) { + mDataDirs = Files::PathContainer(variables["data"].as()); + } + + std::string local = variables["data-local"].as(); + if (!local.empty()) { + mDataLocal.push_back(Files::PathContainer::value_type(local)); + } + + mCfgMgr.processPaths(mDataDirs); + mCfgMgr.processPaths(mDataLocal); + + // Set the charset for reading the esm/esp files + QString encoding = QString::fromStdString(variables["encoding"].as()); + + Files::PathContainer dataDirs; + dataDirs.insert(dataDirs.end(), mDataDirs.begin(), mDataDirs.end()); + dataDirs.insert(dataDirs.end(), mDataLocal.begin(), mDataLocal.end()); + mFileSelector->setupDataFiles(dataDirs, encoding); + + buttonBox = new QDialogButtonBox(QDialogButtonBox::Open | QDialogButtonBox::Cancel, Qt::Horizontal, this); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + layout->addWidget(buttonBox); + + setLayout(layout); + setWindowTitle(tr("Open")); +} + +void OpenDialog::getFileList(std::vector& paths) +{ + mFileSelector->selectedFiles(paths); +} diff --git a/apps/opencs/view/doc/opendialog.hpp b/apps/opencs/view/doc/opendialog.hpp new file mode 100644 index 0000000000..6355aea44f --- /dev/null +++ b/apps/opencs/view/doc/opendialog.hpp @@ -0,0 +1,17 @@ +#include +#include + +class DataFilesList; +class QDialogButtonBox; + +class OpenDialog : public QDialog { + Q_OBJECT +public: + OpenDialog(QWidget * parent = 0); + + void getFileList(std::vector& paths); +private: + DataFilesList * mFileSelector; + QDialogButtonBox * buttonBox; + Files::ConfigurationManager mCfgMgr; +}; \ No newline at end of file diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp new file mode 100644 index 0000000000..7861a1c2ef --- /dev/null +++ b/apps/opencs/view/doc/startup.cpp @@ -0,0 +1,20 @@ + +#include "startup.hpp" + +#include +#include + +CSVDoc::StartupDialogue::StartupDialogue() +{ + QHBoxLayout *layout = new QHBoxLayout (this); + + QPushButton *createDocument = new QPushButton ("new", this); + connect (createDocument, SIGNAL (clicked()), this, SIGNAL (createDocument())); + layout->addWidget (createDocument); + + QPushButton *loadDocument = new QPushButton ("load", this); + connect (loadDocument, SIGNAL (clicked()), this, SIGNAL (loadDocument())); + layout->addWidget (loadDocument); + + setLayout (layout); +} \ No newline at end of file diff --git a/apps/opencs/view/doc/startup.hpp b/apps/opencs/view/doc/startup.hpp new file mode 100644 index 0000000000..f24d2a64ba --- /dev/null +++ b/apps/opencs/view/doc/startup.hpp @@ -0,0 +1,24 @@ +#ifndef CSV_DOC_STARTUP_H +#define CSV_DOC_STARTUP_H + +#include + +namespace CSVDoc +{ + class StartupDialogue : public QWidget + { + Q_OBJECT + + public: + + StartupDialogue(); + + signals: + + void createDocument(); + + void loadDocument(); + }; +} + +#endif diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 13edb6e749..4fd03041f8 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -32,6 +32,10 @@ void CSVDoc::View::setupFileMenu() connect (new_, SIGNAL (triggered()), this, SIGNAL (newDocumentRequest())); file->addAction (new_); + QAction *open = new QAction (tr ("&Open"), this); + connect (open, SIGNAL (triggered()), this, SIGNAL (loadDocumentRequest())); + file->addAction (open); + mSave = new QAction (tr ("&Save"), this); connect (mSave, SIGNAL (triggered()), this, SLOT (save())); file->addAction (mSave); @@ -67,6 +71,10 @@ void CSVDoc::View::setupWorldMenu() connect (globals, SIGNAL (triggered()), this, SLOT (addGlobalsSubView())); world->addAction (globals); + QAction *gmsts = new QAction (tr ("Game settings"), this); + connect (gmsts, SIGNAL (triggered()), this, SLOT (addGmstsSubView())); + world->addAction (gmsts); + mVerify = new QAction (tr ("&Verify"), this); connect (mVerify, SIGNAL (triggered()), this, SLOT (verify())); world->addAction (mVerify); @@ -213,4 +221,9 @@ void CSVDoc::View::verify() void CSVDoc::View::addGlobalsSubView() { addSubView (CSMWorld::UniversalId::Type_Globals); +} + +void CSVDoc::View::addGmstsSubView() +{ + addSubView (CSMWorld::UniversalId::Type_Gmsts); } \ No newline at end of file diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index b1dedafe92..6bdd54e6bf 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -84,6 +84,8 @@ namespace CSVDoc void newDocumentRequest(); + void loadDocumentRequest(); + public slots: void addSubView (const CSMWorld::UniversalId& id); @@ -97,6 +99,8 @@ namespace CSVDoc void verify(); void addGlobalsSubView(); + + void addGmstsSubView(); }; } diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 22847c78b7..b01b9ce342 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -57,6 +57,7 @@ CSVDoc::View *CSVDoc::ViewManager::addView (CSMDoc::Document *document) view->show(); connect (view, SIGNAL (newDocumentRequest ()), this, SIGNAL (newDocumentRequest())); + connect (view, SIGNAL (loadDocumentRequest ()), this, SIGNAL (loadDocumentRequest())); updateIndices(); diff --git a/apps/opencs/view/doc/viewmanager.hpp b/apps/opencs/view/doc/viewmanager.hpp index 5e4b1be075..91a80d4962 100644 --- a/apps/opencs/view/doc/viewmanager.hpp +++ b/apps/opencs/view/doc/viewmanager.hpp @@ -46,6 +46,8 @@ namespace CSVDoc void newDocumentRequest(); + void loadDocumentRequest(); + private slots: void documentStateChanged (int state, CSMDoc::Document *document); diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 2bf6577b11..e16de99eff 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -64,6 +64,8 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSM /// \todo configure widget properly (range, format?) layout->addWidget (widget = new QDoubleSpinBox, i, 1); break; + + default: break; // silence warnings for other times for now } } else @@ -76,6 +78,8 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSM layout->addWidget (widget = new QLabel, i, 1); break; + + default: break; // silence warnings for other times for now } } diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 080a175ea5..351007ded5 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -11,6 +11,9 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_Globals, new CSVDoc::SubViewFactoryWithCreateFlag (true)); + manager.add (CSMWorld::UniversalId::Type_Gmsts, + new CSVDoc::SubViewFactoryWithCreateFlag (false)); + manager.add (CSMWorld::UniversalId::Type_Global, new CSVDoc::SubViewFactoryWithCreateFlag (true)); } \ No newline at end of file diff --git a/cmake/OpenMWMacros.cmake b/cmake/OpenMWMacros.cmake index bb200ee570..f2382fd8ac 100644 --- a/cmake/OpenMWMacros.cmake +++ b/cmake/OpenMWMacros.cmake @@ -23,6 +23,22 @@ endforeach (u) source_group ("components\\${dir}" FILES ${files}) endmacro (add_component_dir) +macro (add_component_qt_dir dir) +set (files) +foreach (u ${ARGN}) +file (GLOB ALL ${CMAKE_CURRENT_SOURCE_DIR} "${dir}/${u}.[ch]pp") +foreach (f ${ALL}) +list (APPEND files "${f}") +list (APPEND COMPONENT_FILES "${f}") +endforeach (f) +file (GLOB MOC_H ${CMAKE_CURRENT_SOURCE_DIR} "${dir}/${u}.hpp") +foreach (fi ${MOC_H}) +list (APPEND COMPONENT_MOC_FILES "${fi}") +endforeach (fi) +endforeach (u) +source_group ("components\\${dir}" FILES ${files}) +endmacro (add_component_qt_dir) + macro (copy_all_files source_dir destination_dir files) foreach (f ${files}) get_filename_component(filename ${f} NAME) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 3da09ecb8f..00342e2ac8 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -66,9 +66,21 @@ add_component_dir (translation translation ) +find_package(Qt4 COMPONENTS QtCore QtGui) + +if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) + add_component_qt_dir (fileorderlist + datafileslist model/modelitem model/datafilesmodel model/esm/esmfile + utils/filedialog utils/lineedit utils/naturalsort + ) + + include(${QT_USE_FILE}) + QT4_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) +endif(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) + include_directories(${BULLET_INCLUDE_DIRS}) -add_library(components STATIC ${COMPONENT_FILES}) +add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS}) target_link_libraries(components ${Boost_LIBRARIES} ${OGRE_LIBRARIES}) diff --git a/components/esm/loadgmst.cpp b/components/esm/loadgmst.cpp index a73095a66a..e9852ec076 100644 --- a/components/esm/loadgmst.cpp +++ b/components/esm/loadgmst.cpp @@ -76,8 +76,29 @@ std::string GameSetting::getString() const { if (mType==VT_String) return mStr; - + throw std::runtime_error ("GMST " + mId + " is not a string"); } + void GameSetting::blank() + { + mStr.clear(); + mI = 0; + mF = 0; + mType = VT_Float; + } + + bool operator== (const GameSetting& left, const GameSetting& right) + { + if (left.mType!=right.mType) + return false; + + switch (left.mType) + { + case VT_Float: return left.mF==right.mF; + case VT_Int: return left.mI==right.mI; + case VT_String: return left.mStr==right.mStr; + default: return false; + } + } } diff --git a/components/esm/loadgmst.hpp b/components/esm/loadgmst.hpp index ab9a9551e5..f7aec5c76c 100644 --- a/components/esm/loadgmst.hpp +++ b/components/esm/loadgmst.hpp @@ -26,17 +26,22 @@ struct GameSetting VarType mType; void load(ESMReader &esm); - + int getInt() const; ///< Throws an exception if GMST is not of type int or float. - + float getFloat() const; ///< Throws an exception if GMST is not of type int or float. - + std::string getString() const; ///< Throwns an exception if GMST is not of type string. void save(ESMWriter &esm); + + void blank(); + ///< Set record to default state (does not touch the ID). }; + + bool operator== (const GameSetting& left, const GameSetting& right); } #endif diff --git a/components/fileorderlist/datafileslist.cpp b/components/fileorderlist/datafileslist.cpp new file mode 100644 index 0000000000..d25060baaf --- /dev/null +++ b/components/fileorderlist/datafileslist.cpp @@ -0,0 +1,351 @@ +#include + +#include +#include + +#include "model/datafilesmodel.hpp" +#include "model/esm/esmfile.hpp" + +#include "utils/filedialog.hpp" +#include "utils/lineedit.hpp" +#include "utils/naturalsort.hpp" + +#include "datafileslist.hpp" + +#include +/** + * Workaround for problems with whitespaces in paths in older versions of Boost library + */ +#if (BOOST_VERSION <= 104600) +namespace boost +{ + + template<> + inline boost::filesystem::path lexical_cast(const std::string& arg) + { + return boost::filesystem::path(arg); + } + +} /* namespace boost */ +#endif /* (BOOST_VERSION <= 104600) */ + +using namespace ESM; +using namespace std; + +//sort QModelIndexList ascending +bool rowGreaterThan(const QModelIndex &index1, const QModelIndex &index2) +{ + return index1.row() >= index2.row(); +} + +//sort QModelIndexList descending +bool rowSmallerThan(const QModelIndex &index1, const QModelIndex &index2) +{ + return index1.row() <= index2.row(); +} + +DataFilesList::DataFilesList(Files::ConfigurationManager &cfg, QWidget *parent) + : QWidget(parent) + , mCfgMgr(cfg) +{ + // Models + mMastersModel = new DataFilesModel(this); + mPluginsModel = new DataFilesModel(this); + + mPluginsProxyModel = new QSortFilterProxyModel(); + mPluginsProxyModel->setDynamicSortFilter(true); + mPluginsProxyModel->setSourceModel(mPluginsModel); + + // Filter toolbar + QLabel *filterLabel = new QLabel(tr("&Filter:"), this); + LineEdit *filterLineEdit = new LineEdit(this); + filterLabel->setBuddy(filterLineEdit); + + QToolBar *filterToolBar = new QToolBar(this); + filterToolBar->setMovable(false); + + // Create a container widget and a layout to get the spacer to work + QWidget *filterWidget = new QWidget(this); + QHBoxLayout *filterLayout = new QHBoxLayout(filterWidget); + QSpacerItem *hSpacer1 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + + filterLayout->addItem(hSpacer1); + filterLayout->addWidget(filterLabel); + filterLayout->addWidget(filterLineEdit); + + filterToolBar->addWidget(filterWidget); + + QCheckBox checkBox; + unsigned int height = checkBox.sizeHint().height() + 4; + + mMastersTable = new QTableView(this); + mMastersTable->setModel(mMastersModel); + mMastersTable->setObjectName("MastersTable"); + mMastersTable->setSelectionBehavior(QAbstractItemView::SelectRows); + mMastersTable->setSelectionMode(QAbstractItemView::SingleSelection); + mMastersTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + mMastersTable->setAlternatingRowColors(true); + mMastersTable->horizontalHeader()->setStretchLastSection(true); + mMastersTable->horizontalHeader()->hide(); + + // Set the row height to the size of the checkboxes + mMastersTable->verticalHeader()->setDefaultSectionSize(height); + mMastersTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); + mMastersTable->verticalHeader()->hide(); + mMastersTable->setColumnHidden(1, true); + mMastersTable->setColumnHidden(2, true); + mMastersTable->setColumnHidden(3, true); + mMastersTable->setColumnHidden(4, true); + mMastersTable->setColumnHidden(5, true); + mMastersTable->setColumnHidden(6, true); + mMastersTable->setColumnHidden(7, true); + mMastersTable->setColumnHidden(8, true); + + mPluginsTable = new QTableView(this); + mPluginsTable->setModel(mPluginsProxyModel); + mPluginsTable->setObjectName("PluginsTable"); + mPluginsTable->setContextMenuPolicy(Qt::CustomContextMenu); + mPluginsTable->setSelectionBehavior(QAbstractItemView::SelectRows); + mPluginsTable->setSelectionMode(QAbstractItemView::SingleSelection); + mPluginsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + mPluginsTable->setAlternatingRowColors(true); + mPluginsTable->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); + mPluginsTable->horizontalHeader()->setStretchLastSection(true); + mPluginsTable->horizontalHeader()->hide(); + + mPluginsTable->verticalHeader()->setDefaultSectionSize(height); + mPluginsTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); + mPluginsTable->setColumnHidden(1, true); + mPluginsTable->setColumnHidden(2, true); + mPluginsTable->setColumnHidden(3, true); + mPluginsTable->setColumnHidden(4, true); + mPluginsTable->setColumnHidden(5, true); + mPluginsTable->setColumnHidden(6, true); + mPluginsTable->setColumnHidden(7, true); + mPluginsTable->setColumnHidden(8, true); + + // Add both tables to a splitter + QSplitter *splitter = new QSplitter(this); + splitter->setOrientation(Qt::Horizontal); + splitter->addWidget(mMastersTable); + splitter->addWidget(mPluginsTable); + + // Adjust the default widget widths inside the splitter + QList sizeList; + sizeList << 175 << 200; + splitter->setSizes(sizeList); + + QVBoxLayout *pageLayout = new QVBoxLayout(this); + + pageLayout->addWidget(filterToolBar); + pageLayout->addWidget(splitter); + + connect(mPluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); + connect(mMastersTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); + + connect(mMastersModel, SIGNAL(checkedItemsChanged(QStringList,QStringList)), mPluginsModel, SLOT(slotcheckedItemsChanged(QStringList,QStringList))); + + connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString))); + + connect(mPluginsTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); + + createActions(); +} + +void DataFilesList::createActions() +{ + // Refresh the plugins + QAction *refreshAction = new QAction(tr("Refresh"), this); + refreshAction->setShortcut(QKeySequence(tr("F5"))); + connect(refreshAction, SIGNAL(triggered()), this, SLOT(refresh())); + + // Context menu actions + mCheckAction = new QAction(tr("Check selected"), this); + connect(mCheckAction, SIGNAL(triggered()), this, SLOT(check())); + + mUncheckAction = new QAction(tr("Uncheck selected"), this); + connect(mUncheckAction, SIGNAL(triggered()), this, SLOT(uncheck())); + + // Context menu for the plugins table + mContextMenu = new QMenu(this); + + mContextMenu->addAction(mCheckAction); + mContextMenu->addAction(mUncheckAction); + +} + +bool DataFilesList::setupDataFiles(Files::PathContainer dataDirs, const QString encoding) +{ + // Set the charset for reading the esm/esp files + if (!encoding.isEmpty() && encoding != QLatin1String("win1252")) { + mMastersModel->setEncoding(encoding); + mPluginsModel->setEncoding(encoding); + } + + // Add the paths to the respective models + for (Files::PathContainer::iterator it = dataDirs.begin(); it != dataDirs.end(); ++it) { + QString path = QString::fromStdString(it->string()); + path.remove(QChar('\"')); + mMastersModel->addMasters(path); + mPluginsModel->addPlugins(path); + } + + mMastersModel->sort(0); + mPluginsModel->sort(0); +// mMastersTable->sortByColumn(3, Qt::AscendingOrder); +// mPluginsTable->sortByColumn(3, Qt::AscendingOrder); + + return true; +} + +void DataFilesList::selectedFiles(std::vector& paths) +{ + QStringList masterPaths = mMastersModel->checkedItemsPaths(); + foreach (const QString &path, masterPaths) + { + paths.push_back(path.toStdString()); + } + QStringList pluginPaths = mPluginsModel->checkedItemsPaths(); + foreach (const QString &path, pluginPaths) + { + paths.push_back(path.toStdString()); + } +} + +void DataFilesList::check() +{ + // Check the current selection + if (!mPluginsTable->selectionModel()->hasSelection()) { + return; + } + + QModelIndexList indexes = mPluginsTable->selectionModel()->selectedIndexes(); + + //sort selection ascending because selectedIndexes returns an unsorted list + //qSort(indexes.begin(), indexes.end(), rowSmallerThan); + + foreach (const QModelIndex &index, indexes) { + if (!index.isValid()) + return; + + mPluginsModel->setCheckState(index, Qt::Checked); + } +} + +void DataFilesList::uncheck() +{ + // uncheck the current selection + if (!mPluginsTable->selectionModel()->hasSelection()) { + return; + } + + QModelIndexList indexes = mPluginsTable->selectionModel()->selectedIndexes(); + + //sort selection ascending because selectedIndexes returns an unsorted list + //qSort(indexes.begin(), indexes.end(), rowSmallerThan); + + foreach (const QModelIndex &index, indexes) { + if (!index.isValid()) + return; + + mPluginsModel->setCheckState(index, Qt::Unchecked); + } +} + +void DataFilesList::refresh() +{ + mPluginsModel->sort(0); + + + // Refresh the plugins table + mPluginsTable->scrollToTop(); +} + + +void DataFilesList::setCheckState(QModelIndex index) +{ + if (!index.isValid()) + return; + + QObject *object = QObject::sender(); + + // Not a signal-slot call + if (!object) + return; + + if (object->objectName() == QLatin1String("PluginsTable")) { + QModelIndex sourceIndex = mPluginsProxyModel->mapToSource(index); + + (mPluginsModel->checkState(sourceIndex) == Qt::Checked) + ? mPluginsModel->setCheckState(sourceIndex, Qt::Unchecked) + : mPluginsModel->setCheckState(sourceIndex, Qt::Checked); + } + + if (object->objectName() == QLatin1String("MastersTable")) { + (mMastersModel->checkState(index) == Qt::Checked) + ? mMastersModel->setCheckState(index, Qt::Unchecked) + : mMastersModel->setCheckState(index, Qt::Checked); + } + + return; + +} + +void DataFilesList::uncheckAll() +{ + mMastersModel->uncheckAll(); + mPluginsModel->uncheckAll(); +} + +void DataFilesList::filterChanged(const QString filter) +{ + QRegExp regExp(filter, Qt::CaseInsensitive, QRegExp::FixedString); + mPluginsProxyModel->setFilterRegExp(regExp); +} + +void DataFilesList::showContextMenu(const QPoint &point) +{ + // Make sure there are plugins in the view + if (!mPluginsTable->selectionModel()->hasSelection()) { + return; + } + + QPoint globalPos = mPluginsTable->mapToGlobal(point); + + QModelIndexList indexes = mPluginsTable->selectionModel()->selectedIndexes(); + + // Show the check/uncheck actions depending on the state of the selected items + mUncheckAction->setEnabled(false); + mCheckAction->setEnabled(false); + + foreach (const QModelIndex &index, indexes) { + if (!index.isValid()) + return; + + (mPluginsModel->checkState(index) == Qt::Checked) + ? mUncheckAction->setEnabled(true) + : mCheckAction->setEnabled(true); + } + + // Show menu + mContextMenu->exec(globalPos); +} + +void DataFilesList::setCheckState(const QString& element, Qt::CheckState state) +{ + EsmFile *file = mPluginsModel->findItem(element); + if (file) + { + mPluginsModel->setCheckState(mPluginsModel->indexFromItem(file), Qt::Checked); + } + else + { + file = mMastersModel->findItem(element); + mMastersModel->setCheckState(mMastersModel->indexFromItem(file), Qt::Checked); + } +} + +QStringList DataFilesList::checkedFiles() +{ + return mMastersModel->checkedItems() + mPluginsModel->checkedItems(); +} \ No newline at end of file diff --git a/components/fileorderlist/datafileslist.hpp b/components/fileorderlist/datafileslist.hpp new file mode 100644 index 0000000000..4b158d316d --- /dev/null +++ b/components/fileorderlist/datafileslist.hpp @@ -0,0 +1,77 @@ +#ifndef DATAFILESLIST_H +#define DATAFILESLIST_H + +#include +#include +#include + + +class QTableView; +class QSortFilterProxyModel; +class QSettings; +class QAction; +class QToolBar; +class QMenu; +class ProfilesComboBox; +class DataFilesModel; + +class TextInputDialog; + +namespace Files { struct ConfigurationManager; } + +class DataFilesList : public QWidget +{ + Q_OBJECT + +public: + DataFilesList(Files::ConfigurationManager& cfg, QWidget *parent = 0); + + bool setupDataFiles(Files::PathContainer dataDirs, const QString encoding); + void selectedFiles(std::vector& paths); + void uncheckAll(); + QStringList checkedFiles(); + void setCheckState(const QString& element, Qt::CheckState); + + +public slots: + void setCheckState(QModelIndex index); + + void filterChanged(const QString filter); + void showContextMenu(const QPoint &point); + + // Action slots +// void moveUp(); +// void moveDown(); +// void moveTop(); +// void moveBottom(); + void check(); + void uncheck(); + void refresh(); + +private: + DataFilesModel *mMastersModel; + DataFilesModel *mPluginsModel; + + QSortFilterProxyModel *mPluginsProxyModel; + + QTableView *mMastersTable; + QTableView *mPluginsTable; + + QMenu *mContextMenu; + +// QAction *mMoveUpAction; +// QAction *mMoveDownAction; +// QAction *mMoveTopAction; +// QAction *mMoveBottomAction; + QAction *mCheckAction; + QAction *mUncheckAction; + + Files::ConfigurationManager &mCfgMgr; + +// const QStringList checkedPlugins(); +// const QStringList selectedMasters(); + + void createActions(); +}; + +#endif diff --git a/apps/launcher/model/datafilesmodel.cpp b/components/fileorderlist/model/datafilesmodel.cpp similarity index 95% rename from apps/launcher/model/datafilesmodel.cpp rename to components/fileorderlist/model/datafilesmodel.cpp index e84dbe0acc..5bb1996791 100644 --- a/apps/launcher/model/datafilesmodel.cpp +++ b/components/fileorderlist/model/datafilesmodel.cpp @@ -292,6 +292,7 @@ void DataFilesModel::addMasters(const QString &path) EsmFile *file = new EsmFile(master); file->setDates(info.lastModified(), info.lastRead()); + file->setPath(info.absoluteFilePath()); // Add the master to the table if (findItem(master) == 0) @@ -427,6 +428,25 @@ QStringList DataFilesModel::checkedItems() return list; } +QStringList DataFilesModel::checkedItemsPaths() +{ + QStringList list; + + QList::ConstIterator it; + QList::ConstIterator itEnd = mFiles.constEnd(); + + int i = 0; + for (it = mFiles.constBegin(); it != itEnd; ++it) { + EsmFile *file = item(i); + ++i; + + if (mCheckStates[file->fileName()] == Qt::Checked && mAvailableFiles.contains(file->fileName())) + list << file->path(); + } + + return list; +} + void DataFilesModel::uncheckAll() { emit layoutAboutToBeChanged(); diff --git a/apps/launcher/model/datafilesmodel.hpp b/components/fileorderlist/model/datafilesmodel.hpp similarity index 98% rename from apps/launcher/model/datafilesmodel.hpp rename to components/fileorderlist/model/datafilesmodel.hpp index 29a770a862..adc80eac2a 100644 --- a/apps/launcher/model/datafilesmodel.hpp +++ b/components/fileorderlist/model/datafilesmodel.hpp @@ -43,6 +43,7 @@ public: QStringList checkedItems(); QStringList uncheckedItems(); + QStringList checkedItemsPaths(); Qt::CheckState checkState(const QModelIndex &index); void setCheckState(const QModelIndex &index, Qt::CheckState state); diff --git a/apps/launcher/model/esm/esmfile.cpp b/components/fileorderlist/model/esm/esmfile.cpp similarity index 100% rename from apps/launcher/model/esm/esmfile.cpp rename to components/fileorderlist/model/esm/esmfile.cpp diff --git a/apps/launcher/model/esm/esmfile.hpp b/components/fileorderlist/model/esm/esmfile.hpp similarity index 100% rename from apps/launcher/model/esm/esmfile.hpp rename to components/fileorderlist/model/esm/esmfile.hpp diff --git a/apps/launcher/model/modelitem.cpp b/components/fileorderlist/model/modelitem.cpp similarity index 100% rename from apps/launcher/model/modelitem.cpp rename to components/fileorderlist/model/modelitem.cpp diff --git a/apps/launcher/model/modelitem.hpp b/components/fileorderlist/model/modelitem.hpp similarity index 100% rename from apps/launcher/model/modelitem.hpp rename to components/fileorderlist/model/modelitem.hpp diff --git a/apps/launcher/utils/filedialog.cpp b/components/fileorderlist/utils/filedialog.cpp similarity index 100% rename from apps/launcher/utils/filedialog.cpp rename to components/fileorderlist/utils/filedialog.cpp diff --git a/apps/launcher/utils/filedialog.hpp b/components/fileorderlist/utils/filedialog.hpp similarity index 100% rename from apps/launcher/utils/filedialog.hpp rename to components/fileorderlist/utils/filedialog.hpp diff --git a/apps/launcher/utils/lineedit.cpp b/components/fileorderlist/utils/lineedit.cpp similarity index 100% rename from apps/launcher/utils/lineedit.cpp rename to components/fileorderlist/utils/lineedit.cpp diff --git a/apps/launcher/utils/lineedit.hpp b/components/fileorderlist/utils/lineedit.hpp similarity index 100% rename from apps/launcher/utils/lineedit.hpp rename to components/fileorderlist/utils/lineedit.hpp diff --git a/apps/launcher/utils/naturalsort.cpp b/components/fileorderlist/utils/naturalsort.cpp similarity index 100% rename from apps/launcher/utils/naturalsort.cpp rename to components/fileorderlist/utils/naturalsort.cpp diff --git a/apps/launcher/utils/naturalsort.hpp b/components/fileorderlist/utils/naturalsort.hpp similarity index 100% rename from apps/launcher/utils/naturalsort.hpp rename to components/fileorderlist/utils/naturalsort.hpp