mirror of
https://github.com/huderlem/porymap.git
synced 2026-09-26 09:56:32 -05:00
Merge remote-tracking branch 'origin/master' into fix827
# Conflicts: # src/mainwindow.cpp
This commit is contained in:
156
src/config.cpp
156
src/config.cpp
@@ -1,5 +1,6 @@
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
#include "shortcut.h"
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFormLayout>
|
||||
@@ -11,6 +12,8 @@
|
||||
#include <QTextStream>
|
||||
#include <QRegularExpression>
|
||||
#include <QStandardPaths>
|
||||
#include <QAction>
|
||||
#include <QAbstractButton>
|
||||
|
||||
KeyValueConfigBase::~KeyValueConfigBase() {
|
||||
|
||||
@@ -36,7 +39,7 @@ void KeyValueConfigBase::load() {
|
||||
QTextStream in(&file);
|
||||
in.setCodec("UTF-8");
|
||||
QList<QString> configLines;
|
||||
QRegularExpression re("^(?<key>.+)=(?<value>.*)$");
|
||||
QRegularExpression re("^(?<key>[^=]+)=(?<value>.*)$");
|
||||
while (!in.atEnd()) {
|
||||
QString line = in.readLine().trimmed();
|
||||
int commentIndex = line.indexOf("#");
|
||||
@@ -187,6 +190,10 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) {
|
||||
}
|
||||
} else if (key == "theme") {
|
||||
this->theme = value;
|
||||
} else if (key == "text_editor_open_directory") {
|
||||
this->textEditorOpenFolder = value;
|
||||
} else if (key == "text_editor_goto_line") {
|
||||
this->textEditorGotoLine = value;
|
||||
} else {
|
||||
logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key));
|
||||
}
|
||||
@@ -216,6 +223,8 @@ QMap<QString, QString> PorymapConfig::getKeyValueMap() {
|
||||
map.insert("region_map_dimensions", QString("%1x%2").arg(this->regionMapDimensions.width())
|
||||
.arg(this->regionMapDimensions.height()));
|
||||
map.insert("theme", this->theme);
|
||||
map.insert("text_editor_open_directory", this->textEditorOpenFolder);
|
||||
map.insert("text_editor_goto_line", this->textEditorGotoLine);
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -316,6 +325,16 @@ void PorymapConfig::setTheme(QString theme) {
|
||||
this->theme = theme;
|
||||
}
|
||||
|
||||
void PorymapConfig::setTextEditorOpenFolder(const QString &command) {
|
||||
this->textEditorOpenFolder = command;
|
||||
this->save();
|
||||
}
|
||||
|
||||
void PorymapConfig::setTextEditorGotoLine(const QString &command) {
|
||||
this->textEditorGotoLine = command;
|
||||
this->save();
|
||||
}
|
||||
|
||||
QString PorymapConfig::getRecentProject() {
|
||||
return this->recentProject;
|
||||
}
|
||||
@@ -398,6 +417,14 @@ QString PorymapConfig::getTheme() {
|
||||
return this->theme;
|
||||
}
|
||||
|
||||
QString PorymapConfig::getTextEditorOpenFolder() {
|
||||
return this->textEditorOpenFolder;
|
||||
}
|
||||
|
||||
QString PorymapConfig::getTextEditorGotoLine() {
|
||||
return this->textEditorGotoLine;
|
||||
}
|
||||
|
||||
const QMap<BaseGameVersion, QString> baseGameVersionMap = {
|
||||
{BaseGameVersion::pokeruby, "pokeruby"},
|
||||
{BaseGameVersion::pokefirered, "pokefirered"},
|
||||
@@ -414,7 +441,7 @@ ProjectConfig projectConfig;
|
||||
|
||||
QString ProjectConfig::getConfigFilepath() {
|
||||
// porymap config file is in the same directory as porymap itself.
|
||||
return QDir(this->projectDir).filePath("porymap.project.cfg");;
|
||||
return QDir(this->projectDir).filePath("porymap.project.cfg");
|
||||
}
|
||||
|
||||
void ProjectConfig::parseConfigKeyValue(QString key, QString value) {
|
||||
@@ -703,3 +730,128 @@ void ProjectConfig::setCustomScripts(QList<QString> scripts) {
|
||||
QList<QString> ProjectConfig::getCustomScripts() {
|
||||
return this->customScripts;
|
||||
}
|
||||
|
||||
ShortcutsConfig shortcutsConfig;
|
||||
|
||||
QString ShortcutsConfig::getConfigFilepath() {
|
||||
QString settingsPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
QDir dir(settingsPath);
|
||||
if (!dir.exists())
|
||||
dir.mkpath(settingsPath);
|
||||
|
||||
QString configPath = dir.absoluteFilePath("porymap.shortcuts.cfg");
|
||||
|
||||
return configPath;
|
||||
}
|
||||
|
||||
void ShortcutsConfig::parseConfigKeyValue(QString key, QString value) {
|
||||
QStringList keySequences = value.split(' ');
|
||||
for (auto keySequence : keySequences)
|
||||
user_shortcuts.insert(key, keySequence);
|
||||
}
|
||||
|
||||
QMap<QString, QString> ShortcutsConfig::getKeyValueMap() {
|
||||
QMap<QString, QString> map;
|
||||
for (auto cfg_key : user_shortcuts.uniqueKeys()) {
|
||||
auto keySequences = user_shortcuts.values(cfg_key);
|
||||
QStringList keySequenceStrings;
|
||||
for (auto keySequence : keySequences)
|
||||
keySequenceStrings.append(keySequence.toString());
|
||||
map.insert(cfg_key, keySequenceStrings.join(' '));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
void ShortcutsConfig::setDefaultShortcuts(const QObjectList &objects) {
|
||||
storeShortcutsFromList(StoreType::Default, objects);
|
||||
save();
|
||||
}
|
||||
|
||||
QList<QKeySequence> ShortcutsConfig::defaultShortcuts(const QObject *object) const {
|
||||
return default_shortcuts.values(cfgKey(object));
|
||||
}
|
||||
|
||||
void ShortcutsConfig::setUserShortcuts(const QObjectList &objects) {
|
||||
storeShortcutsFromList(StoreType::User, objects);
|
||||
save();
|
||||
}
|
||||
|
||||
void ShortcutsConfig::setUserShortcuts(const QMultiMap<const QObject *, QKeySequence> &objects_keySequences) {
|
||||
for (auto *object : objects_keySequences.uniqueKeys())
|
||||
if (!object->objectName().isEmpty() && !object->objectName().startsWith("_q_"))
|
||||
storeShortcuts(StoreType::User, cfgKey(object), objects_keySequences.values(object));
|
||||
save();
|
||||
}
|
||||
|
||||
QList<QKeySequence> ShortcutsConfig::userShortcuts(const QObject *object) const {
|
||||
return user_shortcuts.values(cfgKey(object));
|
||||
}
|
||||
|
||||
void ShortcutsConfig::storeShortcutsFromList(StoreType storeType, const QObjectList &objects) {
|
||||
for (const auto *object : objects)
|
||||
if (!object->objectName().isEmpty() && !object->objectName().startsWith("_q_"))
|
||||
storeShortcuts(storeType, cfgKey(object), currentShortcuts(object));
|
||||
}
|
||||
|
||||
void ShortcutsConfig::storeShortcuts(
|
||||
StoreType storeType,
|
||||
const QString &cfgKey,
|
||||
const QList<QKeySequence> &keySequences)
|
||||
{
|
||||
bool storeUser = (storeType == User) || !user_shortcuts.contains(cfgKey);
|
||||
|
||||
if (storeType == Default)
|
||||
default_shortcuts.remove(cfgKey);
|
||||
if (storeUser)
|
||||
user_shortcuts.remove(cfgKey);
|
||||
|
||||
if (keySequences.isEmpty()) {
|
||||
if (storeType == Default)
|
||||
default_shortcuts.insert(cfgKey, QKeySequence());
|
||||
if (storeUser)
|
||||
user_shortcuts.insert(cfgKey, QKeySequence());
|
||||
} else {
|
||||
for (auto keySequence : keySequences) {
|
||||
if (storeType == Default)
|
||||
default_shortcuts.insert(cfgKey, keySequence);
|
||||
if (storeUser)
|
||||
user_shortcuts.insert(cfgKey, keySequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Creates a config key from the object's name prepended with the parent
|
||||
* window's object name, and converts camelCase to snake_case. */
|
||||
QString ShortcutsConfig::cfgKey(const QObject *object) const {
|
||||
auto cfg_key = QString();
|
||||
auto *parentWidget = static_cast<QWidget *>(object->parent());
|
||||
if (parentWidget)
|
||||
cfg_key = parentWidget->window()->objectName() + '_';
|
||||
cfg_key += object->objectName();
|
||||
|
||||
QRegularExpression re("[A-Z]");
|
||||
int i = cfg_key.indexOf(re, 1);
|
||||
while (i != -1) {
|
||||
if (cfg_key.at(i - 1) != '_')
|
||||
cfg_key.insert(i++, '_');
|
||||
i = cfg_key.indexOf(re, i + 1);
|
||||
}
|
||||
return cfg_key.toLower();
|
||||
}
|
||||
|
||||
QList<QKeySequence> ShortcutsConfig::currentShortcuts(const QObject *object) const {
|
||||
if (object->inherits("QAction")) {
|
||||
const auto *action = qobject_cast<const QAction *>(object);
|
||||
return action->shortcuts();
|
||||
} else if (object->inherits("Shortcut")) {
|
||||
const auto *shortcut = qobject_cast<const Shortcut *>(object);
|
||||
return shortcut->keys();
|
||||
} else if (object->inherits("QShortcut")) {
|
||||
const auto *qshortcut = qobject_cast<const QShortcut *>(object);
|
||||
return { qshortcut->key() };
|
||||
} else if (object->property("shortcut").isValid()) {
|
||||
return { object->property("shortcut").value<QKeySequence>() };
|
||||
} else {
|
||||
return { };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,12 +450,32 @@ void Map::magicFillCollisionElevation(int initialX, int initialY, uint16_t colli
|
||||
}
|
||||
}
|
||||
|
||||
QList<Event *> Map::getAllEvents() {
|
||||
QList<Event*> all;
|
||||
for (QList<Event*> list : events.values()) {
|
||||
all += list;
|
||||
QList<Event *> Map::getAllEvents() const {
|
||||
QList<Event *> all_events;
|
||||
for (const auto &event_list : events) {
|
||||
all_events << event_list;
|
||||
}
|
||||
return all;
|
||||
return all_events;
|
||||
}
|
||||
|
||||
QStringList Map::eventScriptLabels(const QString &event_group_type) const {
|
||||
QStringList scriptLabels;
|
||||
if (event_group_type.isEmpty()) {
|
||||
for (const auto *event : getAllEvents())
|
||||
scriptLabels << event->get("script_label");
|
||||
} else {
|
||||
for (const auto *event : events.value(event_group_type))
|
||||
scriptLabels << event->get("script_label");
|
||||
}
|
||||
|
||||
scriptLabels.removeDuplicates();
|
||||
scriptLabels.removeAll(QString());
|
||||
if (scriptLabels.contains("0x0"))
|
||||
scriptLabels.move(scriptLabels.indexOf("0x0"), scriptLabels.count() - 1);
|
||||
if (scriptLabels.contains("NULL"))
|
||||
scriptLabels.move(scriptLabels.indexOf("NULL"), scriptLabels.count() - 1);
|
||||
|
||||
return scriptLabels;
|
||||
}
|
||||
|
||||
void Map::removeEvent(Event *event) {
|
||||
|
||||
@@ -337,7 +337,7 @@ QMap<QString, QString> ParseUtil::readNamedIndexCArray(QString filename, QString
|
||||
QRegularExpression re_text(QString(R"(\b%1\b\s*(\[?[^\]]*\])?\s*=\s*\{([^\}]*)\})").arg(label));
|
||||
QString body = re_text.match(text).captured(2).replace(QRegularExpression("\\s*"), "");
|
||||
|
||||
QRegularExpression re("\\[(?<index>[A-Za-z1-9_]*)\\]=(?<value>&?[A-Za-z1-9_]*)");
|
||||
QRegularExpression re("\\[(?<index>[A-Za-z0-9_]*)\\]=(?<value>&?[A-Za-z0-9_]*)");
|
||||
QRegularExpressionMatchIterator iter = re.globalMatch(body);
|
||||
|
||||
while (iter.hasNext()) {
|
||||
@@ -420,3 +420,118 @@ bool ParseUtil::ensureFieldsExist(QJsonObject obj, QList<QString> fields) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int ParseUtil::getScriptLineNumber(const QString &filePath, const QString &scriptLabel) {
|
||||
if (scriptLabel.isEmpty())
|
||||
return 0;
|
||||
|
||||
if (filePath.endsWith(".inc") || filePath.endsWith(".s"))
|
||||
return getRawScriptLineNumber(readTextFile(filePath), scriptLabel);
|
||||
else if (filePath.endsWith(".pory"))
|
||||
return getPoryScriptLineNumber(readTextFile(filePath), scriptLabel);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ParseUtil::getRawScriptLineNumber(QString text, const QString &scriptLabel) {
|
||||
removeStringLiterals(text);
|
||||
removeLineComments(text, "@");
|
||||
|
||||
static const QRegularExpression re_incScriptLabel("\\b(?<label>[\\w_][\\w\\d_]*):{1,2}");
|
||||
QRegularExpressionMatchIterator it = re_incScriptLabel.globalMatch(text);
|
||||
while (it.hasNext()) {
|
||||
const QRegularExpressionMatch match = it.next();
|
||||
if (match.captured("label") == scriptLabel)
|
||||
return text.left(match.capturedStart("label")).count('\n') + 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ParseUtil::getPoryScriptLineNumber(QString text, const QString &scriptLabel) {
|
||||
removeStringLiterals(text);
|
||||
removeLineComments(text, {"//", "#"});
|
||||
|
||||
static const QRegularExpression re_poryScriptLabel("\\b(script)(\\((global|local)\\))?\\s*\\b(?<label>[\\w_][\\w\\d_]*)");
|
||||
QRegularExpressionMatchIterator it = re_poryScriptLabel.globalMatch(text);
|
||||
while (it.hasNext()) {
|
||||
const QRegularExpressionMatch match = it.next();
|
||||
if (match.captured("label") == scriptLabel)
|
||||
return text.left(match.capturedStart("label")).count('\n') + 1;
|
||||
}
|
||||
|
||||
static const QRegularExpression re_poryRawSection("\\b(raw)\\s*`(?<raw_script>[^`]*)");
|
||||
QRegularExpressionMatchIterator raw_it = re_poryRawSection.globalMatch(text);
|
||||
while (raw_it.hasNext()) {
|
||||
const QRegularExpressionMatch match = raw_it.next();
|
||||
const int relativelineNum = getRawScriptLineNumber(match.captured("raw_script"), scriptLabel);
|
||||
if (relativelineNum)
|
||||
return text.left(match.capturedStart("raw_script")).count('\n') + relativelineNum;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
QString &ParseUtil::removeStringLiterals(QString &text) {
|
||||
static const QRegularExpression re_string("\".*\"");
|
||||
return text.remove(re_string);
|
||||
}
|
||||
|
||||
QString &ParseUtil::removeLineComments(QString &text, const QString &commentSymbol) {
|
||||
const QRegularExpression re_lineComment(commentSymbol + "+.*");
|
||||
return text.remove(re_lineComment);
|
||||
}
|
||||
|
||||
QString &ParseUtil::removeLineComments(QString &text, const QStringList &commentSymbols) {
|
||||
for (const auto &commentSymbol : commentSymbols)
|
||||
removeLineComments(text, commentSymbol);
|
||||
return text;
|
||||
}
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
|
||||
#include <QProcess>
|
||||
|
||||
QStringList ParseUtil::splitShellCommand(QStringView command) {
|
||||
return QProcess::splitCommand(command);
|
||||
}
|
||||
#else
|
||||
// The source for QProcess::splitCommand() as of Qt 5.15.2
|
||||
QStringList ParseUtil::splitShellCommand(QStringView command) {
|
||||
QStringList args;
|
||||
QString tmp;
|
||||
int quoteCount = 0;
|
||||
bool inQuote = false;
|
||||
|
||||
// handle quoting. tokens can be surrounded by double quotes
|
||||
// "hello world". three consecutive double quotes represent
|
||||
// the quote character itself.
|
||||
for (int i = 0; i < command.size(); ++i) {
|
||||
if (command.at(i) == QLatin1Char('"')) {
|
||||
++quoteCount;
|
||||
if (quoteCount == 3) {
|
||||
// third consecutive quote
|
||||
quoteCount = 0;
|
||||
tmp += command.at(i);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (quoteCount) {
|
||||
if (quoteCount == 1)
|
||||
inQuote = !inQuote;
|
||||
quoteCount = 0;
|
||||
}
|
||||
if (!inQuote && command.at(i).isSpace()) {
|
||||
if (!tmp.isEmpty()) {
|
||||
args += tmp;
|
||||
tmp.clear();
|
||||
}
|
||||
} else {
|
||||
tmp += command.at(i);
|
||||
}
|
||||
}
|
||||
if (!tmp.isEmpty())
|
||||
args += tmp;
|
||||
|
||||
return args;
|
||||
}
|
||||
#endif
|
||||
|
||||
128
src/editor.cpp
128
src/editor.cpp
@@ -9,10 +9,12 @@
|
||||
#include "metatile.h"
|
||||
#include "montabwidget.h"
|
||||
#include "editcommands.h"
|
||||
#include "config.h"
|
||||
#include <QCheckBox>
|
||||
#include <QPainter>
|
||||
#include <QMouseEvent>
|
||||
#include <QDir>
|
||||
#include <QProcess>
|
||||
#include <math.h>
|
||||
|
||||
static bool selectNewEvents = false;
|
||||
@@ -24,9 +26,8 @@ Editor::Editor(Ui::MainWindow* ui)
|
||||
this->settings = new Settings();
|
||||
this->playerViewRect = new MovableRect(&this->settings->playerViewRectEnabled, 30 * 8, 20 * 8, qRgb(255, 255, 255));
|
||||
this->cursorMapTileRect = new CursorTileRect(&this->settings->cursorTileRectEnabled, qRgb(255, 255, 255));
|
||||
this->map_ruler = new MapRuler();
|
||||
connect(this->map_ruler, &MapRuler::lengthChanged, this, &Editor::onMapRulerLengthChanged);
|
||||
connect(this->map_ruler, &MapRuler::deactivated, this, &Editor::onHoveredMapMetatileChanged);
|
||||
this->map_ruler = new MapRuler(4);
|
||||
connect(this->map_ruler, &MapRuler::statusChanged, this, &Editor::mapRulerStatusChanged);
|
||||
|
||||
/// Instead of updating the selected events after every single undo action
|
||||
/// (eg when the user rolls back several at once), only reselect events when
|
||||
@@ -357,6 +358,44 @@ void Editor::addNewWildMonGroup(QWidget *window) {
|
||||
}
|
||||
}
|
||||
|
||||
void Editor::deleteWildMonGroup() {
|
||||
QComboBox *labelCombo = ui->comboBox_EncounterGroupLabel;
|
||||
|
||||
if (labelCombo->count() < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
QMessageBox msgBox;
|
||||
msgBox.setText("Confirm Delete");
|
||||
msgBox.setInformativeText("Are you sure you want to delete " + labelCombo->currentText() + "?");
|
||||
|
||||
QPushButton *deleteButton = msgBox.addButton("Delete", QMessageBox::DestructiveRole);
|
||||
msgBox.addButton(QMessageBox::Cancel);
|
||||
msgBox.setDefaultButton(QMessageBox::Cancel);
|
||||
msgBox.exec();
|
||||
|
||||
if (msgBox.clickedButton() == deleteButton) {
|
||||
auto it = project->wildMonData.find(map->constantName);
|
||||
if (it == project->wildMonData.end()) {
|
||||
logError(QString("Failed to find data for map %1. Unable to delete").arg(map->constantName));
|
||||
return;
|
||||
}
|
||||
|
||||
int i = project->encounterGroupLabels.indexOf(labelCombo->currentText());
|
||||
if (i < 0) {
|
||||
logError(QString("Failed to find selected wild mon group: %1. Unable to delete")
|
||||
.arg(labelCombo->currentText()));
|
||||
return;
|
||||
}
|
||||
|
||||
it.value().erase(labelCombo->currentText());
|
||||
project->encounterGroupLabels.remove(i);
|
||||
|
||||
displayWildMonTables();
|
||||
emit wildMonDataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void Editor::configureEncounterJSON(QWidget *window) {
|
||||
QVector<QWidget *> fieldSlots;
|
||||
|
||||
@@ -905,9 +944,8 @@ void Editor::scaleMapView(int s) {
|
||||
if ((scale_exp + s) <= 5 && (scale_exp + s) >= -2) // sane limits
|
||||
{
|
||||
if (s == 0)
|
||||
scale_exp = 0;
|
||||
else
|
||||
scale_exp += s;
|
||||
s = -scale_exp;
|
||||
scale_exp += s;
|
||||
|
||||
double sfactor = pow(scale_base, s);
|
||||
ui->graphicsView_Map->scale(sfactor, sfactor);
|
||||
@@ -936,24 +974,12 @@ void Editor::onHoveredMapMetatileChanged(const QPoint &pos) {
|
||||
}
|
||||
}
|
||||
|
||||
void Editor::onMapRulerLengthChanged() {
|
||||
const QPoint pos = map_ruler->endPos();
|
||||
ui->statusBar->showMessage(QString("X: %1, Y: %2, Scale = %3x; %4")
|
||||
.arg(pos.x())
|
||||
.arg(pos.y())
|
||||
.arg(QString::number(pow(scale_base, scale_exp), 'g', 2))
|
||||
.arg(map_ruler->statusMessage));
|
||||
}
|
||||
|
||||
void Editor::onHoveredMapMetatileCleared() {
|
||||
this->playerViewRect->setVisible(false);
|
||||
this->cursorMapTileRect->setVisible(false);
|
||||
if (map_item->paintingMode == MapPixmapItem::PaintMode::Metatiles
|
||||
|| map_item->paintingMode == MapPixmapItem::PaintMode::EventObjects) {
|
||||
this->ui->statusBar->clearMessage();
|
||||
if (this->map_ruler->isAnchored()) {
|
||||
this->ui->statusBar->showMessage(this->map_ruler->statusMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2000,6 +2026,72 @@ void Editor::deleteEvent(Event *event) {
|
||||
//updateSelectedObjects();
|
||||
}
|
||||
|
||||
void Editor::openMapScripts() const {
|
||||
const QString scriptPath = project->getMapScriptsFilePath(map->name);
|
||||
openInTextEditor(scriptPath);
|
||||
}
|
||||
|
||||
void Editor::openScript(const QString &scriptLabel) const {
|
||||
// Find the location of scriptLabel.
|
||||
QStringList scriptPaths(project->getMapScriptsFilePath(map->name));
|
||||
scriptPaths << project->getEventScriptsFilePaths();
|
||||
int lineNum = 0;
|
||||
QString scriptPath = scriptPaths.first();
|
||||
for (const auto &path : scriptPaths) {
|
||||
lineNum = ParseUtil::getScriptLineNumber(path, scriptLabel);
|
||||
if (lineNum != 0) {
|
||||
scriptPath = path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
openInTextEditor(scriptPath, lineNum);
|
||||
}
|
||||
|
||||
void Editor::openInTextEditor(const QString &path, int lineNum) const {
|
||||
QString command = porymapConfig.getTextEditorGotoLine();
|
||||
if (command.isEmpty()) {
|
||||
// Open map scripts in the system's default editor.
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||
} else {
|
||||
if (command.contains("%F")) {
|
||||
if (command.contains("%L"))
|
||||
command.replace("%L", QString::number(lineNum));
|
||||
command.replace("%F", path);
|
||||
} else {
|
||||
command += ' ' + path;
|
||||
}
|
||||
startDetachedProcess(command);
|
||||
}
|
||||
}
|
||||
|
||||
void Editor::openProjectInTextEditor() const {
|
||||
QString command = porymapConfig.getTextEditorOpenFolder();
|
||||
if (command.contains("%D"))
|
||||
command.replace("%D", project->root);
|
||||
else
|
||||
command += ' ' + project->root;
|
||||
startDetachedProcess(command);
|
||||
}
|
||||
|
||||
bool Editor::startDetachedProcess(const QString &command, const QString &workingDirectory, qint64 *pid) const {
|
||||
static QProcess process;
|
||||
logInfo("Executing command: " + command);
|
||||
#ifdef Q_OS_WIN
|
||||
// On Windows, a QProcess command must be wrapped in a cmd.exe command.
|
||||
process.setProcessEnvironment(QProcessEnvironment::systemEnvironment());
|
||||
process.setProgram(process.processEnvironment().value("COMSPEC"));
|
||||
process.setNativeArguments("/c " + command);
|
||||
process.setWorkingDirectory(workingDirectory);
|
||||
#else
|
||||
QStringList arguments = ParseUtil::splitShellCommand(command);
|
||||
process.setProgram(arguments.takeFirst());
|
||||
process.setArguments(arguments);
|
||||
process.setWorkingDirectory(workingDirectory);
|
||||
#endif
|
||||
return process.startDetached(pid);
|
||||
}
|
||||
|
||||
// It doesn't seem to be possible to prevent the mousePress event
|
||||
// from triggering both event's DraggablePixmapItem and the background mousePress.
|
||||
// Since the DraggablePixmapItem's event fires first, we can set a temp
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
#include "draggablepixmapitem.h"
|
||||
#include "editcommands.h"
|
||||
#include "flowlayout.h"
|
||||
#include "shortcut.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QDirIterator>
|
||||
#include <QStandardItemModel>
|
||||
#include <QShortcut>
|
||||
#include <QSpinBox>
|
||||
#include <QTextEdit>
|
||||
#include <QSpacerItem>
|
||||
@@ -29,7 +29,6 @@
|
||||
#include <QDialogButtonBox>
|
||||
#include <QScroller>
|
||||
#include <math.h>
|
||||
#include <QProcess>
|
||||
#include <QSysInfo>
|
||||
#include <QDesktopServices>
|
||||
#include <QTransform>
|
||||
@@ -41,6 +40,7 @@
|
||||
MainWindow::MainWindow(QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
ui(new Ui::MainWindow),
|
||||
label_MapRulerStatus(nullptr),
|
||||
selectedObject(nullptr),
|
||||
selectedWarp(nullptr),
|
||||
selectedTrigger(nullptr),
|
||||
@@ -67,54 +67,109 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
delete label_MapRulerStatus;
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void MainWindow::setWindowDisabled(bool disabled) {
|
||||
for (auto *child : findChildren<QWidget *>(QString(), Qt::FindDirectChildrenOnly)) {
|
||||
bool disableThis = disabled;
|
||||
if (child->objectName() == "menuBar") {
|
||||
// disable all but the menuFile and menuHelp
|
||||
for (auto *menu : child->findChildren<QWidget *>(QString(), Qt::FindDirectChildrenOnly)) {
|
||||
disableThis = disabled;
|
||||
if (menu->objectName() == "menuFile") {
|
||||
// disable all but the action_Open_Project and action_Exit
|
||||
for (auto *action : menu->actions()) {
|
||||
action->setDisabled(disabled);
|
||||
}
|
||||
ui->action_Open_Project->setDisabled(false);
|
||||
ui->action_Exit->setDisabled(false);
|
||||
disableThis = false;
|
||||
}
|
||||
menu->setDisabled(disableThis);
|
||||
}
|
||||
child->findChild<QWidget *>("menuHelp")->setDisabled(false);
|
||||
disableThis = false;
|
||||
}
|
||||
child->setDisabled(disableThis);
|
||||
}
|
||||
for (auto action : findChildren<QAction *>())
|
||||
action->setDisabled(disabled);
|
||||
for (auto child : findChildren<QWidget *>(QString(), Qt::FindDirectChildrenOnly))
|
||||
child->setDisabled(disabled);
|
||||
for (auto menu : ui->menuBar->findChildren<QMenu *>(QString(), Qt::FindDirectChildrenOnly))
|
||||
menu->setDisabled(disabled);
|
||||
ui->menuBar->setDisabled(false);
|
||||
ui->menuFile->setDisabled(false);
|
||||
ui->action_Open_Project->setDisabled(false);
|
||||
ui->action_Exit->setDisabled(false);
|
||||
ui->menuHelp->setDisabled(false);
|
||||
ui->actionAbout_Porymap->setDisabled(false);
|
||||
if (!disabled)
|
||||
togglePreferenceSpecificUi();
|
||||
}
|
||||
|
||||
void MainWindow::initWindow() {
|
||||
porymapConfig.load();
|
||||
this->initCustomUI();
|
||||
this->initExtraSignals();
|
||||
this->initExtraShortcuts();
|
||||
this->initEditor();
|
||||
this->initMiscHeapObjects();
|
||||
this->initMapSortOrder();
|
||||
this->initShortcuts();
|
||||
this->restoreWindowState();
|
||||
|
||||
setWindowDisabled(true);
|
||||
}
|
||||
|
||||
void MainWindow::initShortcuts() {
|
||||
initExtraShortcuts();
|
||||
|
||||
shortcutsConfig.load();
|
||||
shortcutsConfig.setDefaultShortcuts(shortcutableObjects());
|
||||
applyUserShortcuts();
|
||||
}
|
||||
|
||||
void MainWindow::initExtraShortcuts() {
|
||||
new QShortcut(QKeySequence("Ctrl+0"), this, SLOT(resetMapViewScale()));
|
||||
new QShortcut(QKeySequence("Ctrl+G"), ui->checkBox_ToggleGrid, SLOT(toggle()));
|
||||
new QShortcut(QKeySequence("Ctrl+D"), this, SLOT(duplicate()));
|
||||
new QShortcut(QKeySequence::Delete, this, SLOT(on_toolButton_deleteObject_clicked()));
|
||||
new QShortcut(QKeySequence("Backspace"), this, SLOT(on_toolButton_deleteObject_clicked()));
|
||||
ui->actionZoom_In->setShortcuts({QKeySequence("Ctrl++"), QKeySequence("Ctrl+=")});
|
||||
ui->actionZoom_In->setShortcuts({ui->actionZoom_In->shortcut(), QKeySequence("Ctrl+=")});
|
||||
|
||||
auto *shortcutReset_Zoom = new Shortcut(QKeySequence("Ctrl+0"), this, SLOT(resetMapViewScale()));
|
||||
shortcutReset_Zoom->setObjectName("shortcutZoom_Reset");
|
||||
shortcutReset_Zoom->setWhatsThis("Zoom Reset");
|
||||
|
||||
auto *shortcutToggle_Grid = new Shortcut(QKeySequence("Ctrl+G"), ui->checkBox_ToggleGrid, SLOT(toggle()));
|
||||
shortcutToggle_Grid->setObjectName("shortcutToggle_Grid");
|
||||
shortcutToggle_Grid->setWhatsThis("Toggle Grid");
|
||||
|
||||
auto *shortcutDuplicate_Events = new Shortcut(QKeySequence("Ctrl+D"), this, SLOT(duplicate()));
|
||||
shortcutDuplicate_Events->setObjectName("shortcutDuplicate_Events");
|
||||
shortcutDuplicate_Events->setWhatsThis("Duplicate Selected Event(s)");
|
||||
|
||||
auto *shortcutDelete_Object = new Shortcut(
|
||||
{QKeySequence("Del"), QKeySequence("Backspace")}, this, SLOT(on_toolButton_deleteObject_clicked()));
|
||||
shortcutDelete_Object->setObjectName("shortcutDelete_Object");
|
||||
shortcutDelete_Object->setWhatsThis("Delete Selected Event(s)");
|
||||
|
||||
auto *shortcutToggle_Border = new Shortcut(QKeySequence(), ui->checkBox_ToggleBorder, SLOT(toggle()));
|
||||
shortcutToggle_Border->setObjectName("shortcutToggle_Border");
|
||||
shortcutToggle_Border->setWhatsThis("Toggle Border");
|
||||
|
||||
auto *shortcutToggle_Smart_Paths = new Shortcut(QKeySequence(), ui->checkBox_smartPaths, SLOT(toggle()));
|
||||
shortcutToggle_Smart_Paths->setObjectName("shortcutToggle_Smart_Paths");
|
||||
shortcutToggle_Smart_Paths->setWhatsThis("Toggle Smart Paths");
|
||||
|
||||
auto *shortcutExpand_All = new Shortcut(QKeySequence(), this, SLOT(on_toolButton_ExpandAll_clicked()));
|
||||
shortcutExpand_All->setObjectName("shortcutExpand_All");
|
||||
shortcutExpand_All->setWhatsThis("Map List: Expand all folders");
|
||||
|
||||
auto *shortcutCollapse_All = new Shortcut(QKeySequence(), this, SLOT(on_toolButton_CollapseAll_clicked()));
|
||||
shortcutCollapse_All->setObjectName("shortcutCollapse_All");
|
||||
shortcutCollapse_All->setWhatsThis("Map List: Collapse all folders");
|
||||
|
||||
auto *shortcut_Open_Scripts = new Shortcut(QKeySequence(), ui->toolButton_Open_Scripts, SLOT(click()));
|
||||
shortcut_Open_Scripts->setObjectName("shortcut_Open_Scripts");
|
||||
shortcut_Open_Scripts->setWhatsThis("Open Map Scripts");
|
||||
}
|
||||
|
||||
QObjectList MainWindow::shortcutableObjects() const {
|
||||
QObjectList shortcutable_objects;
|
||||
|
||||
for (auto *action : findChildren<QAction *>())
|
||||
if (!action->objectName().isEmpty())
|
||||
shortcutable_objects.append(qobject_cast<QObject *>(action));
|
||||
for (auto *shortcut : findChildren<Shortcut *>())
|
||||
if (!shortcut->objectName().isEmpty())
|
||||
shortcutable_objects.append(qobject_cast<QObject *>(shortcut));
|
||||
|
||||
return shortcutable_objects;
|
||||
}
|
||||
|
||||
void MainWindow::applyUserShortcuts() {
|
||||
for (auto *action : findChildren<QAction *>())
|
||||
if (!action->objectName().isEmpty())
|
||||
action->setShortcuts(shortcutsConfig.userShortcuts(action));
|
||||
for (auto *shortcut : findChildren<Shortcut *>())
|
||||
if (!shortcut->objectName().isEmpty())
|
||||
shortcut->setKeys(shortcutsConfig.userShortcuts(shortcut));
|
||||
}
|
||||
|
||||
void MainWindow::initCustomUI() {
|
||||
@@ -158,6 +213,17 @@ void MainWindow::initExtraSignals() {
|
||||
}
|
||||
delete ui->frame_mapTools->layout();
|
||||
ui->frame_mapTools->setLayout(flowLayout);
|
||||
|
||||
// Floating QLabel tool-window that displays over the map when the ruler is active
|
||||
label_MapRulerStatus = new QLabel(ui->graphicsView_Map);
|
||||
label_MapRulerStatus->setObjectName("label_MapRulerStatus");
|
||||
label_MapRulerStatus->setWindowFlags(Qt::Tool | Qt::CustomizeWindowHint | Qt::FramelessWindowHint);
|
||||
label_MapRulerStatus->setFrameShape(QFrame::Box);
|
||||
label_MapRulerStatus->setMargin(3);
|
||||
label_MapRulerStatus->setPalette(palette());
|
||||
label_MapRulerStatus->setAlignment(Qt::AlignCenter);
|
||||
label_MapRulerStatus->setTextFormat(Qt::PlainText);
|
||||
label_MapRulerStatus->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
}
|
||||
|
||||
void MainWindow::initEditor() {
|
||||
@@ -168,13 +234,18 @@ void MainWindow::initEditor() {
|
||||
connect(this->editor, SIGNAL(warpEventDoubleClicked(QString,QString)), this, SLOT(openWarpMap(QString,QString)));
|
||||
connect(this->editor, SIGNAL(currentMetatilesSelectionChanged()), this, SLOT(currentMetatilesSelectionChanged()));
|
||||
connect(this->editor, SIGNAL(wildMonDataChanged()), this, SLOT(onWildMonDataChanged()));
|
||||
connect(this->editor, &Editor::mapRulerStatusChanged, this, &MainWindow::onMapRulerStatusChanged);
|
||||
connect(ui->toolButton_Open_Scripts, &QToolButton::pressed, this->editor, &Editor::openMapScripts);
|
||||
connect(ui->actionOpen_Project_in_Text_Editor, &QAction::triggered, this->editor, &Editor::openProjectInTextEditor);
|
||||
|
||||
this->loadUserSettings();
|
||||
|
||||
undoAction = editor->editGroup.createUndoAction(this, tr("&Undo"));
|
||||
undoAction->setObjectName("action_Undo");
|
||||
undoAction->setShortcut(QKeySequence("Ctrl+Z"));
|
||||
|
||||
redoAction = editor->editGroup.createRedoAction(this, tr("&Redo"));
|
||||
redoAction->setObjectName("action_Redo");
|
||||
redoAction->setShortcuts({QKeySequence("Ctrl+Y"), QKeySequence("Ctrl+Shift+Z")});
|
||||
|
||||
ui->menuEdit->addAction(undoAction);
|
||||
@@ -185,7 +256,8 @@ void MainWindow::initEditor() {
|
||||
undoView->setAttribute(Qt::WA_QuitOnClose, false);
|
||||
|
||||
// Show the EditHistory dialog with Ctrl+E
|
||||
QAction *showHistory = new QAction("Show Edit History...");
|
||||
QAction *showHistory = new QAction("Show Edit History...", this);
|
||||
showHistory->setObjectName("action_ShowEditHistory");
|
||||
showHistory->setShortcut(QKeySequence("Ctrl+E"));
|
||||
connect(showHistory, &QAction::triggered, [undoView](){ undoView->show(); });
|
||||
|
||||
@@ -229,7 +301,7 @@ void MainWindow::initMapSortOrder() {
|
||||
mapSortOrderActionGroup->addAction(ui->actionSort_by_Area);
|
||||
mapSortOrderActionGroup->addAction(ui->actionSort_by_Layout);
|
||||
|
||||
connect(ui->toolButton_MapSortOrder, &QToolButton::triggered, this, &MainWindow::mapSortOrder_changed);
|
||||
connect(mapSortOrderActionGroup, &QActionGroup::triggered, this, &MainWindow::mapSortOrder_changed);
|
||||
|
||||
QAction* sortOrder = ui->toolButton_MapSortOrder->menu()->actions()[mapSortOrder];
|
||||
ui->toolButton_MapSortOrder->setIcon(sortOrder->icon());
|
||||
@@ -1305,16 +1377,6 @@ void MainWindow::duplicate() {
|
||||
editor->duplicateSelectedEvents();
|
||||
}
|
||||
|
||||
// Open current map scripts in system default editor for .inc files
|
||||
void MainWindow::openInTextEditor() {
|
||||
bool usePoryscript = projectConfig.getUsePoryScript();
|
||||
QString path = QDir::cleanPath("file://" + editor->project->root + QDir::separator() + "data/maps/" + editor->map->name + "/scripts");
|
||||
|
||||
// Try opening scripts file, if opening .pory failed try again with .inc
|
||||
if (!QDesktopServices::openUrl(QUrl(path + editor->project->getScriptFileExtension(usePoryscript))) && usePoryscript)
|
||||
QDesktopServices::openUrl(QUrl(path + editor->project->getScriptFileExtension(false)));
|
||||
}
|
||||
|
||||
void MainWindow::on_action_Save_triggered() {
|
||||
editor->save();
|
||||
updateMapList();
|
||||
@@ -1415,6 +1477,48 @@ void MainWindow::on_actionUse_Poryscript_triggered(bool checked)
|
||||
projectConfig.setUsePoryScript(checked);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionEdit_Shortcuts_triggered()
|
||||
{
|
||||
if (!shortcutsEditor)
|
||||
initShortcutsEditor();
|
||||
|
||||
if (shortcutsEditor->isHidden())
|
||||
shortcutsEditor->show();
|
||||
else if (shortcutsEditor->isMinimized())
|
||||
shortcutsEditor->showNormal();
|
||||
else
|
||||
shortcutsEditor->activateWindow();
|
||||
}
|
||||
|
||||
void MainWindow::initShortcutsEditor() {
|
||||
shortcutsEditor = new ShortcutsEditor(this);
|
||||
connect(shortcutsEditor, &ShortcutsEditor::shortcutsSaved,
|
||||
this, &MainWindow::applyUserShortcuts);
|
||||
connect(shortcutsEditor, &QObject::destroyed, [=](QObject *) { shortcutsEditor = nullptr; });
|
||||
|
||||
connectSubEditorsToShortcutsEditor();
|
||||
|
||||
shortcutsEditor->setShortcutableObjects(shortcutableObjects());
|
||||
}
|
||||
|
||||
void MainWindow::connectSubEditorsToShortcutsEditor() {
|
||||
/* Initialize sub-editors so that their children are added to MainWindow's object tree and will
|
||||
* be returned by shortcutableObjects() to be passed to ShortcutsEditor. */
|
||||
if (!tilesetEditor)
|
||||
initTilesetEditor();
|
||||
connect(shortcutsEditor, &ShortcutsEditor::shortcutsSaved,
|
||||
tilesetEditor, &TilesetEditor::applyUserShortcuts);
|
||||
|
||||
// TODO: Remove this check when the region map editor supports pokefirered.
|
||||
if (projectConfig.getBaseGameVersion() != BaseGameVersion::pokefirered) {
|
||||
if (!regionMapEditor)
|
||||
initRegionMapEditor();
|
||||
if (regionMapEditor)
|
||||
connect(shortcutsEditor, &ShortcutsEditor::shortcutsSaved,
|
||||
regionMapEditor, &RegionMapEditor::applyUserShortcuts);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_actionPencil_triggered()
|
||||
{
|
||||
on_toolButton_Paint_clicked();
|
||||
@@ -1550,6 +1654,10 @@ void MainWindow::updateSelectedObjects() {
|
||||
}
|
||||
}
|
||||
|
||||
for (auto *button : openScriptButtons)
|
||||
delete button;
|
||||
openScriptButtons.clear();
|
||||
|
||||
QMap<QString, int> event_obj_gfx_constants = editor->project->getEventObjGfxConstants();
|
||||
|
||||
QList<EventPropertiesFrame *> frames;
|
||||
@@ -1809,6 +1917,7 @@ void MainWindow::updateSelectedObjects() {
|
||||
"normal movement behavior actions.");
|
||||
combo->setMinimumContentsLength(4);
|
||||
} else if (key == "script_label") {
|
||||
combo->addItems(editor->map->eventScriptLabels());
|
||||
combo->setToolTip("The script which is executed with this event.");
|
||||
} else if (key == "trainer_type") {
|
||||
combo->addItems(*editor->project->trainerTypes);
|
||||
@@ -1871,7 +1980,26 @@ void MainWindow::updateSelectedObjects() {
|
||||
} else {
|
||||
combo->setCurrentText(value);
|
||||
|
||||
fl->addRow(new QLabel(field_labels[key], widget), combo);
|
||||
if (key == "script_label") {
|
||||
// Add button next to combo which opens combo's current script.
|
||||
auto *hl = new QHBoxLayout();
|
||||
hl->setSpacing(3);
|
||||
auto *openScriptButton = new QToolButton(widget);
|
||||
openScriptButtons << openScriptButton;
|
||||
openScriptButton->setFixedSize(combo->height(), combo->height());
|
||||
openScriptButton->setIcon(QFileIconProvider().icon(QFileIconProvider::File));
|
||||
openScriptButton->setToolTip("Go to this script definition in text editor.");
|
||||
connect(openScriptButton, &QToolButton::clicked,
|
||||
[this, combo]() { this->editor->openScript(combo->currentText()); });
|
||||
hl->addWidget(combo);
|
||||
hl->addWidget(openScriptButton);
|
||||
fl->addRow(new QLabel(field_labels[key], widget), hl);
|
||||
if (porymapConfig.getTextEditorGotoLine().isEmpty())
|
||||
openScriptButton->hide();
|
||||
} else {
|
||||
fl->addRow(new QLabel(field_labels[key], widget), combo);
|
||||
}
|
||||
|
||||
widget->setLayout(fl);
|
||||
frame->layout()->addWidget(widget);
|
||||
|
||||
@@ -2106,11 +2234,6 @@ void MainWindow::on_toolButton_deleteObject_clicked() {
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_toolButton_Open_Scripts_clicked()
|
||||
{
|
||||
openInTextEditor();
|
||||
}
|
||||
|
||||
void MainWindow::on_toolButton_Paint_clicked()
|
||||
{
|
||||
if (ui->mainTabBar->currentIndex() == 0)
|
||||
@@ -2301,6 +2424,23 @@ void MainWindow::onWildMonDataChanged() {
|
||||
projectHasUnsavedChanges = true;
|
||||
}
|
||||
|
||||
void MainWindow::onMapRulerStatusChanged(const QString &status) {
|
||||
if (status.isEmpty()) {
|
||||
label_MapRulerStatus->hide();
|
||||
} else if (label_MapRulerStatus->parentWidget()) {
|
||||
label_MapRulerStatus->setText(status);
|
||||
label_MapRulerStatus->adjustSize();
|
||||
label_MapRulerStatus->show();
|
||||
label_MapRulerStatus->move(label_MapRulerStatus->parentWidget()->mapToGlobal(QPoint(6, 6)));
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::moveEvent(QMoveEvent *event) {
|
||||
QMainWindow::moveEvent(event);
|
||||
if (label_MapRulerStatus->isVisible() && label_MapRulerStatus->parentWidget())
|
||||
label_MapRulerStatus->move(label_MapRulerStatus->parentWidget()->mapToGlobal(QPoint(6, 6)));
|
||||
}
|
||||
|
||||
void MainWindow::on_action_Export_Map_Image_triggered() {
|
||||
showExportMapImageWindow(false);
|
||||
}
|
||||
@@ -2358,6 +2498,10 @@ void MainWindow::on_pushButton_NewWildMonGroup_clicked() {
|
||||
editor->addNewWildMonGroup(this);
|
||||
}
|
||||
|
||||
void MainWindow::on_pushButton_DeleteWildMonGroup_clicked() {
|
||||
editor->deleteWildMonGroup();
|
||||
}
|
||||
|
||||
void MainWindow::on_pushButton_ConfigureEncountersJSON_clicked() {
|
||||
editor->configureEncounterJSON(this);
|
||||
}
|
||||
@@ -2503,9 +2647,7 @@ void MainWindow::on_checkBox_ToggleBorder_stateChanged(int selected)
|
||||
void MainWindow::on_actionTileset_Editor_triggered()
|
||||
{
|
||||
if (!this->tilesetEditor) {
|
||||
this->tilesetEditor = new TilesetEditor(this->editor->project, this->editor->map, this);
|
||||
connect(this->tilesetEditor, SIGNAL(tilesetsSaved(QString, QString)), this, SLOT(onTilesetsSaved(QString, QString)));
|
||||
connect(this->tilesetEditor, &QObject::destroyed, [=](QObject *) { this->tilesetEditor = nullptr; });
|
||||
initTilesetEditor();
|
||||
}
|
||||
|
||||
if (!this->tilesetEditor->isVisible()) {
|
||||
@@ -2518,6 +2660,12 @@ void MainWindow::on_actionTileset_Editor_triggered()
|
||||
this->tilesetEditor->selectMetatile(this->editor->metatile_selector_item->getSelectedMetatiles()->at(0));
|
||||
}
|
||||
|
||||
void MainWindow::initTilesetEditor() {
|
||||
this->tilesetEditor = new TilesetEditor(this->editor->project, this->editor->map, this);
|
||||
connect(this->tilesetEditor, SIGNAL(tilesetsSaved(QString, QString)), this, SLOT(onTilesetsSaved(QString, QString)));
|
||||
connect(this->tilesetEditor, &QObject::destroyed, [=](QObject *) { this->tilesetEditor = nullptr; });
|
||||
}
|
||||
|
||||
void MainWindow::on_toolButton_ExpandAll_clicked()
|
||||
{
|
||||
if (ui->mapList) {
|
||||
@@ -2539,38 +2687,40 @@ void MainWindow::on_actionAbout_Porymap_triggered()
|
||||
window->show();
|
||||
}
|
||||
|
||||
void MainWindow::on_actionThemes_triggered()
|
||||
{
|
||||
QStringList themes;
|
||||
QRegularExpression re(":/themes/([A-z0-9_-]+).qss");
|
||||
themes.append("default");
|
||||
QDirIterator it(":/themes", QDirIterator::Subdirectories);
|
||||
while (it.hasNext()) {
|
||||
QString themeName = re.match(it.next()).captured(1);
|
||||
themes.append(themeName);
|
||||
void MainWindow::on_actionEdit_Preferences_triggered() {
|
||||
if (!preferenceEditor) {
|
||||
preferenceEditor = new PreferenceEditor(this);
|
||||
connect(preferenceEditor, &PreferenceEditor::themeChanged,
|
||||
this, &MainWindow::setTheme);
|
||||
connect(preferenceEditor, &PreferenceEditor::themeChanged,
|
||||
editor, &Editor::maskNonVisibleConnectionTiles);
|
||||
connect(preferenceEditor, &PreferenceEditor::preferencesSaved,
|
||||
this, &MainWindow::togglePreferenceSpecificUi);
|
||||
connect(preferenceEditor, &QObject::destroyed, [=](QObject *) { preferenceEditor = nullptr; });
|
||||
}
|
||||
|
||||
QDialog themeSelectorWindow(this);
|
||||
QFormLayout form(&themeSelectorWindow);
|
||||
if (!preferenceEditor->isVisible()) {
|
||||
preferenceEditor->show();
|
||||
} else if (preferenceEditor->isMinimized()) {
|
||||
preferenceEditor->showNormal();
|
||||
} else {
|
||||
preferenceEditor->activateWindow();
|
||||
}
|
||||
}
|
||||
|
||||
NoScrollComboBox *themeSelector = new NoScrollComboBox();
|
||||
themeSelector->addItems(themes);
|
||||
themeSelector->setCurrentText(porymapConfig.getTheme());
|
||||
form.addRow(new QLabel("Themes"), themeSelector);
|
||||
void MainWindow::togglePreferenceSpecificUi() {
|
||||
if (porymapConfig.getTextEditorGotoLine().isEmpty()) {
|
||||
for (auto *button : openScriptButtons)
|
||||
button->hide();
|
||||
} else {
|
||||
for (auto *button : openScriptButtons)
|
||||
button->show();
|
||||
}
|
||||
|
||||
QDialogButtonBox buttonBox(QDialogButtonBox::Apply | QDialogButtonBox::Close, Qt::Horizontal, &themeSelectorWindow);
|
||||
form.addRow(&buttonBox);
|
||||
connect(&buttonBox, &QDialogButtonBox::clicked, [&buttonBox, themeSelector, this](QAbstractButton *button){
|
||||
if (button == buttonBox.button(QDialogButtonBox::Apply)) {
|
||||
QString theme = themeSelector->currentText();
|
||||
porymapConfig.setTheme(theme);
|
||||
this->setTheme(theme);
|
||||
editor->maskNonVisibleConnectionTiles();
|
||||
}
|
||||
});
|
||||
connect(&buttonBox, SIGNAL(rejected()), &themeSelectorWindow, SLOT(reject()));
|
||||
|
||||
themeSelectorWindow.exec();
|
||||
if (porymapConfig.getTextEditorOpenFolder().isEmpty())
|
||||
ui->actionOpen_Project_in_Text_Editor->setEnabled(false);
|
||||
else
|
||||
ui->actionOpen_Project_in_Text_Editor->setEnabled(true);
|
||||
}
|
||||
|
||||
void MainWindow::on_pushButton_AddCustomHeaderField_clicked()
|
||||
@@ -2632,20 +2782,9 @@ void MainWindow::on_horizontalSlider_MetatileZoom_valueChanged(int value) {
|
||||
|
||||
void MainWindow::on_actionRegion_Map_Editor_triggered() {
|
||||
if (!this->regionMapEditor) {
|
||||
this->regionMapEditor = new RegionMapEditor(this, this->editor->project);
|
||||
bool success = this->regionMapEditor->loadRegionMapData()
|
||||
&& this->regionMapEditor->loadCityMaps();
|
||||
if (!success) {
|
||||
delete this->regionMapEditor;
|
||||
this->regionMapEditor = nullptr;
|
||||
QMessageBox msgBox(this);
|
||||
QString errorMsg = QString("There was an error opening the region map data. Please see %1 for full error details.\n\n%3")
|
||||
.arg(getLogPath())
|
||||
.arg(getMostRecentError());
|
||||
msgBox.critical(nullptr, "Error Opening Region Map Editor", errorMsg);
|
||||
if (!initRegionMapEditor()) {
|
||||
return;
|
||||
}
|
||||
connect(this->regionMapEditor, &QObject::destroyed, [=](QObject *) { this->regionMapEditor = nullptr; });
|
||||
}
|
||||
|
||||
if (!this->regionMapEditor->isVisible()) {
|
||||
@@ -2657,6 +2796,26 @@ void MainWindow::on_actionRegion_Map_Editor_triggered() {
|
||||
}
|
||||
}
|
||||
|
||||
bool MainWindow::initRegionMapEditor() {
|
||||
this->regionMapEditor = new RegionMapEditor(this, this->editor->project);
|
||||
bool success = this->regionMapEditor->loadRegionMapData()
|
||||
&& this->regionMapEditor->loadCityMaps();
|
||||
if (!success) {
|
||||
delete this->regionMapEditor;
|
||||
this->regionMapEditor = nullptr;
|
||||
QMessageBox msgBox(this);
|
||||
QString errorMsg = QString("There was an error opening the region map data. Please see %1 for full error details.\n\n%3")
|
||||
.arg(getLogPath())
|
||||
.arg(getMostRecentError());
|
||||
msgBox.critical(nullptr, "Error Opening Region Map Editor", errorMsg);
|
||||
|
||||
return false;
|
||||
}
|
||||
connect(this->regionMapEditor, &QObject::destroyed, [=](QObject *) { this->regionMapEditor = nullptr; });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MainWindow::closeSupplementaryWindows() {
|
||||
if (this->tilesetEditor)
|
||||
delete this->tilesetEditor;
|
||||
@@ -2666,6 +2825,8 @@ void MainWindow::closeSupplementaryWindows() {
|
||||
delete this->mapImageExporter;
|
||||
if (this->newmapprompt)
|
||||
delete this->newmapprompt;
|
||||
if (this->shortcutsEditor)
|
||||
delete this->shortcutsEditor;
|
||||
}
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent *event) {
|
||||
@@ -2694,6 +2855,8 @@ void MainWindow::closeEvent(QCloseEvent *event) {
|
||||
this->ui->splitter_main->saveState()
|
||||
);
|
||||
porymapConfig.save();
|
||||
projectConfig.save();
|
||||
shortcutsConfig.save();
|
||||
|
||||
QMainWindow::closeEvent(event);
|
||||
}
|
||||
|
||||
@@ -1361,9 +1361,11 @@ void Project::saveMap(Map *map) {
|
||||
mapObj["requires_flash"] = map->requiresFlash.toInt() > 0 || map->requiresFlash == "TRUE";
|
||||
mapObj["weather"] = map->weather;
|
||||
mapObj["map_type"] = map->type;
|
||||
mapObj["allow_cycling"] = map->allowBiking.toInt() > 0 || map->allowBiking == "TRUE";
|
||||
mapObj["allow_escaping"] = map->allowEscapeRope.toInt() > 0 || map->allowEscapeRope == "TRUE";
|
||||
mapObj["allow_running"] = map->allowRunning.toInt() > 0 || map->allowRunning == "TRUE";
|
||||
if (projectConfig.getBaseGameVersion() != BaseGameVersion::pokeruby) {
|
||||
mapObj["allow_cycling"] = map->allowBiking.toInt() > 0 || map->allowBiking == "TRUE";
|
||||
mapObj["allow_escaping"] = map->allowEscapeRope.toInt() > 0 || map->allowEscapeRope == "TRUE";
|
||||
mapObj["allow_running"] = map->allowRunning.toInt() > 0 || map->allowRunning == "TRUE";
|
||||
}
|
||||
mapObj["show_map_name"] = map->show_location.toInt() > 0 || map->show_location == "TRUE";
|
||||
if (projectConfig.getFloorNumberEnabled()) {
|
||||
mapObj["floor_number"] = map->floorNumber;
|
||||
@@ -2408,7 +2410,7 @@ QString Project::fixGraphicPath(QString path) {
|
||||
return path;
|
||||
}
|
||||
|
||||
QString Project::getScriptFileExtension(bool usePoryScript) {
|
||||
QString Project::getScriptFileExtension(bool usePoryScript) const {
|
||||
if(usePoryScript) {
|
||||
return ".pory";
|
||||
} else {
|
||||
@@ -2416,13 +2418,50 @@ QString Project::getScriptFileExtension(bool usePoryScript) {
|
||||
}
|
||||
}
|
||||
|
||||
QString Project::getScriptDefaultString(bool usePoryScript, QString mapName) {
|
||||
QString Project::getScriptDefaultString(bool usePoryScript, QString mapName) const {
|
||||
if(usePoryScript)
|
||||
return QString("mapscripts %1_MapScripts {}").arg(mapName);
|
||||
else
|
||||
return QString("%1_MapScripts::\n\t.byte 0\n").arg(mapName);
|
||||
}
|
||||
|
||||
QString Project::getMapScriptsFilePath(const QString &mapName) const {
|
||||
const bool usePoryscript = projectConfig.getUsePoryScript();
|
||||
auto path = QDir::cleanPath(root + "/data/maps/" + mapName + "/scripts");
|
||||
auto extension = getScriptFileExtension(usePoryscript);
|
||||
if (usePoryscript && !QFile::exists(path + extension))
|
||||
extension = getScriptFileExtension(false);
|
||||
path += extension;
|
||||
return path;
|
||||
}
|
||||
|
||||
QStringList Project::getEventScriptsFilePaths() const {
|
||||
QStringList filePaths(QDir::cleanPath(root + "/data/event_scripts.s"));
|
||||
const QString scriptsDir = QDir::cleanPath(root + "/data/scripts");
|
||||
const QString mapsDir = QDir::cleanPath(root + "/data/maps");
|
||||
const bool usePoryscript = projectConfig.getUsePoryScript();
|
||||
|
||||
if (usePoryscript) {
|
||||
QDirIterator it_pory_shared(scriptsDir, {"*.pory"}, QDir::Files);
|
||||
while (it_pory_shared.hasNext())
|
||||
filePaths << it_pory_shared.next();
|
||||
|
||||
QDirIterator it_pory_maps(mapsDir, {"scripts.pory"}, QDir::Files, QDirIterator::Subdirectories);
|
||||
while (it_pory_maps.hasNext())
|
||||
filePaths << it_pory_maps.next();
|
||||
}
|
||||
|
||||
QDirIterator it_inc_shared(scriptsDir, {"*.inc"}, QDir::Files);
|
||||
while (it_inc_shared.hasNext())
|
||||
filePaths << it_inc_shared.next();
|
||||
|
||||
QDirIterator it_inc_maps(mapsDir, {"scripts.inc"}, QDir::Files, QDirIterator::Subdirectories);
|
||||
while (it_inc_maps.hasNext())
|
||||
filePaths << it_inc_maps.next();
|
||||
|
||||
return filePaths;
|
||||
}
|
||||
|
||||
void Project::loadEventPixmaps(QList<Event*> objects) {
|
||||
bool needs_update = false;
|
||||
for (Event *object : objects) {
|
||||
|
||||
@@ -21,3 +21,10 @@ void GraphicsView::drawForeground(QPainter *painter, const QRectF&) {
|
||||
item->render(painter);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsView::moveEvent(QMoveEvent *event) {
|
||||
QGraphicsView::moveEvent(event);
|
||||
QLabel *label_MapRulerStatus = findChild<QLabel *>("label_MapRulerStatus", Qt::FindDirectChildrenOnly);
|
||||
if (label_MapRulerStatus && label_MapRulerStatus->isVisible())
|
||||
label_MapRulerStatus->move(mapToGlobal(QPoint(6, 6)));
|
||||
}
|
||||
|
||||
@@ -1,28 +1,34 @@
|
||||
#include "mapruler.h"
|
||||
#include "metatile.h"
|
||||
|
||||
#include <QGraphicsObject>
|
||||
#include <QGraphicsSceneEvent>
|
||||
#include <QPainter>
|
||||
#include <QColor>
|
||||
#include <QToolTip>
|
||||
|
||||
int MapRuler::thickness = 3;
|
||||
#include <QVector>
|
||||
|
||||
|
||||
void MapRuler::init() {
|
||||
setVisible(false);
|
||||
setPoints(QPoint(), QPoint());
|
||||
anchored = false;
|
||||
locked = false;
|
||||
statusMessage = QString("Ruler: 0");
|
||||
xRuler = QRect();
|
||||
yRuler = QRect();
|
||||
cornerTick = QLine();
|
||||
MapRuler::MapRuler(int thickness, QColor innerColor, QColor borderColor) :
|
||||
/* The logical representation of rectangles are always one less than
|
||||
* the rendered shape, so we subtract 1 from thickness. */
|
||||
thickness(thickness - 1),
|
||||
half_thickness(qreal(thickness - 1) / 2.0),
|
||||
innerColor(innerColor),
|
||||
borderColor(borderColor),
|
||||
mapSize(QSize()),
|
||||
xRuler(QRectF()),
|
||||
yRuler(QRectF()),
|
||||
cornerTick(QLineF()),
|
||||
anchored(false),
|
||||
locked(false)
|
||||
{
|
||||
connect(this, &QGraphicsObject::enabledChanged, [this]() {
|
||||
if (!isEnabled() && anchored)
|
||||
reset();
|
||||
});
|
||||
}
|
||||
|
||||
QRectF MapRuler::boundingRect() const {
|
||||
return QRectF(-thickness, -thickness, pixWidth() + thickness * 2, pixHeight() + thickness * 2);
|
||||
return QRectF(-(half_thickness + 1), -(half_thickness + 1), pixWidth() + thickness + 2, pixHeight() + thickness + 2);
|
||||
}
|
||||
|
||||
QPainterPath MapRuler::shape() const {
|
||||
@@ -31,10 +37,12 @@ QPainterPath MapRuler::shape() const {
|
||||
ruler.addRect(xRuler);
|
||||
ruler.addRect(yRuler);
|
||||
ruler = ruler.simplified();
|
||||
for (int x = 17.5; x < pixWidth(); x += 16)
|
||||
for (int x = 16; x < pixWidth(); x += 16)
|
||||
ruler.addRect(x, xRuler.y(), 0, thickness);
|
||||
for (int y = 17.5; y < pixHeight(); y += 16)
|
||||
for (int y = 16; y < pixHeight(); y += 16)
|
||||
ruler.addRect(yRuler.x(), y, thickness, 0);
|
||||
if (deltaX() && deltaY())
|
||||
ruler.addPolygon(QVector<QPointF>({ cornerTick.p1(), cornerTick.p2() }));
|
||||
return ruler;
|
||||
}
|
||||
|
||||
@@ -42,20 +50,16 @@ void MapRuler::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidge
|
||||
painter->setPen(QPen(borderColor));
|
||||
painter->setBrush(QBrush(innerColor));
|
||||
painter->drawPath(shape());
|
||||
if (deltaX() && deltaY())
|
||||
painter->drawLine(cornerTick);
|
||||
}
|
||||
|
||||
bool MapRuler::eventFilter(QObject*, QEvent *event) {
|
||||
bool MapRuler::eventFilter(QObject *, QEvent *event) {
|
||||
if (!isEnabled() || mapSize.isEmpty())
|
||||
return false;
|
||||
|
||||
if (event->type() == QEvent::GraphicsSceneMousePress || event->type() == QEvent::GraphicsSceneMouseMove) {
|
||||
auto mouse_event = static_cast<QGraphicsSceneMouseEvent *>(event);
|
||||
auto *mouse_event = static_cast<QGraphicsSceneMouseEvent *>(event);
|
||||
if (mouse_event->button() == Qt::RightButton || anchored) {
|
||||
mouseEvent(mouse_event);
|
||||
event->accept();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,26 +68,53 @@ bool MapRuler::eventFilter(QObject*, QEvent *event) {
|
||||
|
||||
void MapRuler::mouseEvent(QGraphicsSceneMouseEvent *event) {
|
||||
if (!anchored && event->button() == Qt::RightButton) {
|
||||
setAnchor(event->scenePos(), event->screenPos());
|
||||
setAnchor(event->scenePos());
|
||||
} else if (anchored) {
|
||||
if (event->button() == Qt::LeftButton)
|
||||
locked = !locked;
|
||||
if (event->button() == Qt::RightButton)
|
||||
endAnchor();
|
||||
reset();
|
||||
else
|
||||
setEndPos(event->scenePos(), event->screenPos());
|
||||
setEndPos(event->scenePos());
|
||||
}
|
||||
}
|
||||
|
||||
void MapRuler::setMapDimensions(const QSize &size) {
|
||||
mapSize = size;
|
||||
init();
|
||||
reset();
|
||||
}
|
||||
|
||||
void MapRuler::setEnabled(bool enabled) {
|
||||
QGraphicsItem::setEnabled(enabled);
|
||||
if (!enabled && anchored)
|
||||
endAnchor();
|
||||
void MapRuler::reset() {
|
||||
prepareGeometryChange();
|
||||
hide();
|
||||
setPoints(QPoint(), QPoint());
|
||||
xRuler = QRectF();
|
||||
yRuler = QRectF();
|
||||
cornerTick = QLineF();
|
||||
anchored = false;
|
||||
locked = false;
|
||||
emit statusChanged(QString());
|
||||
}
|
||||
|
||||
void MapRuler::setAnchor(const QPointF &scenePos) {
|
||||
QPoint pos = Metatile::coordFromPixmapCoord(scenePos);
|
||||
pos = snapToWithinBounds(pos);
|
||||
anchored = true;
|
||||
locked = false;
|
||||
setPoints(pos, pos);
|
||||
updateGeometry();
|
||||
show();
|
||||
}
|
||||
|
||||
void MapRuler::setEndPos(const QPointF &scenePos) {
|
||||
if (locked)
|
||||
return;
|
||||
QPoint pos = Metatile::coordFromPixmapCoord(scenePos);
|
||||
pos = snapToWithinBounds(pos);
|
||||
const QPoint lastEndPos = endPos();
|
||||
setP2(pos);
|
||||
if (pos != lastEndPos)
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
QPoint MapRuler::snapToWithinBounds(QPoint pos) const {
|
||||
@@ -98,80 +129,62 @@ QPoint MapRuler::snapToWithinBounds(QPoint pos) const {
|
||||
return pos;
|
||||
}
|
||||
|
||||
void MapRuler::setAnchor(const QPointF &scenePos, const QPoint &screenPos) {
|
||||
QPoint pos = Metatile::coordFromPixmapCoord(scenePos);
|
||||
pos = snapToWithinBounds(pos);
|
||||
anchored = true;
|
||||
locked = false;
|
||||
setPoints(pos, pos);
|
||||
updateGeometry();
|
||||
setVisible(true);
|
||||
showDimensions(screenPos);
|
||||
}
|
||||
|
||||
void MapRuler::endAnchor() {
|
||||
emit deactivated(endPos());
|
||||
hideDimensions();
|
||||
prepareGeometryChange();
|
||||
init();
|
||||
}
|
||||
|
||||
void MapRuler::setEndPos(const QPointF &scenePos, const QPoint &screenPos) {
|
||||
if (locked)
|
||||
return;
|
||||
QPoint pos = Metatile::coordFromPixmapCoord(scenePos);
|
||||
pos = snapToWithinBounds(pos);
|
||||
const QPoint lastEndPos = endPos();
|
||||
setP2(pos);
|
||||
if (pos != lastEndPos)
|
||||
updateGeometry();
|
||||
showDimensions(screenPos);
|
||||
}
|
||||
|
||||
void MapRuler::showDimensions(const QPoint &screenPos) const {
|
||||
// This is a hack to make the tool tip follow the cursor since it won't change position if the text is the same.
|
||||
QToolTip::showText(screenPos + QPoint(16, -8), statusMessage + ' ');
|
||||
QToolTip::showText(screenPos + QPoint(16, -8), statusMessage);
|
||||
}
|
||||
|
||||
void MapRuler::hideDimensions() const {
|
||||
QToolTip::hideText();
|
||||
}
|
||||
|
||||
void MapRuler::updateGeometry() {
|
||||
prepareGeometryChange();
|
||||
setPos(QPoint(left() * 16 + 7, top() * 16 + 7));
|
||||
setPos(QPoint(left() * 16 + 8, top() * 16 + 8));
|
||||
/* Determine what quadrant the end point is in relative to the anchor point. The anchor
|
||||
* point is the top-left corner of the metatile the ruler starts in, so a zero-length(s)
|
||||
* point is the top-left corner of the metatile the ruler starts in, so a zero-length
|
||||
* ruler is considered to be in the bottom-right quadrant from the anchor point. */
|
||||
if (deltaX() < 0 && deltaY() < 0) {
|
||||
// Top-left
|
||||
xRuler = QRect(0, pixHeight(), pixWidth() + thickness, thickness);
|
||||
yRuler = QRect(0, 0, thickness, pixHeight() + thickness);
|
||||
xRuler = QRectF(-half_thickness, pixHeight() - half_thickness, pixWidth() + thickness, thickness);
|
||||
yRuler = QRectF(-half_thickness, -half_thickness, thickness, pixHeight() + thickness);
|
||||
cornerTick = QLineF(yRuler.x() + 0.5, xRuler.y() + thickness - 0.5, yRuler.x() + thickness, xRuler.y());
|
||||
statusMessage = QString("Ruler: Left %1, Up %2").arg(width()).arg(height());
|
||||
updateStatus(Qt::TopLeftCorner);
|
||||
} else if (deltaX() < 0) {
|
||||
// Bottom-left
|
||||
xRuler = QRect(0, 0, pixWidth() + thickness, thickness);
|
||||
yRuler = QRect(0, 0, thickness, pixHeight() + thickness);
|
||||
xRuler = QRectF(-half_thickness, -half_thickness, pixWidth() + thickness, thickness);
|
||||
yRuler = QRectF(-half_thickness, -half_thickness, thickness, pixHeight() + thickness);
|
||||
cornerTick = QLineF(xRuler.x() + 0.5, xRuler.y() + 0.5, xRuler.x() + thickness, xRuler.y() + thickness);
|
||||
updateStatus(Qt::BottomLeftCorner);
|
||||
} else if (deltaY() < 0) {
|
||||
// Top-right
|
||||
xRuler = QRectF(-half_thickness, pixHeight() - half_thickness, pixWidth() + thickness, thickness);
|
||||
yRuler = QRectF(pixWidth() - half_thickness, -half_thickness, thickness, pixHeight() + thickness);
|
||||
cornerTick = QLineF(yRuler.x(), xRuler.y(), yRuler.x() + thickness - 0.5, xRuler.y() + thickness - 0.5);
|
||||
updateStatus(Qt::TopRightCorner);
|
||||
} else {
|
||||
// Bottom-right
|
||||
xRuler = QRectF(-half_thickness, -half_thickness, pixWidth() + thickness, thickness);
|
||||
yRuler = QRectF(pixWidth() - half_thickness, -half_thickness, thickness, pixHeight() + thickness);
|
||||
cornerTick = QLineF(yRuler.x(), yRuler.y() + thickness, yRuler.x() + thickness - 0.5, yRuler.y() + 0.5);
|
||||
updateStatus(Qt::BottomRightCorner);
|
||||
}
|
||||
}
|
||||
|
||||
void MapRuler::updateStatus(Qt::Corner corner) {
|
||||
QString statusMessage;
|
||||
switch (corner)
|
||||
{
|
||||
case Qt::TopLeftCorner:
|
||||
statusMessage = QString("Ruler: Left %1, Up %2\nStart(%3, %4), End(%5, %6)").arg(width()).arg(height())
|
||||
.arg(anchor().x()).arg(anchor().y()).arg(endPos().x()).arg(endPos().y());
|
||||
break;
|
||||
case Qt::BottomLeftCorner:
|
||||
statusMessage = QString("Ruler: Left %1").arg(width());
|
||||
if (deltaY())
|
||||
statusMessage += QString(", Down %1").arg(height());
|
||||
} else if (deltaY() < 0) {
|
||||
// Top-right
|
||||
xRuler = QRect(0, pixHeight(), pixWidth() + thickness, thickness);
|
||||
yRuler = QRect(pixWidth(), 0, thickness, pixHeight() + thickness);
|
||||
cornerTick = QLineF(yRuler.x(), xRuler.y(), yRuler.x() + thickness - 0.5, xRuler.y() + thickness - 0.5);
|
||||
statusMessage += QString("\nStart(%1, %2), End(%3, %4)")
|
||||
.arg(anchor().x()).arg(anchor().y()).arg(endPos().x()).arg(endPos().y());
|
||||
break;
|
||||
case Qt::TopRightCorner:
|
||||
statusMessage = QString("Ruler: ");
|
||||
if (deltaX())
|
||||
statusMessage += QString("Right %1, ").arg(width());
|
||||
statusMessage += QString("Up %1").arg(height());
|
||||
} else {
|
||||
// Bottom-right
|
||||
xRuler = QRect(0, 0, pixWidth() + thickness, thickness);
|
||||
yRuler = QRect(pixWidth(), 0, thickness, pixHeight() + thickness);
|
||||
cornerTick = QLineF(yRuler.x(), yRuler.y() + thickness, yRuler.x() + thickness - 0.5, yRuler.y() + 0.5);
|
||||
statusMessage += QString("Up %1\nStart(%2, %3), End(%4, %5)").arg(height())
|
||||
.arg(anchor().x()).arg(anchor().y()).arg(endPos().x()).arg(endPos().y());
|
||||
break;
|
||||
case Qt::BottomRightCorner:
|
||||
statusMessage = QString("Ruler: ");
|
||||
if (deltaX() || deltaY()) {
|
||||
if (deltaX())
|
||||
@@ -181,9 +194,13 @@ void MapRuler::updateGeometry() {
|
||||
statusMessage += ", ";
|
||||
statusMessage += QString("Down: %1").arg(height());
|
||||
}
|
||||
statusMessage += QString("\nStart(%1, %2), End(%3, %4)")
|
||||
.arg(anchor().x()).arg(anchor().y()).arg(endPos().x()).arg(endPos().y());
|
||||
} else {
|
||||
statusMessage += QString("0");
|
||||
statusMessage += QString("0\nStart(%1, %2)")
|
||||
.arg(anchor().x()).arg(anchor().y());
|
||||
}
|
||||
break;
|
||||
}
|
||||
emit lengthChanged();
|
||||
emit statusChanged(statusMessage);
|
||||
}
|
||||
|
||||
185
src/ui/multikeyedit.cpp
Normal file
185
src/ui/multikeyedit.cpp
Normal file
@@ -0,0 +1,185 @@
|
||||
#include "multikeyedit.h"
|
||||
|
||||
#include <QLineEdit>
|
||||
#include <QHBoxLayout>
|
||||
#include <QtEvents>
|
||||
#include <QMenu>
|
||||
#include <QAction>
|
||||
|
||||
|
||||
MultiKeyEdit::MultiKeyEdit(QWidget *parent, int fieldCount) :
|
||||
QWidget(parent),
|
||||
keySequenceEdit_vec(QVector<QKeySequenceEdit *>()),
|
||||
keySequence_list(QList<QKeySequence>())
|
||||
{
|
||||
setLayout(new QHBoxLayout(this));
|
||||
layout()->setContentsMargins(0, 0, 0, 0);
|
||||
setFieldCount(fieldCount);
|
||||
}
|
||||
|
||||
bool MultiKeyEdit::eventFilter(QObject *watched, QEvent *event) {
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
auto *watched_kse = qobject_cast<QKeySequenceEdit *>(watched);
|
||||
if (!watched_kse)
|
||||
return false;
|
||||
|
||||
auto *keyEvent = static_cast<QKeyEvent *>(event);
|
||||
if (keyEvent->key() == Qt::Key_Escape) {
|
||||
watched_kse->clearFocus();
|
||||
return true;
|
||||
} else {
|
||||
watched_kse->clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::ContextMenu) {
|
||||
auto *watched_lineEdit = qobject_cast<QLineEdit *>(watched);
|
||||
if (!watched_lineEdit)
|
||||
return false;
|
||||
|
||||
auto *contextMenuEvent = static_cast<QContextMenuEvent *>(event);
|
||||
if (contextMenuPolicy() == Qt::DefaultContextMenu) {
|
||||
showDefaultContextMenu(watched_lineEdit, contextMenuEvent->pos());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int MultiKeyEdit::fieldCount() const {
|
||||
return keySequenceEdit_vec.count();
|
||||
}
|
||||
|
||||
void MultiKeyEdit::setFieldCount(int count) {
|
||||
if (count < 1)
|
||||
count = 1;
|
||||
|
||||
while (keySequenceEdit_vec.count() < count)
|
||||
addNewKeySequenceEdit();
|
||||
|
||||
while (keySequenceEdit_vec.count() > count)
|
||||
delete keySequenceEdit_vec.takeLast();
|
||||
|
||||
alignKeySequencesLeft();
|
||||
}
|
||||
|
||||
QList<QKeySequence> MultiKeyEdit::keySequences() const {
|
||||
QList<QKeySequence> current_keySequences;
|
||||
for (auto *kse : keySequenceEdit_vec)
|
||||
if (!kse->keySequence().isEmpty())
|
||||
current_keySequences.append(kse->keySequence());
|
||||
return current_keySequences;
|
||||
}
|
||||
|
||||
bool MultiKeyEdit::removeOne(const QKeySequence &keySequence) {
|
||||
for (auto *keySequenceEdit : keySequenceEdit_vec) {
|
||||
if (keySequenceEdit->keySequence() == keySequence) {
|
||||
keySequence_list.removeOne(keySequence);
|
||||
keySequenceEdit->clear();
|
||||
alignKeySequencesLeft();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MultiKeyEdit::contains(const QKeySequence &keySequence) const {
|
||||
for (auto current_keySequence : keySequences())
|
||||
if (current_keySequence == keySequence)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void MultiKeyEdit::setContextMenuPolicy(Qt::ContextMenuPolicy policy) {
|
||||
QWidget::setContextMenuPolicy(policy);
|
||||
auto lineEdit_children = findChildren<QLineEdit *>();
|
||||
for (auto *lineEdit : lineEdit_children)
|
||||
lineEdit->setContextMenuPolicy(policy);
|
||||
}
|
||||
|
||||
bool MultiKeyEdit::isClearButtonEnabled() const {
|
||||
return findChild<QLineEdit *>()->isClearButtonEnabled();
|
||||
}
|
||||
|
||||
void MultiKeyEdit::setClearButtonEnabled(bool enable) {
|
||||
for (auto *lineEdit : findChildren<QLineEdit *>())
|
||||
lineEdit->setClearButtonEnabled(enable);
|
||||
}
|
||||
|
||||
void MultiKeyEdit::clear() {
|
||||
for (auto *keySequenceEdit : keySequenceEdit_vec)
|
||||
keySequenceEdit->clear();
|
||||
keySequence_list.clear();
|
||||
}
|
||||
|
||||
void MultiKeyEdit::setKeySequences(const QList<QKeySequence> &keySequences) {
|
||||
clear();
|
||||
keySequence_list = keySequences;
|
||||
int minCount = qMin(keySequenceEdit_vec.count(), keySequence_list.count());
|
||||
for (int i = 0; i < minCount; ++i)
|
||||
keySequenceEdit_vec[i]->setKeySequence(keySequence_list[i]);
|
||||
}
|
||||
|
||||
void MultiKeyEdit::addKeySequence(const QKeySequence &keySequence) {
|
||||
keySequenceEdit_vec.last()->setKeySequence(keySequence);
|
||||
alignKeySequencesLeft();
|
||||
}
|
||||
|
||||
void MultiKeyEdit::addNewKeySequenceEdit() {
|
||||
auto *keySequenceEdit = new QKeySequenceEdit(this);
|
||||
keySequenceEdit->installEventFilter(this);
|
||||
connect(keySequenceEdit, &QKeySequenceEdit::editingFinished,
|
||||
this, &MultiKeyEdit::onEditingFinished);
|
||||
connect(keySequenceEdit, &QKeySequenceEdit::keySequenceChanged,
|
||||
this, &MultiKeyEdit::keySequenceChanged);
|
||||
|
||||
auto *lineEdit = keySequenceEdit->findChild<QLineEdit *>();
|
||||
lineEdit->setClearButtonEnabled(true);
|
||||
lineEdit->installEventFilter(this);
|
||||
connect(lineEdit, &QLineEdit::customContextMenuRequested,
|
||||
this, &MultiKeyEdit::customContextMenuRequested);
|
||||
|
||||
layout()->addWidget(keySequenceEdit);
|
||||
keySequenceEdit_vec.append(keySequenceEdit);
|
||||
}
|
||||
|
||||
// Shift all key sequences left if there are any empty QKeySequenceEdit's.
|
||||
void MultiKeyEdit::alignKeySequencesLeft() {
|
||||
blockSignals(true);
|
||||
setKeySequences(keySequences());
|
||||
blockSignals(false);
|
||||
}
|
||||
|
||||
void MultiKeyEdit::setFocusToLastNonEmptyKeySequenceEdit() {
|
||||
for (auto it = keySequenceEdit_vec.rbegin(); it != keySequenceEdit_vec.rend(); ++it) {
|
||||
if (!(*it)->keySequence().isEmpty()) {
|
||||
(*it)->setFocus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MultiKeyEdit::onEditingFinished() {
|
||||
auto *keySequenceEdit = qobject_cast<QKeySequenceEdit *>(sender());
|
||||
if (keySequenceEdit && keySequence_list.contains(keySequenceEdit->keySequence()))
|
||||
removeOne(keySequenceEdit->keySequence());
|
||||
alignKeySequencesLeft();
|
||||
setFocusToLastNonEmptyKeySequenceEdit();
|
||||
|
||||
emit editingFinished();
|
||||
}
|
||||
|
||||
/* QKeySequenceEdit doesn't send or receive context menu events, but it owns QLineEdit that does.
|
||||
* This QLineEdit hijacks those events and so we need to filter/connect to it directly, rather than
|
||||
* the QKeySequenceEdit. I wouldn't be surprised if Qt fixed this in the future, in which case any
|
||||
* context menu related code in this class might need to change. */
|
||||
void MultiKeyEdit::showDefaultContextMenu(QLineEdit *lineEdit, const QPoint &pos) {
|
||||
QMenu menu(this);
|
||||
QAction clearAction("Clear Shortcut", &menu);
|
||||
connect(&clearAction, &QAction::triggered, lineEdit, [this, &lineEdit]() {
|
||||
removeOne(lineEdit->text());
|
||||
});
|
||||
menu.addAction(&clearAction);
|
||||
menu.exec(lineEdit->mapToGlobal(pos));
|
||||
}
|
||||
@@ -185,7 +185,9 @@ void NewMapPopup::on_pushButton_NewMap_Accept_clicked() {
|
||||
MapLayout *layout;
|
||||
|
||||
// If map name is not unique, use default value. Also use only valid characters.
|
||||
// After stripping invalid characters, strip any leading digits.
|
||||
QString newMapName = this->ui->lineEdit_NewMap_Name->text().remove(QRegularExpression("[^a-zA-Z0-9_]+"));
|
||||
newMapName.remove(QRegularExpression("^[0-9]*"));
|
||||
if (project->mapNames->contains(newMapName) || newMapName.isEmpty()) {
|
||||
newMapName = project->getNewMapName();
|
||||
}
|
||||
|
||||
72
src/ui/preferenceeditor.cpp
Normal file
72
src/ui/preferenceeditor.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#include "preferenceeditor.h"
|
||||
#include "ui_preferenceeditor.h"
|
||||
#include "config.h"
|
||||
#include "noscrollcombobox.h"
|
||||
|
||||
#include <QAbstractButton>
|
||||
#include <QRegularExpression>
|
||||
#include <QDirIterator>
|
||||
#include <QFormLayout>
|
||||
|
||||
|
||||
PreferenceEditor::PreferenceEditor(QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
ui(new Ui::PreferenceEditor),
|
||||
themeSelector(nullptr)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
auto *formLayout = new QFormLayout(ui->groupBox_Themes);
|
||||
themeSelector = new NoScrollComboBox(ui->groupBox_Themes);
|
||||
formLayout->addRow("Themes", themeSelector);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::clicked,
|
||||
this, &PreferenceEditor::dialogButtonClicked);
|
||||
populateFields();
|
||||
}
|
||||
|
||||
PreferenceEditor::~PreferenceEditor()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void PreferenceEditor::populateFields() {
|
||||
QStringList themes = { "default" };
|
||||
QRegularExpression re(":/themes/([A-z0-9_-]+).qss");
|
||||
QDirIterator it(":/themes", QDirIterator::Subdirectories);
|
||||
while (it.hasNext()) {
|
||||
QString themeName = re.match(it.next()).captured(1);
|
||||
themes.append(themeName);
|
||||
}
|
||||
themeSelector->addItems(themes);
|
||||
themeSelector->setCurrentText(porymapConfig.getTheme());
|
||||
|
||||
ui->lineEdit_TextEditorOpenFolder->setText(porymapConfig.getTextEditorOpenFolder());
|
||||
|
||||
ui->lineEdit_TextEditorGotoLine->setText(porymapConfig.getTextEditorGotoLine());
|
||||
}
|
||||
|
||||
void PreferenceEditor::saveFields() {
|
||||
if (themeSelector->currentText() != porymapConfig.getTheme()) {
|
||||
const auto theme = themeSelector->currentText();
|
||||
porymapConfig.setTheme(theme);
|
||||
emit themeChanged(theme);
|
||||
}
|
||||
|
||||
porymapConfig.setTextEditorOpenFolder(ui->lineEdit_TextEditorOpenFolder->text());
|
||||
|
||||
porymapConfig.setTextEditorGotoLine(ui->lineEdit_TextEditorGotoLine->text());
|
||||
|
||||
emit preferencesSaved();
|
||||
}
|
||||
|
||||
void PreferenceEditor::dialogButtonClicked(QAbstractButton *button) {
|
||||
auto buttonRole = ui->buttonBox->buttonRole(button);
|
||||
if (buttonRole == QDialogButtonBox::AcceptRole) {
|
||||
saveFields();
|
||||
close();
|
||||
} else if (buttonRole == QDialogButtonBox::ApplyRole) {
|
||||
saveFields();
|
||||
} else if (buttonRole == QDialogButtonBox::RejectRole) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "regionmapeditor.h"
|
||||
#include "ui_regionmapeditor.h"
|
||||
#include "imageexport.h"
|
||||
#include "shortcut.h"
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
|
||||
@@ -24,6 +25,7 @@ RegionMapEditor::RegionMapEditor(QWidget *parent, Project *project_) :
|
||||
this->project = project_;
|
||||
this->region_map = new RegionMap;
|
||||
this->ui->action_RegionMap_Resize->setVisible(false);
|
||||
this->initShortcuts();
|
||||
this->restoreWindowState();
|
||||
}
|
||||
|
||||
@@ -92,6 +94,39 @@ bool RegionMapEditor::loadCityMaps() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void RegionMapEditor::initShortcuts() {
|
||||
auto *shortcut_RM_Options_delete = new Shortcut(
|
||||
{QKeySequence("Del"), QKeySequence("Backspace")}, this, SLOT(on_pushButton_RM_Options_delete_clicked()));
|
||||
shortcut_RM_Options_delete->setObjectName("shortcut_RM_Options_delete");
|
||||
shortcut_RM_Options_delete->setWhatsThis("Map Layout: Delete Square");
|
||||
|
||||
shortcutsConfig.load();
|
||||
shortcutsConfig.setDefaultShortcuts(shortcutableObjects());
|
||||
applyUserShortcuts();
|
||||
}
|
||||
|
||||
QObjectList RegionMapEditor::shortcutableObjects() const {
|
||||
QObjectList shortcutable_objects;
|
||||
|
||||
for (auto *action : findChildren<QAction *>())
|
||||
if (!action->objectName().isEmpty())
|
||||
shortcutable_objects.append(qobject_cast<QObject *>(action));
|
||||
for (auto *shortcut : findChildren<Shortcut *>())
|
||||
if (!shortcut->objectName().isEmpty())
|
||||
shortcutable_objects.append(qobject_cast<QObject *>(shortcut));
|
||||
|
||||
return shortcutable_objects;
|
||||
}
|
||||
|
||||
void RegionMapEditor::applyUserShortcuts() {
|
||||
for (auto *action : findChildren<QAction *>())
|
||||
if (!action->objectName().isEmpty())
|
||||
action->setShortcuts(shortcutsConfig.userShortcuts(action));
|
||||
for (auto *shortcut : findChildren<Shortcut *>())
|
||||
if (!shortcut->objectName().isEmpty())
|
||||
shortcut->setKeys(shortcutsConfig.userShortcuts(shortcut));
|
||||
}
|
||||
|
||||
void RegionMapEditor::displayRegionMap() {
|
||||
displayRegionMapTileSelector();
|
||||
displayCityMapTileSelector();
|
||||
|
||||
153
src/ui/shortcut.cpp
Normal file
153
src/ui/shortcut.cpp
Normal file
@@ -0,0 +1,153 @@
|
||||
#include "shortcut.h"
|
||||
|
||||
#include <QtEvents>
|
||||
#include <QWhatsThis>
|
||||
|
||||
|
||||
Shortcut::Shortcut(QWidget *parent) :
|
||||
QObject(parent),
|
||||
sc_member(nullptr),
|
||||
sc_ambiguousmember(nullptr),
|
||||
sc_context(Qt::WindowShortcut),
|
||||
sc_vec(QVector<QShortcut *>({new QShortcut(parent)}))
|
||||
{ }
|
||||
|
||||
Shortcut::Shortcut(const QKeySequence &key, QWidget *parent,
|
||||
const char *member, const char *ambiguousMember,
|
||||
Qt::ShortcutContext shortcutContext) :
|
||||
QObject(parent),
|
||||
sc_member(member),
|
||||
sc_ambiguousmember(ambiguousMember),
|
||||
sc_context(shortcutContext),
|
||||
sc_vec(QVector<QShortcut *>())
|
||||
{
|
||||
setKey(key);
|
||||
}
|
||||
|
||||
Shortcut::Shortcut(const QList<QKeySequence> &keys, QWidget *parent,
|
||||
const char *member, const char *ambiguousMember,
|
||||
Qt::ShortcutContext shortcutContext) :
|
||||
QObject(parent),
|
||||
sc_member(member),
|
||||
sc_ambiguousmember(ambiguousMember),
|
||||
sc_context(shortcutContext),
|
||||
sc_vec(QVector<QShortcut *>())
|
||||
{
|
||||
setKeys(keys);
|
||||
}
|
||||
|
||||
Shortcut::~Shortcut()
|
||||
{
|
||||
for (auto *sc : sc_vec)
|
||||
delete sc;
|
||||
}
|
||||
|
||||
void Shortcut::addKey(const QKeySequence &key) {
|
||||
sc_vec.append(new QShortcut(key, parentWidget(), sc_member, sc_ambiguousmember, sc_context));
|
||||
}
|
||||
|
||||
void Shortcut::setKey(const QKeySequence &key) {
|
||||
if (sc_vec.isEmpty()) {
|
||||
addKey(key);
|
||||
} else {
|
||||
while (sc_vec.count() != 1)
|
||||
delete sc_vec.takeLast();
|
||||
sc_vec.first()->setKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
QKeySequence Shortcut::key() const {
|
||||
return sc_vec.first()->key();
|
||||
}
|
||||
|
||||
void Shortcut::addKeys(const QList<QKeySequence> &keys) {
|
||||
for (auto key : keys)
|
||||
addKey(key);
|
||||
}
|
||||
|
||||
void Shortcut::setKeys(const QList<QKeySequence> &keys) {
|
||||
if (keys.isEmpty())
|
||||
return;
|
||||
|
||||
while (sc_vec.count() < keys.count())
|
||||
addKey(QKeySequence());
|
||||
|
||||
while (sc_vec.count() > keys.count())
|
||||
delete sc_vec.takeLast();
|
||||
|
||||
for (int i = 0; i < keys.count(); ++i)
|
||||
sc_vec[i]->setKey(keys[i]);
|
||||
}
|
||||
|
||||
QList<QKeySequence> Shortcut::keys() const {
|
||||
QList<QKeySequence> ks_list = QList<QKeySequence>();
|
||||
for (auto *sc : sc_vec)
|
||||
ks_list.append(sc->key());
|
||||
return ks_list;
|
||||
}
|
||||
|
||||
void Shortcut::setEnabled(bool enable) {
|
||||
for (auto *sc : sc_vec)
|
||||
sc->setEnabled(enable);
|
||||
}
|
||||
|
||||
bool Shortcut::isEnabled() const {
|
||||
return sc_vec.first()->isEnabled();
|
||||
}
|
||||
|
||||
void Shortcut::setContext(Qt::ShortcutContext context) {
|
||||
sc_context = context;
|
||||
for (auto *sc : sc_vec)
|
||||
sc->setContext(context);
|
||||
}
|
||||
|
||||
Qt::ShortcutContext Shortcut::context() const {
|
||||
return sc_context;
|
||||
}
|
||||
|
||||
void Shortcut::setWhatsThis(const QString &text) {
|
||||
for (auto *sc : sc_vec)
|
||||
sc->setWhatsThis(text);
|
||||
}
|
||||
|
||||
QString Shortcut::whatsThis() const {
|
||||
return sc_vec.first()->whatsThis();
|
||||
}
|
||||
|
||||
void Shortcut::setAutoRepeat(bool on) {
|
||||
for (auto *sc : sc_vec)
|
||||
sc->setAutoRepeat(on);
|
||||
}
|
||||
|
||||
bool Shortcut::autoRepeat() const {
|
||||
return sc_vec.first()->autoRepeat();
|
||||
}
|
||||
|
||||
int Shortcut::id() const {
|
||||
return sc_vec.first()->id();
|
||||
}
|
||||
|
||||
QList<int> Shortcut::ids() const {
|
||||
QList<int> id_list;
|
||||
for (auto *sc : sc_vec)
|
||||
id_list.append(sc->id());
|
||||
return id_list;
|
||||
}
|
||||
|
||||
bool Shortcut::event(QEvent *e) {
|
||||
if (isEnabled() && e->type() == QEvent::Shortcut) {
|
||||
auto se = static_cast<QShortcutEvent *>(e);
|
||||
if (ids().contains(se->shortcutId()) && keys().contains(se->key())) {
|
||||
if (QWhatsThis::inWhatsThisMode()) {
|
||||
QWhatsThis::showText(QCursor::pos(), whatsThis());
|
||||
} else {
|
||||
if (se->isAmbiguous())
|
||||
emit activatedAmbiguously();
|
||||
else
|
||||
emit activated();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
188
src/ui/shortcutseditor.cpp
Normal file
188
src/ui/shortcutseditor.cpp
Normal file
@@ -0,0 +1,188 @@
|
||||
#include "shortcutseditor.h"
|
||||
#include "ui_shortcutseditor.h"
|
||||
#include "config.h"
|
||||
#include "multikeyedit.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <QFormLayout>
|
||||
#include <QAbstractButton>
|
||||
#include <QtEvents>
|
||||
#include <QMessageBox>
|
||||
#include <QRegularExpression>
|
||||
#include <QLabel>
|
||||
|
||||
|
||||
ShortcutsEditor::ShortcutsEditor(QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
ui(new Ui::ShortcutsEditor),
|
||||
main_container(nullptr)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
main_container = ui->scrollAreaWidgetContents_Shortcuts;
|
||||
auto *main_layout = new QVBoxLayout(main_container);
|
||||
main_layout->setSpacing(12);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::clicked,
|
||||
this, &ShortcutsEditor::dialogButtonClicked);
|
||||
}
|
||||
|
||||
ShortcutsEditor::ShortcutsEditor(const QObjectList &shortcutableObjects, QWidget *parent) :
|
||||
ShortcutsEditor(parent)
|
||||
{
|
||||
setShortcutableObjects(shortcutableObjects);
|
||||
}
|
||||
|
||||
ShortcutsEditor::~ShortcutsEditor()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void ShortcutsEditor::setShortcutableObjects(const QObjectList &shortcutableObjects) {
|
||||
parseObjectList(shortcutableObjects);
|
||||
populateMainContainer();
|
||||
}
|
||||
|
||||
void ShortcutsEditor::saveShortcuts() {
|
||||
QMultiMap<const QObject *, QKeySequence> objects_keySequences;
|
||||
for (auto it = multiKeyEdits_objects.cbegin(); it != multiKeyEdits_objects.cend(); ++it) {
|
||||
if (it.key()->keySequences().isEmpty())
|
||||
objects_keySequences.insert(it.value(), QKeySequence());
|
||||
for (auto keySequence : it.key()->keySequences())
|
||||
objects_keySequences.insert(it.value(), keySequence);
|
||||
}
|
||||
|
||||
shortcutsConfig.setUserShortcuts(objects_keySequences);
|
||||
emit shortcutsSaved();
|
||||
}
|
||||
|
||||
// Restores default shortcuts but doesn't save until Apply or OK is clicked.
|
||||
void ShortcutsEditor::resetShortcuts() {
|
||||
for (auto it = multiKeyEdits_objects.begin(); it != multiKeyEdits_objects.end(); ++it) {
|
||||
it.key()->blockSignals(true);
|
||||
const auto defaults = shortcutsConfig.defaultShortcuts(it.value());
|
||||
it.key()->setKeySequences(defaults);
|
||||
it.key()->blockSignals(false);
|
||||
}
|
||||
}
|
||||
|
||||
void ShortcutsEditor::parseObjectList(const QObjectList &objectList) {
|
||||
for (auto *object : objectList) {
|
||||
const auto label = getLabel(object);
|
||||
if (!label.isEmpty() && !object->objectName().isEmpty() && !object->objectName().startsWith("_q_"))
|
||||
labels_objects.insert(label, object);
|
||||
}
|
||||
}
|
||||
|
||||
QString ShortcutsEditor::getLabel(const QObject *object) const {
|
||||
if (stringPropertyIsNotEmpty(object, "text"))
|
||||
return object->property("text").toString().remove('&');
|
||||
else if (stringPropertyIsNotEmpty(object, "whatsThis"))
|
||||
return object->property("whatsThis").toString().remove('&');
|
||||
else
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool ShortcutsEditor::stringPropertyIsNotEmpty(const QObject *object, const char *name) const {
|
||||
return object->property(name).isValid() && !object->property(name).toString().isEmpty();
|
||||
}
|
||||
|
||||
void ShortcutsEditor::populateMainContainer() {
|
||||
for (auto object : labels_objects) {
|
||||
const auto shortcutContext = getShortcutContext(object);
|
||||
if (!contexts_layouts.contains(shortcutContext))
|
||||
addNewContextGroup(shortcutContext);
|
||||
|
||||
addNewMultiKeyEdit(object, shortcutContext);
|
||||
}
|
||||
}
|
||||
|
||||
// The context for which the object's shortcut is active (Displayed in group box title).
|
||||
// Uses the parent window's objectName and adds spaces between words.
|
||||
QString ShortcutsEditor::getShortcutContext(const QObject *object) const {
|
||||
auto objectParentWidget = static_cast<QWidget *>(object->parent());
|
||||
auto context = objectParentWidget->window()->objectName();
|
||||
QRegularExpression re("[A-Z]");
|
||||
int i = context.indexOf(re, 1);
|
||||
while (i != -1) {
|
||||
if (context.at(i - 1) != ' ')
|
||||
context.insert(i++, ' ');
|
||||
i = context.indexOf(re, i + 1);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
// Seperate shortcuts into context groups for duplicate checking.
|
||||
void ShortcutsEditor::addNewContextGroup(const QString &shortcutContext) {
|
||||
auto *groupBox = new QGroupBox(shortcutContext, main_container);
|
||||
main_container->layout()->addWidget(groupBox);
|
||||
auto *formLayout = new QFormLayout(groupBox);
|
||||
contexts_layouts.insert(shortcutContext, formLayout);
|
||||
}
|
||||
|
||||
void ShortcutsEditor::addNewMultiKeyEdit(const QObject *object, const QString &shortcutContext) {
|
||||
auto *container = contexts_layouts.value(shortcutContext)->parentWidget();
|
||||
auto *multiKeyEdit = new MultiKeyEdit(container);
|
||||
multiKeyEdit->setKeySequences(shortcutsConfig.userShortcuts(object));
|
||||
connect(multiKeyEdit, &MultiKeyEdit::keySequenceChanged,
|
||||
this, &ShortcutsEditor::checkForDuplicates);
|
||||
contexts_layouts.value(shortcutContext)->addRow(labels_objects.key(object), multiKeyEdit);
|
||||
multiKeyEdits_objects.insert(multiKeyEdit, object);
|
||||
}
|
||||
|
||||
void ShortcutsEditor::checkForDuplicates(const QKeySequence &keySequence) {
|
||||
if (keySequence.isEmpty())
|
||||
return;
|
||||
|
||||
auto *sender_multiKeyEdit = qobject_cast<MultiKeyEdit *>(sender());
|
||||
if (!sender_multiKeyEdit)
|
||||
return;
|
||||
|
||||
for (auto *sibling_multiKeyEdit : siblings(sender_multiKeyEdit))
|
||||
if (sibling_multiKeyEdit->contains(keySequence))
|
||||
promptUserOnDuplicateFound(sender_multiKeyEdit, sibling_multiKeyEdit);
|
||||
}
|
||||
|
||||
QList<MultiKeyEdit *> ShortcutsEditor::siblings(MultiKeyEdit *multiKeyEdit) const {
|
||||
auto list = multiKeyEdit->parent()->findChildren<MultiKeyEdit *>(QString(), Qt::FindDirectChildrenOnly);
|
||||
list.removeOne(multiKeyEdit);
|
||||
return list;
|
||||
}
|
||||
|
||||
void ShortcutsEditor::promptUserOnDuplicateFound(MultiKeyEdit *sender, MultiKeyEdit *sibling) {
|
||||
const auto duplicateKeySequence = sender->keySequences().last();
|
||||
const auto siblingLabel = getLabel(multiKeyEdits_objects.value(sibling));
|
||||
const auto message = QString(
|
||||
"Shortcut '%1' is already used by '%2', would you like to replace it?")
|
||||
.arg(duplicateKeySequence.toString()).arg(siblingLabel);
|
||||
|
||||
const auto result = QMessageBox::question(
|
||||
this, "porymap", message, QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
|
||||
if (result == QMessageBox::Yes)
|
||||
removeKeySequence(duplicateKeySequence, sibling);
|
||||
else
|
||||
removeKeySequence(duplicateKeySequence, sender);
|
||||
|
||||
activateWindow();
|
||||
}
|
||||
|
||||
void ShortcutsEditor::removeKeySequence(const QKeySequence &keySequence, MultiKeyEdit *multiKeyEdit) {
|
||||
multiKeyEdit->blockSignals(true);
|
||||
multiKeyEdit->removeOne(keySequence);
|
||||
multiKeyEdit->blockSignals(false);
|
||||
}
|
||||
|
||||
void ShortcutsEditor::dialogButtonClicked(QAbstractButton *button) {
|
||||
auto buttonRole = ui->buttonBox->buttonRole(button);
|
||||
if (buttonRole == QDialogButtonBox::AcceptRole) {
|
||||
saveShortcuts();
|
||||
close();
|
||||
} else if (buttonRole == QDialogButtonBox::ApplyRole) {
|
||||
saveShortcuts();
|
||||
} else if (buttonRole == QDialogButtonBox::RejectRole) {
|
||||
close();
|
||||
} else if (buttonRole == QDialogButtonBox::ResetRole) {
|
||||
resetShortcuts();
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "paletteutil.h"
|
||||
#include "imageexport.h"
|
||||
#include "config.h"
|
||||
#include "shortcut.h"
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QDialogButtonBox>
|
||||
@@ -86,7 +87,6 @@ void TilesetEditor::setTilesets(QString primaryTilesetLabel, QString secondaryTi
|
||||
|
||||
void TilesetEditor::initUi() {
|
||||
ui->setupUi(this);
|
||||
new QShortcut(QKeySequence("Ctrl+Shift+Z"), this, SLOT(on_actionRedo_triggered()));
|
||||
this->tileXFlip = ui->checkBox_xFlip->isChecked();
|
||||
this->tileYFlip = ui->checkBox_yFlip->isChecked();
|
||||
this->paletteId = ui->spinBox_paletteSelector->value();
|
||||
@@ -102,6 +102,7 @@ void TilesetEditor::initUi() {
|
||||
this->initMetatileLayersItem();
|
||||
this->initTileSelector();
|
||||
this->initSelectedTileItem();
|
||||
this->initShortcuts();
|
||||
this->metatileSelector->select(0);
|
||||
this->restoreWindowState();
|
||||
}
|
||||
@@ -209,6 +210,48 @@ void TilesetEditor::initSelectedTileItem() {
|
||||
this->ui->graphicsView_selectedTile->setFixedSize(this->selectedTilePixmapItem->pixmap().width() + 2, this->selectedTilePixmapItem->pixmap().height() + 2);
|
||||
}
|
||||
|
||||
void TilesetEditor::initShortcuts() {
|
||||
initExtraShortcuts();
|
||||
|
||||
shortcutsConfig.load();
|
||||
shortcutsConfig.setDefaultShortcuts(shortcutableObjects());
|
||||
applyUserShortcuts();
|
||||
}
|
||||
|
||||
void TilesetEditor::initExtraShortcuts() {
|
||||
ui->actionRedo->setShortcuts({ui->actionRedo->shortcut(), QKeySequence("Ctrl+Shift+Z")});
|
||||
|
||||
auto *shortcut_xFlip = new Shortcut(QKeySequence(), ui->checkBox_xFlip, SLOT(toggle()));
|
||||
shortcut_xFlip->setObjectName("shortcut_xFlip");
|
||||
shortcut_xFlip->setWhatsThis("X Flip");
|
||||
|
||||
auto *shortcut_yFlip = new Shortcut(QKeySequence(), ui->checkBox_yFlip, SLOT(toggle()));
|
||||
shortcut_yFlip->setObjectName("shortcut_yFlip");
|
||||
shortcut_yFlip->setWhatsThis("Y Flip");
|
||||
}
|
||||
|
||||
QObjectList TilesetEditor::shortcutableObjects() const {
|
||||
QObjectList shortcutable_objects;
|
||||
|
||||
for (auto *action : findChildren<QAction *>())
|
||||
if (!action->objectName().isEmpty())
|
||||
shortcutable_objects.append(qobject_cast<QObject *>(action));
|
||||
for (auto *shortcut : findChildren<Shortcut *>())
|
||||
if (!shortcut->objectName().isEmpty())
|
||||
shortcutable_objects.append(qobject_cast<QObject *>(shortcut));
|
||||
|
||||
return shortcutable_objects;
|
||||
}
|
||||
|
||||
void TilesetEditor::applyUserShortcuts() {
|
||||
for (auto *action : findChildren<QAction *>())
|
||||
if (!action->objectName().isEmpty())
|
||||
action->setShortcuts(shortcutsConfig.userShortcuts(action));
|
||||
for (auto *shortcut : findChildren<Shortcut *>())
|
||||
if (!shortcut->objectName().isEmpty())
|
||||
shortcut->setKeys(shortcutsConfig.userShortcuts(shortcut));
|
||||
}
|
||||
|
||||
void TilesetEditor::restoreWindowState() {
|
||||
logInfo("Restoring tileset editor geometry from previous session.");
|
||||
QMap<QString, QByteArray> geometry = porymapConfig.getTilesetEditorGeometry();
|
||||
|
||||
Reference in New Issue
Block a user