Something went wrong. Try again.
OpenMW is an open implementation of the Morrowind engine
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219#include "view.hpp"
#include <QApplication>#include <QCloseEvent>#include <QMenuBar>#include <QMessageBox>#include <QScreen>#include <QScrollArea>#include <QScrollBar>
#include <algorithm>#include <filesystem>#include <sstream>
#include "../../model/doc/document.hpp"#include "../../model/doc/state.hpp"
#include "../../model/prefs/shortcut.hpp"#include "../../model/prefs/state.hpp"
#include "../../model/world/idtable.hpp"
#include "../world/dialoguesubview.hpp"#include "../world/scenesubview.hpp"#include "../world/scriptsubview.hpp"#include "../world/subviews.hpp"#include "../world/tablesubview.hpp"
#include "../tools/subviews.hpp"
#include <apps/opencs/model/prefs/category.hpp>#include <apps/opencs/model/prefs/setting.hpp>#include <apps/opencs/model/prefs/shortcutmanager.hpp>#include <apps/opencs/model/world/data.hpp>#include <apps/opencs/view/doc/subviewfactory.hpp>
#include <components/files/conversion.hpp>#include <components/misc/helpviewer.hpp>#include <components/misc/scalableicon.hpp>#include <components/misc/timeconvert.hpp>#include <components/version/version.hpp>
#include "globaldebugprofilemenu.hpp"#include "operations.hpp"#include "runlogsubview.hpp"#include "subview.hpp"#include "subviewfactoryimp.hpp"#include "viewmanager.hpp"
QRect desktopRect(){ QRegion virtualGeometry; for (auto screen : QGuiApplication::screens()) { virtualGeometry += screen->geometry(); } return virtualGeometry.boundingRect();}
void CSVDoc::View::closeEvent(QCloseEvent* event){ if (!mViewManager.closeRequest(this)) event->ignore(); else { // closeRequest() returns true if last document mViewManager.removeDocAndView(mDocument); }}
void CSVDoc::View::setupFileMenu(){ QMenu* file = menuBar()->addMenu(tr("File"));
QAction* newGame = createMenuEntry("New Game", ":menu-new-game", file, "document-file-newgame"); connect(newGame, &QAction::triggered, this, &View::newGameRequest);
QAction* newAddon = createMenuEntry("New Addon", ":menu-new-addon", file, "document-file-newaddon"); connect(newAddon, &QAction::triggered, this, &View::newAddonRequest);
QAction* open = createMenuEntry("Open", ":menu-open", file, "document-file-open"); connect(open, &QAction::triggered, this, &View::loadDocumentRequest);
QAction* save = createMenuEntry("Save", ":menu-save", file, "document-file-save"); connect(save, &QAction::triggered, this, &View::save); mSave = save;
file->addSeparator();
QAction* verify = createMenuEntry("Verify", ":menu-verify", file, "document-file-verify"); connect(verify, &QAction::triggered, this, &View::verify); mVerify = verify;
QAction* merge = createMenuEntry("Merge", ":menu-merge", file, "document-file-merge"); connect(merge, &QAction::triggered, this, &View::merge); mMerge = merge;
QAction* loadErrors = createMenuEntry("Error Log", ":error-log", file, "document-file-errorlog"); connect(loadErrors, &QAction::triggered, this, &View::loadErrorLog);
QAction* meta = createMenuEntry(CSMWorld::UniversalId::Type_MetaDatas, file, "document-file-metadata"); connect(meta, &QAction::triggered, this, &View::addMetaDataSubView);
file->addSeparator();
QAction* close = createMenuEntry("Close", ":menu-close", file, "document-file-close"); connect(close, &QAction::triggered, this, &View::close);
QAction* exit = createMenuEntry("Exit", ":menu-exit", file, "document-file-exit"); connect(exit, &QAction::triggered, this, &View::exit);
connect(this, &View::exitApplicationRequest, &mViewManager, &ViewManager::exitApplication);}
namespace{
void updateUndoRedoAction(QAction* action, const std::string& settingsKey) { QKeySequence seq; CSMPrefs::State::get().getShortcutManager().getSequence(settingsKey, seq); action->setShortcut(seq); }
}
void CSVDoc::View::undoActionChanged(){ updateUndoRedoAction(mUndo, "document-edit-undo");}
void CSVDoc::View::redoActionChanged(){ updateUndoRedoAction(mRedo, "document-edit-redo");}
void CSVDoc::View::setupEditMenu(){ QMenu* edit = menuBar()->addMenu(tr("Edit"));
mUndo = mDocument->getUndoStack().createUndoAction(this, tr("Undo")); setupShortcut("document-edit-undo", mUndo); connect(mUndo, &QAction::changed, this, &View::undoActionChanged); mUndo->setIcon(Misc::ScalableIcon::load(":menu-undo")); edit->addAction(mUndo);
mRedo = mDocument->getUndoStack().createRedoAction(this, tr("Redo")); connect(mRedo, &QAction::changed, this, &View::redoActionChanged); setupShortcut("document-edit-redo", mRedo); mRedo->setIcon(Misc::ScalableIcon::load(":menu-redo")); edit->addAction(mRedo);
QAction* userSettings = createMenuEntry("Preferences", ":menu-preferences", edit, "document-edit-preferences"); connect(userSettings, &QAction::triggered, this, &View::editSettingsRequest);
QAction* search = createMenuEntry(CSMWorld::UniversalId::Type_Search, edit, "document-edit-search"); connect(search, &QAction::triggered, this, &View::addSearchSubView);}
void CSVDoc::View::setupViewMenu(){ QMenu* view = menuBar()->addMenu(tr("View"));
QAction* newWindow = createMenuEntry("New View", ":menu-new-window", view, "document-view-newview"); connect(newWindow, &QAction::triggered, this, &View::newView);
mShowStatusBar = createMenuEntry("Toggle Status Bar", ":menu-status-bar", view, "document-view-statusbar"); connect(mShowStatusBar, &QAction::toggled, this, &View::toggleShowStatusBar); mShowStatusBar->setCheckable(true); mShowStatusBar->setChecked(CSMPrefs::get()["Windows"]["show-statusbar"].isTrue());
view->addAction(mShowStatusBar);
QAction* filters = createMenuEntry(CSMWorld::UniversalId::Type_Filters, view, "document-mechanics-filters"); connect(filters, &QAction::triggered, this, &View::addFiltersSubView);}
void CSVDoc::View::setupWorldMenu(){ QMenu* world = menuBar()->addMenu(tr("World"));
QAction* referenceables = createMenuEntry(CSMWorld::UniversalId::Type_Referenceables, world, "document-world-referencables"); connect(referenceables, &QAction::triggered, this, &View::addReferenceablesSubView);
QAction* references = createMenuEntry(CSMWorld::UniversalId::Type_References, world, "document-world-references"); connect(references, &QAction::triggered, this, &View::addReferencesSubView);
world->addSeparator();
QAction* cells = createMenuEntry(CSMWorld::UniversalId::Type_Cells, world, "document-world-cells"); connect(cells, &QAction::triggered, this, &View::addCellsSubView);
QAction* lands = createMenuEntry(CSMWorld::UniversalId::Type_Lands, world, "document-world-lands"); connect(lands, &QAction::triggered, this, &View::addLandsSubView);
QAction* landTextures = createMenuEntry(CSMWorld::UniversalId::Type_LandTextures, world, "document-world-landtextures"); connect(landTextures, &QAction::triggered, this, &View::addLandTexturesSubView);
QAction* grid = createMenuEntry(CSMWorld::UniversalId::Type_Pathgrids, world, "document-world-pathgrid"); connect(grid, &QAction::triggered, this, &View::addPathgridSubView);
world->addSeparator();
QAction* regions = createMenuEntry(CSMWorld::UniversalId::Type_Regions, world, "document-world-regions"); connect(regions, &QAction::triggered, this, &View::addRegionsSubView);
QAction* regionMap = createMenuEntry(CSMWorld::UniversalId::Type_RegionMap, world, "document-world-regionmap"); connect(regionMap, &QAction::triggered, this, &View::addRegionMapSubView);}
void CSVDoc::View::setupMechanicsMenu(){ QMenu* mechanics = menuBar()->addMenu(tr("Mechanics"));
QAction* scripts = createMenuEntry(CSMWorld::UniversalId::Type_Scripts, mechanics, "document-mechanics-scripts"); connect(scripts, &QAction::triggered, this, &View::addScriptsSubView);
QAction* startScripts = createMenuEntry(CSMWorld::UniversalId::Type_StartScripts, mechanics, "document-mechanics-startscripts"); connect(startScripts, &QAction::triggered, this, &View::addStartScriptsSubView);
QAction* globals = createMenuEntry(CSMWorld::UniversalId::Type_Globals, mechanics, "document-mechanics-globals"); connect(globals, &QAction::triggered, this, &View::addGlobalsSubView);
QAction* gmsts = createMenuEntry(CSMWorld::UniversalId::Type_Gmsts, mechanics, "document-mechanics-gamesettings"); connect(gmsts, &QAction::triggered, this, &View::addGmstsSubView);
mechanics->addSeparator();
QAction* spells = createMenuEntry(CSMWorld::UniversalId::Type_Spells, mechanics, "document-mechanics-spells"); connect(spells, &QAction::triggered, this, &View::addSpellsSubView);
QAction* enchantments = createMenuEntry(CSMWorld::UniversalId::Type_Enchantments, mechanics, "document-mechanics-enchantments"); connect(enchantments, &QAction::triggered, this, &View::addEnchantmentsSubView);
QAction* magicEffects = createMenuEntry(CSMWorld::UniversalId::Type_MagicEffects, mechanics, "document-mechanics-magiceffects"); connect(magicEffects, &QAction::triggered, this, &View::addMagicEffectsSubView);}
void CSVDoc::View::setupCharacterMenu(){ QMenu* characters = menuBar()->addMenu(tr("Characters"));
QAction* skills = createMenuEntry(CSMWorld::UniversalId::Type_Skills, characters, "document-character-skills"); connect(skills, &QAction::triggered, this, &View::addSkillsSubView);
QAction* classes = createMenuEntry(CSMWorld::UniversalId::Type_Classes, characters, "document-character-classes"); connect(classes, &QAction::triggered, this, &View::addClassesSubView);
QAction* factions = createMenuEntry(CSMWorld::UniversalId::Type_Faction, characters, "document-character-factions"); connect(factions, &QAction::triggered, this, &View::addFactionsSubView);
QAction* races = createMenuEntry(CSMWorld::UniversalId::Type_Races, characters, "document-character-races"); connect(races, &QAction::triggered, this, &View::addRacesSubView);
QAction* birthsigns = createMenuEntry(CSMWorld::UniversalId::Type_Birthsigns, characters, "document-character-birthsigns"); connect(birthsigns, &QAction::triggered, this, &View::addBirthsignsSubView);
QAction* bodyParts = createMenuEntry(CSMWorld::UniversalId::Type_BodyParts, characters, "document-character-bodyparts"); connect(bodyParts, &QAction::triggered, this, &View::addBodyPartsSubView);
characters->addSeparator();
QAction* topics = createMenuEntry(CSMWorld::UniversalId::Type_Topics, characters, "document-character-topics"); connect(topics, &QAction::triggered, this, &View::addTopicsSubView);
QAction* topicInfos = createMenuEntry(CSMWorld::UniversalId::Type_TopicInfos, characters, "document-character-topicinfos"); connect(topicInfos, &QAction::triggered, this, &View::addTopicInfosSubView);
characters->addSeparator();
QAction* journals = createMenuEntry(CSMWorld::UniversalId::Type_Journals, characters, "document-character-journals"); connect(journals, &QAction::triggered, this, &View::addJournalsSubView);
QAction* journalInfos = createMenuEntry(CSMWorld::UniversalId::Type_JournalInfos, characters, "document-character-journalinfos"); connect(journalInfos, &QAction::triggered, this, &View::addJournalInfosSubView);}
void CSVDoc::View::setupAssetsMenu(){ QMenu* assets = menuBar()->addMenu(tr("Assets"));
QAction* reload = createMenuEntry("Reload", ":menu-reload", assets, "document-assets-reload"); connect(reload, &QAction::triggered, &mDocument->getData(), &CSMWorld::Data::assetsChanged);
assets->addSeparator();
QAction* sounds = createMenuEntry(CSMWorld::UniversalId::Type_Sounds, assets, "document-assets-sounds"); connect(sounds, &QAction::triggered, this, &View::addSoundsSubView);
QAction* soundGens = createMenuEntry(CSMWorld::UniversalId::Type_SoundGens, assets, "document-assets-soundgens"); connect(soundGens, &QAction::triggered, this, &View::addSoundGensSubView);
assets->addSeparator(); // resources follow here
QAction* meshes = createMenuEntry(CSMWorld::UniversalId::Type_Meshes, assets, "document-assets-meshes"); connect(meshes, &QAction::triggered, this, &View::addMeshesSubView);
QAction* icons = createMenuEntry(CSMWorld::UniversalId::Type_Icons, assets, "document-assets-icons"); connect(icons, &QAction::triggered, this, &View::addIconsSubView);
QAction* musics = createMenuEntry(CSMWorld::UniversalId::Type_Musics, assets, "document-assets-musics"); connect(musics, &QAction::triggered, this, &View::addMusicsSubView);
QAction* soundFiles = createMenuEntry(CSMWorld::UniversalId::Type_SoundsRes, assets, "document-assets-soundres"); connect(soundFiles, &QAction::triggered, this, &View::addSoundsResSubView);
QAction* textures = createMenuEntry(CSMWorld::UniversalId::Type_Textures, assets, "document-assets-textures"); connect(textures, &QAction::triggered, this, &View::addTexturesSubView);
QAction* videos = createMenuEntry(CSMWorld::UniversalId::Type_Videos, assets, "document-assets-videos"); connect(videos, &QAction::triggered, this, &View::addVideosSubView);}
void CSVDoc::View::setupDebugMenu(){ QMenu* debug = menuBar()->addMenu(tr("Debug"));
QAction* profiles = createMenuEntry(CSMWorld::UniversalId::Type_DebugProfiles, debug, "document-debug-profiles"); connect(profiles, &QAction::triggered, this, &View::addDebugProfilesSubView);
debug->addSeparator();
mGlobalDebugProfileMenu = new GlobalDebugProfileMenu( &dynamic_cast<CSMWorld::IdTable&>( *mDocument->getData().getTableModel(CSMWorld::UniversalId::Type_DebugProfiles)), this);
connect(mGlobalDebugProfileMenu, &GlobalDebugProfileMenu::triggered, this, [this](const std::string& profile) { this->run(profile, ""); });
QAction* runDebug = debug->addMenu(mGlobalDebugProfileMenu); runDebug->setText(tr("Run OpenMW")); setupShortcut("document-debug-run", runDebug); runDebug->setIcon(Misc::ScalableIcon::load(":run-openmw"));
QAction* stopDebug = createMenuEntry("Stop OpenMW", ":stop-openmw", debug, "document-debug-shutdown"); connect(stopDebug, &QAction::triggered, this, &View::stop); mStopDebug = stopDebug;
QAction* runLog = createMenuEntry(CSMWorld::UniversalId::Type_RunLog, debug, "document-debug-runlog"); connect(runLog, &QAction::triggered, this, &View::addRunLogSubView);}
void CSVDoc::View::setupHelpMenu(){ QMenu* help = menuBar()->addMenu(tr("Help"));
QAction* helpInfo = createMenuEntry("Help", ":info", help, "document-help-help"); connect(helpInfo, &QAction::triggered, this, &View::openHelp);
QAction* tutorial = createMenuEntry("Tutorial", ":info", help, "document-help-tutorial"); connect(tutorial, &QAction::triggered, this, &View::tutorial);
QAction* about = createMenuEntry("About OpenMW-CS", ":info", help, "document-help-about"); connect(about, &QAction::triggered, this, &View::infoAbout);
QAction* aboutQt = createMenuEntry("About Qt", ":qt", help, "document-help-qt"); connect(aboutQt, &QAction::triggered, this, &View::infoAboutQt);}
QAction* CSVDoc::View::createMenuEntry(CSMWorld::UniversalId::Type type, QMenu* menu, const char* shortcutName){ const std::string title = CSMWorld::UniversalId(type).getTypeName(); QAction* entry = new QAction(QString::fromStdString(title), this); setupShortcut(shortcutName, entry); const std::string iconName = CSMWorld::UniversalId(type).getIcon(); if (!iconName.empty() && iconName != ":placeholder") entry->setIcon(Misc::ScalableIcon::load(QString::fromStdString(iconName)));
menu->addAction(entry);
return entry;}
QAction* CSVDoc::View::createMenuEntry( const std::string& title, const std::string& iconName, QMenu* menu, const char* shortcutName){ QAction* entry = new QAction(QString::fromStdString(title), this); setupShortcut(shortcutName, entry); if (!iconName.empty() && iconName != ":placeholder") entry->setIcon(Misc::ScalableIcon::load(QString::fromStdString(iconName)));
menu->addAction(entry);
return entry;}
void CSVDoc::View::setupUi(){ setupFileMenu(); setupEditMenu(); setupViewMenu(); setupWorldMenu(); setupMechanicsMenu(); setupCharacterMenu(); setupAssetsMenu(); setupDebugMenu(); setupHelpMenu();}
void CSVDoc::View::setupShortcut(const char* name, QAction* action){ CSMPrefs::Shortcut* shortcut = new CSMPrefs::Shortcut(name, this); shortcut->associateAction(action);}
void CSVDoc::View::updateTitle(){ std::ostringstream stream;
stream << Files::pathToUnicodeString(mDocument->getSavePath().filename());
if (mDocument->getState() & CSMDoc::State_Modified) stream << " *";
if (mViewTotal > 1) stream << " [" << (mViewIndex + 1) << "/" << mViewTotal << "]";
CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"];
bool hideTitle = windows["hide-subview"].isTrue() && mSubViews.size() == 1 && !mSubViews.at(0)->isFloating();
if (hideTitle) stream << " - " << mSubViews.at(0)->getTitle();
setWindowTitle(QString::fromUtf8(stream.str().c_str()));}
void CSVDoc::View::updateSubViewIndices(SubView* view){ CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"];
if (view && mSubViews.contains(view)) { mSubViews.removeOne(view);
// adjust (reduce) the scroll area (even floating), except when it is "Scrollbar Only" if (windows["mainwindow-scrollbar"].toString() == "Grow then Scroll") updateScrollbar(); }
bool hideTitle = windows["hide-subview"].isTrue() && mSubViews.size() == 1 && !mSubViews.at(0)->isFloating();
updateTitle();
for (SubView* subView : mSubViews) { if (!subView->isFloating()) { if (hideTitle) { subView->setTitleBarWidget(new QWidget(this)); subView->setWindowTitle(QString::fromUtf8(subView->getTitle().c_str())); } else { delete subView->titleBarWidget(); subView->setTitleBarWidget(nullptr); } } }}
void CSVDoc::View::updateActions(){ bool editing = !(mDocument->getState() & CSMDoc::State_Locked); bool running = mDocument->getState() & CSMDoc::State_Running;
for (std::vector<QAction*>::iterator iter(mEditingActions.begin()); iter != mEditingActions.end(); ++iter) (*iter)->setEnabled(editing);
mUndo->setEnabled(editing && mDocument->getUndoStack().canUndo()); mRedo->setEnabled(editing && mDocument->getUndoStack().canRedo());
mSave->setEnabled(!(mDocument->getState() & CSMDoc::State_Saving) && !running); mVerify->setEnabled(!(mDocument->getState() & CSMDoc::State_Verifying));
mGlobalDebugProfileMenu->updateActions(running); mStopDebug->setEnabled(running);
mMerge->setEnabled(mDocument->getContentFiles().size() > 1 && !(mDocument->getState() & CSMDoc::State_Merging));}
CSVDoc::View::View(ViewManager& viewManager, CSMDoc::Document* document, int totalViews) : mViewManager(viewManager) , mDocument(document) , mViewIndex(totalViews - 1) , mViewTotal(totalViews) , mScroll(nullptr) , mScrollbarOnly(false){ CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"];
int width = std::max(windows["default-width"].toInt(), 300); int height = std::max(windows["default-height"].toInt(), 300);
resize(width, height);
mSubViewWindow.setDockOptions(QMainWindow::AllowNestedDocks);
if (windows["mainwindow-scrollbar"].toString() == "Grow Only") { setCentralWidget(&mSubViewWindow); } else { createScrollArea(); }
mOperations = new Operations; addDockWidget(Qt::BottomDockWidgetArea, mOperations);
setContextMenuPolicy(Qt::NoContextMenu);
updateTitle();
setupUi();
updateActions();
CSVWorld::addSubViewFactories(mSubViewFactory); CSVTools::addSubViewFactories(mSubViewFactory);
mSubViewFactory.add(CSMWorld::UniversalId::Type_RunLog, new SubViewFactory<RunLogSubView>);
connect(mOperations, &Operations::abortOperation, this, &View::abortOperation);
connect(&CSMPrefs::State::get(), &CSMPrefs::State::settingChanged, this, &View::settingChanged);}
const CSMDoc::Document* CSVDoc::View::getDocument() const{ return mDocument;}
CSMDoc::Document* CSVDoc::View::getDocument(){ return mDocument;}
void CSVDoc::View::setIndex(int viewIndex, int totalViews){ mViewIndex = viewIndex; mViewTotal = totalViews; updateTitle();}
void CSVDoc::View::updateDocumentState(){ updateTitle(); updateActions();
static const int operations[] = { CSMDoc::State_Saving, CSMDoc::State_Verifying, CSMDoc::State_Searching, CSMDoc::State_Merging, // end marker -1, };
int state = mDocument->getState();
for (int i = 0; operations[i] != -1; ++i) if (!(state & operations[i])) mOperations->quitOperation(operations[i]);
QList<CSVDoc::SubView*> subViews = findChildren<CSVDoc::SubView*>();
for (QList<CSVDoc::SubView*>::iterator iter(subViews.begin()); iter != subViews.end(); ++iter) (*iter)->setEditLock(state & CSMDoc::State_Locked);}
void CSVDoc::View::updateProgress(int current, int max, int type, int threads){ mOperations->setProgress(current, max, type, threads);}
void CSVDoc::View::addSubView(const CSMWorld::UniversalId& id, const std::string& hint){ CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"];
bool isReferenceable = id.getClass() == CSMWorld::UniversalId::Class_RefRecord;
// User setting to reuse sub views (on a per top level view basis) if (windows["reuse"].isTrue()) { for (SubView* sb : mSubViews) { bool isSubViewReferenceable = sb->getUniversalId().getType() == CSMWorld::UniversalId::Type_Referenceable;
if ((isReferenceable && isSubViewReferenceable && id.getId() == sb->getUniversalId().getId()) || (!isReferenceable && id == sb->getUniversalId())) { sb->setFocus(); if (!hint.empty()) sb->useHint(hint); return; } } }
if (mScroll) QObject::connect(mScroll->horizontalScrollBar(), &QScrollBar::rangeChanged, this, &View::moveScrollBarToEnd);
// User setting for limiting the number of sub views per top level view. // Automatically open a new top level view if this number is exceeded // // If the sub view limit setting is one, the sub view title bar is hidden and the // text in the main title bar is adjusted accordingly if (mSubViews.size() >= windows["max-subviews"].toInt()) // create a new top level view { mViewManager.addView(mDocument, id, hint);
return; }
SubView* view = nullptr; if (isReferenceable) { view = mSubViewFactory.makeSubView( CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Referenceable, id), *mDocument); } else { view = mSubViewFactory.makeSubView(id, *mDocument); } assert(view); view->setParent(this); view->setEditLock(mDocument->getState() & CSMDoc::State_Locked); mSubViews.append(view); // only after assert
int minWidth = windows["minimum-width"].toInt(); view->setMinimumWidth(minWidth);
view->setStatusBar(mShowStatusBar->isChecked());
// Work out how to deal with additional subviews // // Policy for "Grow then Scroll": // // - Increase the horizontal width of the mainwindow until it becomes greater than or equal // to the screen (monitor) width. // - Move the mainwindow position sideways if necessary to fit within the screen. // - Any more additions increases the size of the mSubViewWindow (horizontal scrollbar // should become visible) // - Move the scroll bar to the newly added subview // mScrollbarOnly = windows["mainwindow-scrollbar"].toString() == "Scrollbar Only";
updateWidth(true, minWidth);
mSubViewWindow.addDockWidget(Qt::TopDockWidgetArea, view);
// Horizontal orientation doesn't respect layout direction, so we need to move the new view if (windows["subview-open-direction"].toString() == "Open Left" && mSubViews.size() > 1) { // Get list of top-area dock widget subviews sorted by coordinates QList<SubView*> orderedTopViews; for (const auto& windowView : mSubViews) if (windowView != view && mSubViewWindow.dockWidgetArea(windowView) == Qt::TopDockWidgetArea) orderedTopViews.append(windowView); if (!orderedTopViews.isEmpty()) { std::sort(orderedTopViews.begin(), orderedTopViews.end(), [](auto* a, auto* b) { if (a->x() != b->x()) return a->x() < b->x(); else if (a->y() != b->y()) return a->y() < b->y(); return a->getUniversalId() < b->getUniversalId(); });
// Split twice to make the new view the leftmost. // If the leftmost view is nested, the new view will be added above it. mSubViewWindow.splitDockWidget(orderedTopViews[0], view, Qt::Orientation::Horizontal); mSubViewWindow.splitDockWidget(view, orderedTopViews[0], Qt::Orientation::Horizontal); } }
updateSubViewIndices();
connect(view, &SubView::focusId, this, &View::addSubView);
connect(view, qOverload<SubView*>(&SubView::closeRequest), this, &View::closeRequest);
connect(view, &SubView::updateTitle, this, &View::updateTitle);
connect(view, &SubView::updateSubViewIndices, this, &View::updateSubViewIndices);
CSVWorld::TableSubView* tableView = dynamic_cast<CSVWorld::TableSubView*>(view); if (tableView) { connect(this, &View::requestFocus, tableView, &CSVWorld::TableSubView::requestFocus); }
CSVWorld::SceneSubView* sceneView = dynamic_cast<CSVWorld::SceneSubView*>(view); if (sceneView) { connect(sceneView, &CSVWorld::SceneSubView::requestFocus, this, &View::onRequestFocus); }
if (CSMPrefs::State::get()["ID Tables"]["subview-new-window"].isTrue()) { CSVWorld::DialogueSubView* dialogueView = dynamic_cast<CSVWorld::DialogueSubView*>(view); if (dialogueView) dialogueView->setFloating(true);
CSVWorld::ScriptSubView* scriptView = dynamic_cast<CSVWorld::ScriptSubView*>(view); if (scriptView) scriptView->setFloating(true); }
view->show();
if (!hint.empty()) view->useHint(hint);}
void CSVDoc::View::moveScrollBarToEnd(int min, int max){ if (mScroll) { CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; if (windows["subview-open-direction"].toString() == "Open Left") mScroll->horizontalScrollBar()->setValue(min); else mScroll->horizontalScrollBar()->setValue(max);
QObject::disconnect(mScroll->horizontalScrollBar(), &QScrollBar::rangeChanged, this, &View::moveScrollBarToEnd); }}
void CSVDoc::View::settingChanged(const CSMPrefs::Setting* setting){ if (*setting == "Windows/hide-subview") updateSubViewIndices(nullptr); else if (*setting == "Windows/mainwindow-scrollbar") { if (setting->toString() != "Grow Only") { if (mScroll) { if (setting->toString() == "Scrollbar Only") { mScrollbarOnly = true; mSubViewWindow.setMinimumWidth(0); } else if (mScrollbarOnly) { mScrollbarOnly = false; updateScrollbar(); } } else { createScrollArea(); } } else if (mScroll) { mScroll->takeWidget(); setCentralWidget(&mSubViewWindow); mScroll->deleteLater(); mScroll = nullptr; } }}
void CSVDoc::View::newView(){ mViewManager.addView(mDocument);}
void CSVDoc::View::save(){ mDocument->save();}
void CSVDoc::View::openHelp(){ Misc::HelpViewer::openHelp("manuals/openmw-cs/index.html");}
void CSVDoc::View::tutorial(){ Misc::HelpViewer::openHelp("manuals/openmw-cs/tour.html");}
void CSVDoc::View::infoAbout(){ // Get current OpenMW version QString versionInfo = (Version::getOpenmwVersionDescription() +#if defined(__x86_64__) || defined(_M_X64) " (64-bit)") .c_str();#else " (32-bit)") .c_str();#endif
// Get current year const auto copyrightInfo = Misc::timeToString(std::chrono::system_clock::now(), "Copyright © 2008-%Y OpenMW Team");
QString aboutText = QString( "<p style=\"white-space: pre-wrap;\">" "<b><h2>OpenMW Construction Set</h2></b>" "%1\n\n" "%2\n\n" "%3\n\n" "<table>" "<tr><td>%4</td><td><a href=\"https://openmw.org\">https://openmw.org</a></td></tr>" "<tr><td>%5</td><td><a href=\"https://forum.openmw.org\">https://forum.openmw.org</a></td></tr>" "<tr><td>%6</td><td><a " "href=\"https://gitlab.com/OpenMW/openmw/issues\">https://gitlab.com/OpenMW/openmw/issues</a></td></tr>" "<tr><td>%7</td><td><a href=\"https://web.libera.chat/#openmw\">ircs://irc.libera.chat/#openmw</a></td></tr>" "</table>" "</p>") .arg(versionInfo, tr("OpenMW-CS is a content file editor for OpenMW, a modern, free and open source game " "engine."), tr(copyrightInfo.c_str()), tr("Home Page:"), tr("Forum:"), tr("Bug Tracker:"), tr("IRC:"));
QMessageBox::about(this, "About OpenMW-CS", aboutText);}
void CSVDoc::View::infoAboutQt(){ QMessageBox::aboutQt(this);}
void CSVDoc::View::verify(){ addSubView(mDocument->verify());}
void CSVDoc::View::addGlobalsSubView(){ addSubView(CSMWorld::UniversalId::Type_Globals);}
void CSVDoc::View::addGmstsSubView(){ addSubView(CSMWorld::UniversalId::Type_Gmsts);}
void CSVDoc::View::addSkillsSubView(){ addSubView(CSMWorld::UniversalId::Type_Skills);}
void CSVDoc::View::addClassesSubView(){ addSubView(CSMWorld::UniversalId::Type_Classes);}
void CSVDoc::View::addFactionsSubView(){ addSubView(CSMWorld::UniversalId::Type_Factions);}
void CSVDoc::View::addRacesSubView(){ addSubView(CSMWorld::UniversalId::Type_Races);}
void CSVDoc::View::addSoundsSubView(){ addSubView(CSMWorld::UniversalId::Type_Sounds);}
void CSVDoc::View::addScriptsSubView(){ addSubView(CSMWorld::UniversalId::Type_Scripts);}
void CSVDoc::View::addRegionsSubView(){ addSubView(CSMWorld::UniversalId::Type_Regions);}
void CSVDoc::View::addBirthsignsSubView(){ addSubView(CSMWorld::UniversalId::Type_Birthsigns);}
void CSVDoc::View::addSpellsSubView(){ addSubView(CSMWorld::UniversalId::Type_Spells);}
void CSVDoc::View::addCellsSubView(){ addSubView(CSMWorld::UniversalId::Type_Cells);}
void CSVDoc::View::addReferenceablesSubView(){ addSubView(CSMWorld::UniversalId::Type_Referenceables);}
void CSVDoc::View::addReferencesSubView(){ addSubView(CSMWorld::UniversalId::Type_References);}
void CSVDoc::View::addRegionMapSubView(){ addSubView(CSMWorld::UniversalId::Type_RegionMap);}
void CSVDoc::View::addFiltersSubView(){ addSubView(CSMWorld::UniversalId::Type_Filters);}
void CSVDoc::View::addTopicsSubView(){ addSubView(CSMWorld::UniversalId::Type_Topics);}
void CSVDoc::View::addJournalsSubView(){ addSubView(CSMWorld::UniversalId::Type_Journals);}
void CSVDoc::View::addTopicInfosSubView(){ addSubView(CSMWorld::UniversalId::Type_TopicInfos);}
void CSVDoc::View::addJournalInfosSubView(){ addSubView(CSMWorld::UniversalId::Type_JournalInfos);}
void CSVDoc::View::addEnchantmentsSubView(){ addSubView(CSMWorld::UniversalId::Type_Enchantments);}
void CSVDoc::View::addBodyPartsSubView(){ addSubView(CSMWorld::UniversalId::Type_BodyParts);}
void CSVDoc::View::addSoundGensSubView(){ addSubView(CSMWorld::UniversalId::Type_SoundGens);}
void CSVDoc::View::addMeshesSubView(){ addSubView(CSMWorld::UniversalId::Type_Meshes);}
void CSVDoc::View::addIconsSubView(){ addSubView(CSMWorld::UniversalId::Type_Icons);}
void CSVDoc::View::addMusicsSubView(){ addSubView(CSMWorld::UniversalId::Type_Musics);}
void CSVDoc::View::addSoundsResSubView(){ addSubView(CSMWorld::UniversalId::Type_SoundsRes);}
void CSVDoc::View::addMagicEffectsSubView(){ addSubView(CSMWorld::UniversalId::Type_MagicEffects);}
void CSVDoc::View::addTexturesSubView(){ addSubView(CSMWorld::UniversalId::Type_Textures);}
void CSVDoc::View::addVideosSubView(){ addSubView(CSMWorld::UniversalId::Type_Videos);}
void CSVDoc::View::addDebugProfilesSubView(){ addSubView(CSMWorld::UniversalId::Type_DebugProfiles);}
void CSVDoc::View::addRunLogSubView(){ addSubView(CSMWorld::UniversalId::Type_RunLog);}
void CSVDoc::View::addLandsSubView(){ addSubView(CSMWorld::UniversalId::Type_Lands);}
void CSVDoc::View::addLandTexturesSubView(){ addSubView(CSMWorld::UniversalId::Type_LandTextures);}
void CSVDoc::View::addPathgridSubView(){ addSubView(CSMWorld::UniversalId::Type_Pathgrids);}
void CSVDoc::View::addStartScriptsSubView(){ addSubView(CSMWorld::UniversalId::Type_StartScripts);}
void CSVDoc::View::addSearchSubView(){ addSubView(mDocument->newSearch());}
void CSVDoc::View::addMetaDataSubView(){ addSubView(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_MetaData, "sys::meta"));}
void CSVDoc::View::abortOperation(int type){ mDocument->abortOperation(type); updateActions();}
CSVDoc::Operations* CSVDoc::View::getOperations() const{ return mOperations;}
void CSVDoc::View::exit(){ emit exitApplicationRequest(this);}
void CSVDoc::View::resizeViewWidth(int width){ if (width >= 0) resize(width, geometry().height());}
void CSVDoc::View::resizeViewHeight(int height){ if (height >= 0) resize(geometry().width(), height);}
void CSVDoc::View::toggleShowStatusBar(bool show){ for (QObject* view : mSubViewWindow.children()) { if (CSVDoc::SubView* subView = dynamic_cast<CSVDoc::SubView*>(view)) subView->setStatusBar(show); }}
void CSVDoc::View::toggleStatusBar(bool checked){ mShowStatusBar->setChecked(checked);}
void CSVDoc::View::loadErrorLog(){ addSubView(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_LoadErrorLog, 0));}
void CSVDoc::View::run(const std::string& profile, const std::string& startupInstruction){ mDocument->startRunning(profile, startupInstruction);}
void CSVDoc::View::stop(){ mDocument->stopRunning();}
void CSVDoc::View::closeRequest(SubView* subView){ CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"];
if (mSubViews.size() > 1 || mViewTotal <= 1 || !windows["hide-subview"].isTrue()) { subView->deleteLater(); mSubViews.removeOne(subView); } else if (mViewManager.closeRequest(this)) mViewManager.removeDocAndView(mDocument);}
void CSVDoc::View::updateScrollbar(){ QRect rect; QWidget* topLevel = QApplication::topLevelAt(pos()); if (topLevel) rect = topLevel->rect(); else rect = this->rect();
int newWidth = 0; for (int i = 0; i < mSubViews.size(); ++i) { newWidth += mSubViews[i]->width(); }
int frameWidth = frameGeometry().width() - width();
if ((newWidth + frameWidth) >= rect.width()) mSubViewWindow.setMinimumWidth(newWidth); else mSubViewWindow.setMinimumWidth(0);}
void CSVDoc::View::merge(){ emit mergeDocument(mDocument);}
void CSVDoc::View::updateWidth(bool isGrowLimit, int minSubViewWidth){ QRect rect; if (isGrowLimit) { QScreen* screen = getWidgetScreen(pos()); rect = screen->geometry(); } else rect = desktopRect();
if (!mScrollbarOnly && mScroll && mSubViews.size() > 1) { int newWidth = width() + minSubViewWidth; int frameWidth = frameGeometry().width() - width(); if (newWidth + frameWidth <= rect.width()) { resize(newWidth, height()); // WARNING: below code assumes that the frame geometry expands to the right. // This doesn't conflict with subview-open-direction. if (x() > rect.width() - (newWidth + frameWidth)) move(rect.width() - (newWidth + frameWidth), y()); // shift left to stay within the screen } else { // full width resize(rect.width() - frameWidth, height()); mSubViewWindow.setMinimumWidth(mSubViewWindow.width() + minSubViewWidth); move(0, y()); } }}
void CSVDoc::View::createScrollArea(){ mScroll = new QScrollArea(this); mScroll->setWidgetResizable(true); mScroll->setWidget(&mSubViewWindow); setCentralWidget(mScroll);}
void CSVDoc::View::onRequestFocus(const std::string& id){ if (CSMPrefs::get()["3D Scene Editing"]["open-list-view"].isTrue()) { addReferencesSubView(); emit requestFocus(id); } else { addSubView(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Reference, id)); }}
QScreen* CSVDoc::View::getWidgetScreen(const QPoint& position){ QScreen* screen = QApplication::screenAt(position); if (screen) return screen;
const QList<QScreen*> screens = QApplication::screens(); if (screens.isEmpty()) throw std::runtime_error("No screens available");
int closestDistance = std::numeric_limits<int>::max(); for (QScreen* candidate : screens) { const QRect geometry = candidate->geometry(); const int dx = position.x() - std::clamp(position.x(), geometry.left(), geometry.right()); const int dy = position.y() - std::clamp(position.y(), geometry.top(), geometry.bottom()); const int distance = dx * dx + dy * dy;
if (distance < closestDistance) { closestDistance = distance; screen = candidate; } }
if (screen == nullptr) screen = screens.first();
return screen;}