Correctly restore window focus for file dialogs

This commit is contained in:
GriffinR 2024-10-09 12:35:12 -04:00
parent 931b471cf1
commit 1ed9b1ee10
16 changed files with 134 additions and 91 deletions

53
include/core/filedialog.h Normal file
View file

@ -0,0 +1,53 @@
#ifndef FILEDIALOG_H
#define FILEDIALOG_H
#include <QFileDialog>
/*
Static QFileDialog functions will (unless otherwise specified) use native file dialogs.
In general this is good (we want our file dialogs to be visually seamless) but unfortunately
the native file dialogs ignore the parent widget, so in some cases they'll return focus to
the main window rather than the window that opened the file dialog.
To make working around this a little easier we use this class, which will use the native
file dialog and manually return focus to the parent widget.
It will also save the directory of the previous file selected in a file dialog, and if
no 'dir' argument is specified it will open new dialogs at that directory.
*/
class FileDialog : public QFileDialog
{
public:
FileDialog(QWidget *parent, Qt::WindowFlags flags) : QFileDialog(parent, flags) {};
FileDialog(QWidget *parent = nullptr,
const QString &caption = QString(),
const QString &directory = QString(),
const QString &filter = QString()) : QFileDialog(parent, caption, directory, filter) {};
static void setDirectory(const QString &dir) { FileDialog::prevDirectory = dir; }
static QString getDirectory() { return FileDialog::prevDirectory; }
static QString getOpenFileName(QWidget *parent = nullptr,
const QString &caption = QString(),
const QString &dir = QString(),
const QString &filter = QString(),
QString *selectedFilter = nullptr,
QFileDialog::Options options = Options());
static QString getSaveFileName(QWidget *parent = nullptr,
const QString &caption = QString(),
const QString &dir = QString(),
const QString &filter = QString(),
QString *selectedFilter = nullptr,
QFileDialog::Options options = Options());
private:
static QString prevDirectory;
static QString getDirectoryFromInput(const QString &dir);
static void setDirectoryFromFile(const QString &fileName);
static void restoreFocus(QWidget *parent);
};
#endif // FILEDIALOG_H

View file

@ -76,7 +76,6 @@ public:
QFileSystemWatcher fileWatcher;
QMap<QString, qint64> modifiedFileTimestamps;
bool usingAsmTilesets;
QString importExportPath;
QSet<QString> disabledSettingsNames;
int pokemonMinLevel;
int pokemonMaxLevel;
@ -216,7 +215,6 @@ public:
QString buildMetatileLabelsText(const QMap<QString, uint16_t> defines);
QString findMetatileLabelsTileset(QString label);
void setImportExportPath(QString filename);
static QString getExistingFilepath(QString filepath);
void applyParsedLimits();

View file

@ -31,7 +31,6 @@ private:
Ui::CustomScriptsEditor *ui;
bool hasUnsavedChanges = false;
QString fileDialogDir;
const QString baseDir;
void displayScript(const QString &filepath, bool enabled);

View file

@ -4,7 +4,6 @@
#include "orderedjson.h"
#include <QDialog>
#include <QFileDialog>
class Project;
@ -33,7 +32,7 @@ private:
void hideMessages();
QString browse(QString filter, QFileDialog::FileMode mode);
QString browse(QString filter);
private slots:
void on_browse_tilesetImagePath_clicked();

View file

@ -25,6 +25,7 @@ SOURCES += src/core/block.cpp \
src/core/bitpacker.cpp \
src/core/blockdata.cpp \
src/core/events.cpp \
src/core/filedialog.cpp \
src/core/heallocation.cpp \
src/core/imageexport.cpp \
src/core/map.cpp \
@ -123,6 +124,7 @@ HEADERS += include/core/block.h \
include/core/bitpacker.h \
include/core/blockdata.h \
include/core/events.h \
include/core/filedialog.h \
include/core/heallocation.h \
include/core/history.h \
include/core/imageexport.h \

36
src/core/filedialog.cpp Normal file
View file

