Merge remote-tracking branch 'origin/master' into fix827

# Conflicts:
#	src/mainwindow.cpp
This commit is contained in:
Marcus Huderle
2020-12-14 17:57:30 -06:00
43 changed files with 2263 additions and 271 deletions

View File

@@ -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 { };
}
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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)));
}

View File

@@ -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
View 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));
}

View File

@@ -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();
}

View 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();
}
}

View File

@@ -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
View 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
View 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();
}
}

View File

@@ -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();