@ -0,0 +1,36 @@
#include "filedialog.h"
QString FileDialog::prevDirectory;
QString FileDialog::getDirectoryFromInput(const QString &dir) {
if (dir.isEmpty())
return FileDialog::prevDirectory;
return dir;
}
void FileDialog::setDirectoryFromFile(const QString &fileName) {
if (!fileName.isEmpty())
FileDialog::prevDirectory = QFileInfo(fileName).absolutePath();
}
void FileDialog::restoreFocus(QWidget *parent) {
if (parent) {
parent->raise();
parent->activateWindow();
}
}
QString FileDialog::getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) {
const QString fileName = QFileDialog::getOpenFileName(parent, caption, getDirectoryFromInput(dir), filter, selectedFilter, options);
setDirectoryFromFile(fileName);
restoreFocus(parent);
return fileName;
}
QString FileDialog::getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) {
const QString fileName = QFileDialog::getSaveFileName(parent, caption, getDirectoryFromInput(dir), filter, selectedFilter, options);
setDirectoryFromFile(fileName);
restoreFocus(parent);
return fileName;
}

View file

@ -19,8 +19,8 @@
#include "montabwidget.h"
#include "imageexport.h"
#include "newmapconnectiondialog.h"
#include "filedialog.h"
#include <QFileDialog>
#include <QClipboard>
#include <QDirIterator>
#include <QStandardItemModel>
@ -730,7 +730,7 @@ void MainWindow::openSubWindow(QWidget * window) {
}
QString MainWindow::getExistingDirectory(QString dir) {
return QFileDialog::getExistingDirectory(this, "Open Directory", dir, QFileDialog::ShowDirsOnly);
return FileDialog::getExistingDirectory(this, "Open Directory", dir, QFileDialog::ShowDirsOnly);
}
void MainWindow::on_action_Open_Project_triggered()
@ -2596,15 +2596,11 @@ void MainWindow::on_actionImport_Map_from_Advance_Map_1_92_triggered(){
void MainWindow::importMapFromAdvanceMap1_92()
{
QString filepath = QFileDialog::getOpenFileName(
this,
QString("Import Map from Advance Map 1.92"),
this->editor->project->importExportPath,
"Advance Map 1.92 Map Files (*.map)");
QString filepath = FileDialog::getOpenFileName(this, "Import Map from Advance Map 1.92", "", "Advance Map 1.92 Map Files (*.map)");
if (filepath.isEmpty()) {
return;
}
this->editor->project->setImportExportPath(filepath);
MapParser parser;
bool error = false;
MapLayout *mapLayout = parser.parse(filepath, &error, editor->project);

View file

@ -7,6 +7,7 @@
#include "tile.h"
#include "tileset.h"
#include "map.h"
#include "filedialog.h"
#include "orderedjson.h"
@ -89,7 +90,7 @@ void Project::initSignals() {
void Project::set_root(QString dir) {
this->root = dir;
this->importExportPath = dir;
FileDialog::setDirectory(dir);
this->parser.set_root(dir);
}
@ -2829,11 +2830,6 @@ QString Project::getDynamicMapDefineName() {
return prefix + projectConfig.getIdentifier(ProjectIdentifier::define_map_dynamic);
}
void Project::setImportExportPath(QString filename)
{
this->importExportPath = QFileInfo(filename).absolutePath();
}
// If the provided filepath is an absolute path to an existing file, return filepath.
// If not, and the provided filepath is a relative path from the project dir to an existing file, return the relative path.
// Otherwise return empty string.

View file

@ -4,9 +4,9 @@
#include "config.h"
#include "editor.h"
#include "shortcut.h"
#include "filedialog.h"
#include <QDir>
#include <QFileDialog>
CustomScriptsEditor::CustomScriptsEditor(QWidget *parent) :
QMainWindow(parent),
@ -23,8 +23,6 @@ CustomScriptsEditor::CustomScriptsEditor(QWidget *parent) :
for (int i = 0; i < paths.length(); i++)
this->displayScript(paths.at(i), enabled.at(i));
this->fileDialogDir = userConfig.projectDir;
connect(ui->button_CreateNewScript, &QAbstractButton::clicked, this, &CustomScriptsEditor::createNewScript);
connect(ui->button_LoadScript, &QAbstractButton::clicked, this, &CustomScriptsEditor::loadScript);
connect(ui->button_RefreshScripts, &QAbstractButton::clicked, this, &CustomScriptsEditor::userRefreshScripts);
@ -147,19 +145,13 @@ bool CustomScriptsEditor::getScriptEnabled(QListWidgetItem * item) const {
}
QString CustomScriptsEditor::chooseScript(QString dir) {
return QFileDialog::getOpenFileName(this, "Choose Custom Script File", dir, "JavaScript Files (*.js)");
return FileDialog::getOpenFileName(this, "Choose Custom Script File", dir, "JavaScript Files (*.js)");
}
void CustomScriptsEditor::createNewScript() {
QString filepath = QFileDialog::getSaveFileName(this, "Create New Script File", this->fileDialogDir + "/new_script.js", "JavaScript Files (*.js)");
// QFileDialog::getSaveFileName returns focus to the main editor window when closed. Workaround for this below
this->raise();
this->activateWindow();
const QString filepath = FileDialog::getSaveFileName(this, "Create New Script File", FileDialog::getDirectory() + "/new_script.js", "JavaScript Files (*.js)");
if (filepath.isEmpty())
return;
this->fileDialogDir = filepath;
QFile scriptFile(filepath);
if (!scriptFile.open(QIODevice::WriteOnly)) {
@ -179,10 +171,9 @@ void CustomScriptsEditor::createNewScript() {
}
void CustomScriptsEditor::loadScript() {
QString filepath = this->chooseScript(this->fileDialogDir);
QString filepath = this->chooseScript(FileDialog::getDirectory());
if (filepath.isEmpty())
return;
this->fileDialogDir = filepath;
this->displayNewScript(filepath);
}

View file

@ -2,8 +2,8 @@
#include "ui_mapimageexporter.h"
#include "qgifimage.h"
#include "editcommands.h"
#include "filedialog.h"
#include <QFileDialog>
#include <QImage>
#include <QPainter>
#include <QPoint>
@ -65,13 +65,12 @@ void MapImageExporter::saveImage() {
}
QString defaultFilepath = QString("%1/%2.%3")
.arg(editor->project->importExportPath)
.arg(FileDialog::getDirectory())
.arg(defaultFilename)
.arg(this->mode == ImageExporterMode::Timelapse ? "gif" : "png");
QString filter = this->mode == ImageExporterMode::Timelapse ? "Image Files (*.gif)" : "Image Files (*.png *.jpg *.bmp)";
QString filepath = QFileDialog::getSaveFileName(this, title, defaultFilepath, filter);
QString filepath = FileDialog::getSaveFileName(this, title, defaultFilepath, filter);
if (!filepath.isEmpty()) {
editor->project->setImportExportPath(filepath);
switch (this->mode) {
case ImageExporterMode::Normal:
this->preview.save(filepath);

View file

@ -1,6 +1,5 @@
#include "newtilesetdialog.h"
#include "ui_newtilesetdialog.h"
#include <QFileDialog>
#include "project.h"
NewTilesetDialog::NewTilesetDialog(Project* project, QWidget *parent) :

View file

@ -3,8 +3,8 @@
#include "paletteutil.h"
#include "config.h"
#include "log.h"
#include "filedialog.h"
#include <QFileDialog>
#include <QMessageBox>
@ -158,15 +158,10 @@ void PaletteEditor::on_actionRedo_triggered()
void PaletteEditor::on_actionImport_Palette_triggered()
{
QString filepath = QFileDialog::getOpenFileName(
this,
QString("Import Tileset Palette"),
this->project->importExportPath,
"Palette Files (*.pal *.act *tpl *gpl)");
QString filepath = FileDialog::getOpenFileName(this, "Import Tileset Palette", "", "Palette Files (*.pal *.act *tpl *gpl)");
if (filepath.isEmpty()) {
return;
}
this->project->setImportExportPath(filepath);
bool error = false;
QList<QRgb> palette = PaletteUtil::parse(filepath, &error);
if (error) {

View file

@ -2,6 +2,7 @@
#include "config.h"
#include "noscrollcombobox.h"
#include "prefab.h"
#include "filedialog.h"
#include <QAbstractButton>
#include <QFormLayout>
@ -383,10 +384,10 @@ QString ProjectSettingsEditor::chooseProjectFile(const QString &defaultFilepath)
QString path;
if (defaultFilepath.endsWith("/")){
// Default filepath is a folder, choose a new folder
path = QFileDialog::getExistingDirectory(this, "Choose Project File Folder", startDir) + QDir::separator();
path = FileDialog::getExistingDirectory(this, "Choose Project File Folder", startDir) + QDir::separator();
} else{
// Default filepath is not a folder, choose a new file
path = QFileDialog::getOpenFileName(this, "Choose Project File", startDir);
path = FileDialog::getOpenFileName(this, "Choose Project File", startDir);
}
if (!path.startsWith(this->baseDir)){
@ -573,10 +574,9 @@ void ProjectSettingsEditor::chooseImageFile(QLineEdit * filepathEdit) {
}
void ProjectSettingsEditor::chooseFile(QLineEdit * filepathEdit, const QString &description, const QString &extensions) {
QString filepath = QFileDialog::getOpenFileName(this, description, this->project->importExportPath, extensions);
QString filepath = FileDialog::getOpenFileName(this, description, "", extensions);
if (filepath.isEmpty())
return;
this->project->setImportExportPath(filepath);
if (filepathEdit)
filepathEdit->setText(this->stripProjectDir(filepath));

View file

@ -10,7 +10,6 @@
#include <QDir>
#include <QDialog>
#include <QDialogButtonBox>
#include <QFileDialog>
#include <QFormLayout>
#include <QLineEdit>
#include <QSpinBox>

View file

@ -1,6 +1,7 @@
#include "project.h"
#include "regionmappropertiesdialog.h"
#include "ui_regionmappropertiesdialog.h"
#include "filedialog.h"
RegionMapPropertiesDialog::RegionMapPropertiesDialog(QWidget *parent) :
QDialog(parent),
@ -30,13 +31,9 @@ void RegionMapPropertiesDialog::hideMessages() {
this->adjustSize();
}
QString RegionMapPropertiesDialog::browse(QString filter, QFileDialog::FileMode mode) {
QString RegionMapPropertiesDialog::browse(QString filter) {
if (!this->project) return QString();
QFileDialog browser;
browser.setFileMode(mode);
QString filepath = browser.getOpenFileName(this, "Select a File", this->project->importExportPath, filter);
if (!filepath.isEmpty())
this->project->setImportExportPath(filepath);
QString filepath = FileDialog::getOpenFileName(this, "Select a File", "", filter);
// remove the project root from the filepath
return filepath.replace(this->project->root + "/", "");
@ -107,21 +104,21 @@ poryjson::Json RegionMapPropertiesDialog::saveToJson() {
}
void RegionMapPropertiesDialog::on_browse_tilesetImagePath_clicked() {
QString path = browse("Images (*.png *.bmp)", QFileDialog::ExistingFile);
QString path = browse("Images (*.png *.bmp)");
if (!path.isEmpty()) {
ui->config_tilemapImagePath->setText(path);
}
}
void RegionMapPropertiesDialog::on_browse_tilemapBinPath_clicked() {
QString path = browse("Binary (*.bin *.tilemap *.4bpp *.8bpp)", QFileDialog::AnyFile);
QString path = browse("Binary (*.bin *.tilemap *.4bpp *.8bpp)");
if (!path.isEmpty()) {
ui->config_tilemapBinPath->setText(path);
}
}
void RegionMapPropertiesDialog::on_browse_tilemapPalettePath_clicked() {
QString path = browse("Text (*.pal)", QFileDialog::AnyFile);
QString path = browse("Text (*.pal)");
if (!path.isEmpty()) {
ui->config_tilemapPalettePath->setText(path);
}
@ -129,12 +126,12 @@ void RegionMapPropertiesDialog::on_browse_tilemapPalettePath_clicked() {
void RegionMapPropertiesDialog::on_browse_layoutPath_clicked() {
if (ui->config_layoutFormat->currentIndex() == 0) {
QString path = browse("Text File (*.h *.c *.inc *.txt)", QFileDialog::AnyFile);
QString path = browse("Text File (*.h *.c *.inc *.txt)");
if (!path.isEmpty()) {
ui->config_layoutPath->setText(path);
}
} else {
QString path = browse("Binary (*.bin)", QFileDialog::AnyFile);
QString path = browse("Binary (*.bin)");
if (!path.isEmpty()) {
ui->config_layoutPath->setText(path);
}

View file

@ -7,7 +7,7 @@
#include "imageexport.h"
#include "config.h"
#include "shortcut.h"
#include <QFileDialog>
#include "filedialog.h"
#include <QMessageBox>
#include <QDialogButtonBox>
#include <QCloseEvent>
@ -637,15 +637,11 @@ void TilesetEditor::importTilesetTiles(Tileset *tileset, bool primary) {
QString descriptor = primary ? "primary" : "secondary";
QString descriptorCaps = primary ? "Primary" : "Secondary";
QString filepath = QFileDialog::getOpenFileName(
this,
QString("Import %1 Tileset Tiles Image").arg(descriptorCaps),
this->project->importExportPath,
"Image Files (*.png *.bmp *.jpg *.dib)");
QString filepath = FileDialog::getOpenFileName(this, QString("Import %1 Tileset Tiles Image").arg(descriptorCaps), "", "Image Files (*.png *.bmp *.jpg *.dib)");
if (filepath.isEmpty()) {
return;
}
this->project->setImportExportPath(filepath);
logInfo(QString("Importing %1 tileset tiles '%2'").arg(descriptor).arg(filepath));
// Read image data from buffer so that the built-in QImage doesn't try to detect file format
@ -698,15 +694,11 @@ void TilesetEditor::importTilesetTiles(Tileset *tileset, bool primary) {
msgBox.setIcon(QMessageBox::Icon::Warning);
msgBox.exec();
QString filepath = QFileDialog::getOpenFileName(
this,
QString("Select Palette for Tiles Image").arg(descriptorCaps),
this->project->importExportPath,
"Palette Files (*.pal *.act *tpl *gpl)");
QString filepath = FileDialog::getOpenFileName(this, "Select Palette for Tiles Image", "", "Palette Files (*.pal *.act *tpl *gpl)");
if (filepath.isEmpty()) {
return;
}
this->project->setImportExportPath(filepath);
bool error = false;
QList<QRgb> palette = PaletteUtil::parse(filepath, &error);
if (error) {
@ -939,10 +931,9 @@ void TilesetEditor::pasteMetatile(const Metatile * toPaste, QString newLabel)
void TilesetEditor::on_actionExport_Primary_Tiles_Image_triggered()
{
QString defaultName = QString("%1_Tiles_Pal%2").arg(this->primaryTileset->name).arg(this->paletteId);
QString defaultFilepath = QString("%1/%2.png").arg(this->project->importExportPath).arg(defaultName);
QString filepath = QFileDialog::getSaveFileName(this, "Export Primary Tiles Image", defaultFilepath, "Image Files (*.png)");
QString defaultFilepath = QString("%1/%2.png").arg(FileDialog::getDirectory()).arg(defaultName);
QString filepath = FileDialog::getSaveFileName(this, "Export Primary Tiles Image", defaultFilepath, "Image Files (*.png)");
if (!filepath.isEmpty()) {
this->project->setImportExportPath(filepath);
QImage image = this->tileSelector->buildPrimaryTilesIndexedImage();
exportIndexed4BPPPng(image, filepath);
}
@ -951,10 +942,9 @@ void TilesetEditor::on_actionExport_Primary_Tiles_Image_triggered()
void TilesetEditor::on_actionExport_Secondary_Tiles_Image_triggered()
{
QString defaultName = QString("%1_Tiles_Pal%2").arg(this->secondaryTileset->name).arg(this->paletteId);
QString defaultFilepath = QString("%1/%2.png").arg(this->project->importExportPath).arg(defaultName);
QString filepath = QFileDialog::getSaveFileName(this, "Export Secondary Tiles Image", defaultFilepath, "Image Files (*.png)");
QString defaultFilepath = QString("%1/%2.png").arg(FileDialog::getDirectory()).arg(defaultName);
QString filepath = FileDialog::getSaveFileName(this, "Export Secondary Tiles Image", defaultFilepath, "Image Files (*.png)");
if (!filepath.isEmpty()) {
this->project->setImportExportPath(filepath);
QImage image = this->tileSelector->buildSecondaryTilesIndexedImage();
exportIndexed4BPPPng(image, filepath);
}
@ -963,10 +953,9 @@ void TilesetEditor::on_actionExport_Secondary_Tiles_Image_triggered()
void TilesetEditor::on_actionExport_Primary_Metatiles_Image_triggered()
{
QString defaultName = QString("%1_Metatiles").arg(this->primaryTileset->name);
QString defaultFilepath = QString("%1/%2.png").arg(this->project->importExportPath).arg(defaultName);
QString filepath = QFileDialog::getSaveFileName(this, "Export Primary Metatiles Image", defaultFilepath, "Image Files (*.png)");
QString defaultFilepath = QString("%1/%2.png").arg(FileDialog::getDirectory()).arg(defaultName);
QString filepath = FileDialog::getSaveFileName(this, "Export Primary Metatiles Image", defaultFilepath, "Image Files (*.png)");
if (!filepath.isEmpty()) {
this->project->setImportExportPath(filepath);
QImage image = this->metatileSelector->buildPrimaryMetatilesImage();
image.save(filepath, "PNG");
}
@ -975,10 +964,9 @@ void TilesetEditor::on_actionExport_Primary_Metatiles_Image_triggered()
void TilesetEditor::on_actionExport_Secondary_Metatiles_Image_triggered()
{
QString defaultName = QString("%1_Metatiles").arg(this->secondaryTileset->name);
QString defaultFilepath = QString("%1/%2.png").arg(this->project->importExportPath).arg(defaultName);
QString filepath = QFileDialog::getSaveFileName(this, "Export Secondary Metatiles Image", defaultFilepath, "Image Files (*.png)");
QString defaultFilepath = QString("%1/%2.png").arg(FileDialog::getDirectory()).arg(defaultName);
QString filepath = FileDialog::getSaveFileName(this, "Export Secondary Metatiles Image", defaultFilepath, "Image Files (*.png)");
if (!filepath.isEmpty()) {
this->project->setImportExportPath(filepath);
QImage image = this->metatileSelector->buildSecondaryMetatilesImage();
image.save(filepath, "PNG");
}
@ -998,15 +986,11 @@ void TilesetEditor::importTilesetMetatiles(Tileset *tileset, bool primary)
{
QString descriptorCaps = primary ? "Primary" : "Secondary";
QString filepath = QFileDialog::getOpenFileName(
this,
QString("Import %1 Tileset Metatiles from Advance Map 1.92").arg(descriptorCaps),
this->project->importExportPath,
"Advance Map 1.92 Metatile Files (*.bvd)");
QString filepath = FileDialog::getOpenFileName(this, QString("Import %1 Tileset Metatiles from Advance Map 1.92").arg(descriptorCaps), "", "Advance Map 1.92 Metatile Files (*.bvd)");
if (filepath.isEmpty()) {
return;
}
this->project->setImportExportPath(filepath);
bool error = false;
QList<Metatile*> metatiles = MetatileParser::parse(filepath, &error, primary);
if (error) {