Merge remote-tracking branch 'origin/master' into fix827
# Conflicts: # src/mainwindow.cpp
This commit is contained in:
commit
d63bebee69
43 changed files with 2263 additions and 271 deletions
40
.github/workflows/main.yml
vendored
Normal file
40
.github/workflows/main.yml
vendored
Normal file
|
@ -0,0 +1,40 @@
|
|||
name: Build Porymap
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Cache Qt
|
||||
id: cache-qt
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ../Qt
|
||||
key: ${{ runner.os }}-QtCache
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v2
|
||||
with:
|
||||
version: '5.14.2'
|
||||
modules: 'qtwidgets qtqml'
|
||||
cached: ${{ steps.cache-qt.outputs.cache-hit }}
|
||||
|
||||
- name: Configure
|
||||
run: qmake porymap.pro
|
||||
|
||||
- name: Compile
|
||||
run: make
|
|
@ -17,6 +17,9 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
|
|||
- All plain text boxes now have a clear button to delete the text.
|
||||
- The window sizes and positions of the tileset editor, palette editor, and region map editor are now stored in `porymap.cfg`.
|
||||
- Add ruler tool for measuring metatile distance in events tab (Right-click to turn on/off, left-click to lock in place).
|
||||
- Add delete button to wild pokemon encounters tab.
|
||||
- Add shortcut customization via `Options -> Edit Shortcuts`.
|
||||
- Add custom text editor commands in `Options -> Edit Preferences`, a tool-button next to the `Script` combo-box, and `Tools -> Open Project in Text Editor`. The tool-button will open the containing file to the cooresponding script.
|
||||
|
||||
### Changed
|
||||
- Holding `shift` now toggles "Smart Path" drawing; when the "Smart Paths" checkbox is checked, holding `shift` will temporarily disable it.
|
||||
|
@ -27,6 +30,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
|
|||
- Fix porymap icon not showing on window or panel on Linux.
|
||||
- The main window can now be resized to fit on lower resolution displays.
|
||||
- Zooming the map in/out will now focus on the cursor.
|
||||
- Fix bug where object event sprites whose name contained a 0 character would display the placeholder "N" picture.
|
||||
|
||||
## [4.3.1] - 2020-07-17
|
||||
### Added
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# porymap
|
||||
|
||||
[![Actions Status](https://github.com/huderlem/porymap/workflows/Build%20Porymap/badge.svg)](https://github.com/huderlem/porymap/actions)
|
||||
|
||||
A map editor for the generation 3 decompilation projects using Qt.
|
||||
|
||||
Currently supports [pokeruby][pokeruby], [pokeemerald][pokeemerald], and [pokefirered][pokefirered].
|
||||
|
|
|
@ -225,11 +225,17 @@ Respawn NPC
|
|||
Open Map Scripts
|
||||
----------------
|
||||
|
||||
Clicking the ``Open Map Scripts`` button |open-map-scripts-button| will open the map's scripts file in your default text editor. If nothing happens when this button is clicked, you may need to associate a text editor with the `.inc` file extension.
|
||||
Clicking the ``Open Map Scripts`` button |open-map-scripts-button| will open the map's scripts file in your default text editor. If nothing happens when this button is clicked, you may need to associate a text editor with the `.inc` file extension (or `.pory` if you're using Porycript).
|
||||
|
||||
Additionally, if you specify a ``Goto Line Command`` in *Options -> Edit Preferences* then a tool-button will appear next to the `Script` combo-box in the *Events* tab. Clicking this button will open the file that contains the script directly to the line number of that script. If the script cannot be found in the project then the current map's scripts file is opened.
|
||||
|go-to-script-button|
|
||||
|
||||
.. |open-map-scripts-button|
|
||||
image:: images/editing-map-events/open-map-scripts-button.png
|
||||
|
||||
.. |go-to-script-button|
|
||||
image:: images/editing-map-events/go-to-script-button.png
|
||||
|
||||
Tool Buttons
|
||||
------------
|
||||
|
||||
|
@ -260,7 +266,7 @@ Shift
|
|||
Ruler Tool
|
||||
----------
|
||||
|
||||
The Ruler Tool provides a convenient way to measure distance on the map. This is particularly useful for scripting object movement. With the Pointer Tool selected you can activate the ruler with a Right-click. With the ruler active you can drag the mouse around to extend the ruler. The ruler can be deactivated with another Right-click, or locked in place with a Left-click (Left-click again to unlock the ruler). The dimensions of the ruler are displayed in a tool-tip and in the status bar in the bottom left corner of the widnow.
|
||||
The Ruler Tool provides a convenient way to measure distance on the map. This is particularly useful for scripting object movement. With the Pointer Tool selected you can activate the ruler with a Right-click. With the ruler active you can move the mouse around to extend the ruler. The ruler can be deactivated with another Right-click, or locked in place with a Left-click (Left-click again to unlock the ruler).
|
||||
|
||||
.. figure:: images/editing-map-events/event-tool-ruler.gif
|
||||
:alt: Measuring metatile distance with the Ruler Tool
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 231 KiB After Width: | Height: | Size: 206 KiB |
BIN
docsrc/manual/images/editing-map-events/go-to-script-button.png
Normal file
BIN
docsrc/manual/images/editing-map-events/go-to-script-button.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
BIN
docsrc/manual/images/shortcuts/edit-shortcuts.gif
Normal file
BIN
docsrc/manual/images/shortcuts/edit-shortcuts.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 591 KiB |
|
@ -31,6 +31,8 @@ determined by this file.
|
|||
``monitor_files``, 1, global, yes, Whether porymap will monitor changes to project files
|
||||
``region_map_dimensions``, 32x20, global, yes, The dimensions of the region map tilemap
|
||||
``theme``, default, global, yes, The color theme for porymap windows and widgets
|
||||
``text_editor_goto_line``, , global, yes, The command that will be executed when clicking the button next the ``Script`` combo-box.
|
||||
``text_editor_open_directory``, , global, yes, The command that will be executed when clicking ``Open Project in Text Editor``.
|
||||
``base_game_version``, , project, no, The base pret repo for this project
|
||||
``use_encounter_json``, 1, project, yes, Enables wild encounter table editing
|
||||
``use_poryscript``, 0, project, yes, Whether to open .pory files for scripts
|
||||
|
|
|
@ -2,8 +2,16 @@
|
|||
Shortcuts
|
||||
*********
|
||||
|
||||
Porymap has many shortcuts and it can sometimes be hard to keep track of them all.
|
||||
Here is a comprehensive list of the shortcuts provided all in one place for your convenience.
|
||||
Porymap has many keyboard shortcuts set by default, and even more that can be customized yourself. You can view and customize your shortcuts by going to *Options -> Edit Shortcuts*. Shortcut actions are grouped together by the window that they are used in (Main Window, Tileset Editor...). You can set up to 2 shortcuts per action, but you cannot have duplicate shortcuts set within the same window. If you enter a shortcut that is already in use, Porymap will prompt you cancel or overwrite the old shortcut. You can also restore your shortcuts to the defaults.
|
||||
|
||||
.. figure:: images/shortcuts/edit-shortcuts.gif
|
||||
:alt: Edit Shortcuts
|
||||
|
||||
Edit Shortcuts
|
||||
|
||||
Your shortcuts are stored at ``%Appdata%\pret\porymap\porymap.shortcuts.cfg`` on Windows and ``~/Library/Application\ Support/pret/porymap/porymap.shortcuts.cfg`` on macOS).
|
||||
|
||||
For reference, here is a comprehensive list of the default shortcuts set in Porymap.
|
||||
|
||||
Main Window
|
||||
-----------
|
||||
|
@ -33,6 +41,7 @@ Main Window
|
|||
Open New Tileset Dialog, ``Ctrl+Shift+N``
|
||||
Open Tileset Editor, ``Ctrl+T``
|
||||
Open Region Map Editor, ``Ctrl+M``
|
||||
Edit Preferences, ``Ctrl+,``
|
||||
|
||||
.. csv-table::
|
||||
:header: Map Editing
|
||||
|
|
|
@ -2527,6 +2527,23 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_DeleteWildMonGroup">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Delete a group of wild pokemon data on this map.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/images.qrc">
|
||||
<normaloff>:/icons/delete.ico</normaloff>:/icons/delete.ico</iconset>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_9">
|
||||
<property name="orientation">
|
||||
|
@ -2607,8 +2624,6 @@
|
|||
<addaction name="actionCursor_Tile_Outline"/>
|
||||
<addaction name="actionPlayer_View_Rectangle"/>
|
||||
<addaction name="actionBetter_Cursors"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionThemes"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuTools">
|
||||
<property name="title">
|
||||
|
@ -2625,6 +2640,8 @@
|
|||
<addaction name="actionNew_Tileset"/>
|
||||
<addaction name="actionTileset_Editor"/>
|
||||
<addaction name="actionRegion_Map_Editor"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionOpen_Project_in_Text_Editor"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuHelp">
|
||||
<property name="title">
|
||||
|
@ -2639,6 +2656,9 @@
|
|||
<addaction name="actionUse_Encounter_Json"/>
|
||||
<addaction name="actionMonitor_Project_Files"/>
|
||||
<addaction name="actionUse_Poryscript"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionEdit_Preferences"/>
|
||||
<addaction name="actionEdit_Shortcuts"/>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
<addaction name="menuEdit"/>
|
||||
|
@ -2895,16 +2915,29 @@
|
|||
<string>Ctrl+Shift+N</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionThemes">
|
||||
<property name="text">
|
||||
<string>Themes...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionExport_Stitched_Map_Image">
|
||||
<property name="text">
|
||||
<string>Export Map Stitch Image...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionEdit_Preferences">
|
||||
<property name="text">
|
||||
<string>Edit Preferences...</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+,</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionOpen_Project_in_Text_Editor">
|
||||
<property name="text">
|
||||
<string>Open Project in Text Editor</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionEdit_Shortcuts">
|
||||
<property name="text">
|
||||
<string>Edit Shortcuts...</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<customwidgets>
|
||||
|
|
190
forms/preferenceeditor.ui
Normal file
190
forms/preferenceeditor.ui
Normal file
|
@ -0,0 +1,190 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>PreferenceEditor</class>
|
||||
<widget class="QMainWindow" name="PreferenceEditor">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>600</width>
|
||||
<height>480</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Preferences</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_Themes">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Application Theme</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_TextEditor">
|
||||
<property name="title">
|
||||
<string>Preferred Text Editor</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="2" column="0">
|
||||
<widget class="QScrollArea" name="scrollArea_TextEditor">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents_TextEditor">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>582</width>
|
||||
<height>372</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetMinimumSize</enum>
|
||||
</property>
|
||||
<item row="6" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_TextEditorGotoLineHelp">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>When this command is set a button will appear next to the <span style=" font-weight:600; font-style:italic;">Script</span> combo-box in the <span style=" font-weight:600; font-style:italic;">Events</span> tab which executes this command.<span style=" font-weight:600;"> %F</span> will be substituted with the file path of the script and <span style=" font-weight:600;">%L</span> will be substituted with the line number of the script in that file. <span style=" font-weight:600;">%F </span><span style=" font-style:italic;">must</span> be given if <span style=" font-weight:600;">%L</span> is given. If <span style=" font-weight:600;">%F</span> is <span style=" font-style:italic;">not</span> given then the script's file path will be added to the end of the command. If the script can't be found then the current map's scripts file is opened.</p></body></html></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_TextEditorGotoLine">
|
||||
<property name="text">
|
||||
<string>Goto Line Command</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_TextEditorOpenFolder">
|
||||
<property name="toolTip">
|
||||
<string>The shell command for your preferred text editor (possibly an absolute path if the program doesn't exist in your PATH).</string>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>e.g. code %D</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_TextEditorOpenFolderHelp">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>This is the command that is executed when clicking <span style=" font-weight:600; font-style:italic;">Open Project in Text Editor</span> in the <span style=" font-weight:600; font-style:italic;">Tools</span> menu. <span style=" font-weight:600;">%D</span> will be substituted with the project's root directory. If <span style=" font-weight:600;">%D</span> is <span style=" font-style:italic;">not</span> specified then the project directory will be added to the end of the command.</p></body></html></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_TextEditorOpenFolder">
|
||||
<property name="text">
|
||||
<string>Open Directory Command</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_TextEditorGotoLine">
|
||||
<property name="toolTip">
|
||||
<string>The shell command for your preferred text editor to open a file to a specific line number (possibly an absolute path if the program doesn't exist in your PATH).</string>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>e.g. code --goto %F:%L</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>15</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
89
forms/shortcutseditor.ui
Normal file
89
forms/shortcutseditor.ui
Normal file
|
@ -0,0 +1,89 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ShortcutsEditor</class>
|
||||
<widget class="QMainWindow" name="ShortcutsEditor">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>700</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Shortcuts Editor</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QScrollArea" name="scrollArea_Shortcuts">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents_Shortcuts">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>794</width>
|
||||
<height>642</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="frame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Box</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults</set>
|
||||
</property>
|
||||
<property name="centerButtons">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -6,6 +6,8 @@
|
|||
#include <QObject>
|
||||
#include <QByteArrayList>
|
||||
#include <QSize>
|
||||
#include <QKeySequence>
|
||||
#include <QMultiMap>
|
||||
|
||||
enum MapSortOrder {
|
||||
Group = 0,
|
||||
|
@ -46,6 +48,8 @@ public:
|
|||
this->monitorFiles = true;
|
||||
this->regionMapDimensions = QSize(32, 20);
|
||||
this->theme = "default";
|
||||
this->textEditorOpenFolder = "";
|
||||
this->textEditorGotoLine = "";
|
||||
}
|
||||
void setRecentProject(QString project);
|
||||
void setRecentMap(QString map);
|
||||
|
@ -62,6 +66,8 @@ public:
|
|||
void setMonitorFiles(bool monitor);
|
||||
void setRegionMapDimensions(int width, int height);
|
||||
void setTheme(QString theme);
|
||||
void setTextEditorOpenFolder(const QString &command);
|
||||
void setTextEditorGotoLine(const QString &command);
|
||||
QString getRecentProject();
|
||||
QString getRecentMap();
|
||||
MapSortOrder getMapSortOrder();
|
||||
|
@ -77,6 +83,8 @@ public:
|
|||
bool getMonitorFiles();
|
||||
QSize getRegionMapDimensions();
|
||||
QString getTheme();
|
||||
QString getTextEditorOpenFolder();
|
||||
QString getTextEditorGotoLine();
|
||||
protected:
|
||||
virtual QString getConfigFilepath() override;
|
||||
virtual void parseConfigKeyValue(QString key, QString value) override;
|
||||
|
@ -108,6 +116,8 @@ private:
|
|||
bool monitorFiles;
|
||||
QSize regionMapDimensions;
|
||||
QString theme;
|
||||
QString textEditorOpenFolder;
|
||||
QString textEditorGotoLine;
|
||||
};
|
||||
|
||||
extern PorymapConfig porymapConfig;
|
||||
|
@ -193,4 +203,53 @@ private:
|
|||
|
||||
extern ProjectConfig projectConfig;
|
||||
|
||||
class QAction;
|
||||
class Shortcut;
|
||||
|
||||
class ShortcutsConfig : public KeyValueConfigBase
|
||||
{
|
||||
public:
|
||||
ShortcutsConfig() :
|
||||
user_shortcuts({ }),
|
||||
default_shortcuts({ })
|
||||
{ }
|
||||
|
||||
virtual void reset() override { user_shortcuts.clear(); }
|
||||
|
||||
// Call this before applying user shortcuts so that the user can restore defaults.
|
||||
void setDefaultShortcuts(const QObjectList &objects);
|
||||
QList<QKeySequence> defaultShortcuts(const QObject *object) const;
|
||||
|
||||
void setUserShortcuts(const QObjectList &objects);
|
||||
void setUserShortcuts(const QMultiMap<const QObject *, QKeySequence> &objects_keySequences);
|
||||
QList<QKeySequence> userShortcuts(const QObject *object) const;
|
||||
|
||||
protected:
|
||||
virtual QString getConfigFilepath() override;
|
||||
virtual void parseConfigKeyValue(QString key, QString value) override;
|
||||
virtual QMap<QString, QString> getKeyValueMap() override;
|
||||
virtual void onNewConfigFileCreated() override { };
|
||||
virtual void setUnreadKeys() override { };
|
||||
|
||||
private:
|
||||
QMultiMap<QString, QKeySequence> user_shortcuts;
|
||||
QMultiMap<QString, QKeySequence> default_shortcuts;
|
||||
|
||||
enum StoreType {
|
||||
User,
|
||||
Default
|
||||
};
|
||||
|
||||
QString cfgKey(const QObject *object) const;
|
||||
QList<QKeySequence> currentShortcuts(const QObject *object) const;
|
||||
|
||||
void storeShortcutsFromList(StoreType storeType, const QObjectList &objects);
|
||||
void storeShortcuts(
|
||||
StoreType storeType,
|
||||
const QString &cfgKey,
|
||||
const QList<QKeySequence> &keySequences);
|
||||
};
|
||||
|
||||
extern ShortcutsConfig shortcutsConfig;
|
||||
|
||||
#endif // CONFIG_H
|
||||
|
|
|
@ -32,10 +32,10 @@ public:
|
|||
Event(const Event&);
|
||||
Event(QJsonObject, QString);
|
||||
public:
|
||||
int x() {
|
||||
int x() const {
|
||||
return getInt("x");
|
||||
}
|
||||
int y() {
|
||||
int y() const {
|
||||
return getInt("y");
|
||||
}
|
||||
int elevation() {
|
||||
|
@ -47,16 +47,16 @@ public:
|
|||
void setY(int y) {
|
||||
put("y", y);
|
||||
}
|
||||
QString get(QString key) {
|
||||
QString get(const QString &key) const {
|
||||
return values.value(key);
|
||||
}
|
||||
int getInt(QString key) {
|
||||
int getInt(const QString &key) const {
|
||||
return values.value(key).toInt(nullptr, 0);
|
||||
}
|
||||
uint16_t getU16(QString key) {
|
||||
uint16_t getU16(const QString &key) const {
|
||||
return values.value(key).toUShort(nullptr, 0);
|
||||
}
|
||||
int16_t getS16(QString key) {
|
||||
int16_t getS16(const QString &key) const {
|
||||
return values.value(key).toShort(nullptr, 0);
|
||||
}
|
||||
void put(QString key, int value) {
|
||||
|
|
|
@ -84,7 +84,8 @@ public:
|
|||
void floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation);
|
||||
void _floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation);
|
||||
void magicFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation);
|
||||
QList<Event*> getAllEvents();
|
||||
QList<Event*> getAllEvents() const;
|
||||
QStringList eventScriptLabels(const QString &event_group_type = QString()) const;
|
||||
void removeEvent(Event*);
|
||||
void addEvent(Event*);
|
||||
QPixmap renderConnection(MapConnection, MapLayout *);
|
||||
|
|
|
@ -41,7 +41,7 @@ class ParseUtil
|
|||
public:
|
||||
ParseUtil();
|
||||
void set_root(QString);
|
||||
QString readTextFile(QString);
|
||||
static QString readTextFile(QString);
|
||||
void strip_comment(QString*);
|
||||
QList<QStringList>* parseAsm(QString);
|
||||
int evaluateDefine(QString, QMap<QString, int>*);
|
||||
|
@ -55,6 +55,17 @@ public:
|
|||
bool tryParseJsonFile(QJsonDocument *out, QString filepath);
|
||||
bool ensureFieldsExist(QJsonObject obj, QList<QString> fields);
|
||||
|
||||
// Returns the 1-indexed line number for the definition of scriptLabel in the scripts file at filePath.
|
||||
// Returns 0 if a definition for scriptLabel cannot be found.
|
||||
static int getScriptLineNumber(const QString &filePath, const QString &scriptLabel);
|
||||
static int getRawScriptLineNumber(QString text, const QString &scriptLabel);
|
||||
static int getPoryScriptLineNumber(QString text, const QString &scriptLabel);
|
||||
static QString &removeStringLiterals(QString &text);
|
||||
static QString &removeLineComments(QString &text, const QString &commentSymbol);
|
||||
static QString &removeLineComments(QString &text, const QStringList &commentSymbols);
|
||||
|
||||
static QStringList splitShellCommand(QStringView command);
|
||||
|
||||
private:
|
||||
QString root;
|
||||
QString text;
|
||||
|
|
|
@ -66,7 +66,6 @@ public:
|
|||
void displayMapBorder();
|
||||
void displayMapGrid();
|
||||
void displayWildMonTables();
|
||||
void maskNonVisibleConnectionTiles();
|
||||
|
||||
void updateMapBorder();
|
||||
void updateMapConnections();
|
||||
|
@ -85,6 +84,7 @@ public:
|
|||
void addNewConnection();
|
||||
void removeCurrentConnection();
|
||||
void addNewWildMonGroup(QWidget *window);
|
||||
void deleteWildMonGroup();
|
||||
void updateDiveMap(QString mapName);
|
||||
void updateEmergeMap(QString mapName);
|
||||
void setSelectedConnectionFromMap(QString mapName);
|
||||
|
@ -155,6 +155,12 @@ public:
|
|||
void shouldReselectEvents();
|
||||
void scaleMapView(int);
|
||||
|
||||
public slots:
|
||||
void openMapScripts() const;
|
||||
void openScript(const QString &scriptLabel) const;
|
||||
void openProjectInTextEditor() const;
|
||||
void maskNonVisibleConnectionTiles();
|
||||
|
||||
private:
|
||||
void setConnectionItemsVisible(bool);
|
||||
void setBorderItemsVisible(bool, qreal = 1);
|
||||
|
@ -182,6 +188,10 @@ private:
|
|||
QString getMovementPermissionText(uint16_t collision, uint16_t elevation);
|
||||
QString getMetatileDisplayMessage(uint16_t metatileId);
|
||||
bool eventLimitReached(Map *, QString);
|
||||
void openInTextEditor(const QString &path, int lineNum = 0) const;
|
||||
bool startDetachedProcess(const QString &command,
|
||||
const QString &workingDirectory = QString(),
|
||||
qint64 *pid = nullptr) const;
|
||||
|
||||
private slots:
|
||||
void onMapStartPaint(QGraphicsSceneMouseEvent *event, MapPixmapItem *item);
|
||||
|
@ -205,7 +215,6 @@ private slots:
|
|||
void onHoveredMapMovementPermissionCleared();
|
||||
void onSelectedMetatilesChanged();
|
||||
void onWheelZoom(int);
|
||||
void onMapRulerLengthChanged();
|
||||
|
||||
signals:
|
||||
void objectsChanged();
|
||||
|
@ -214,6 +223,7 @@ signals:
|
|||
void wildMonDataChanged();
|
||||
void warpEventDoubleClicked(QString mapName, QString warpNum);
|
||||
void currentMetatilesSelectionChanged();
|
||||
void mapRulerStatusChanged(const QString &);
|
||||
};
|
||||
|
||||
#endif // EDITOR_H
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
#include "filterchildrenproxymodel.h"
|
||||
#include "newmappopup.h"
|
||||
#include "newtilesetdialog.h"
|
||||
#include "shortcutseditor.h"
|
||||
#include "preferenceeditor.h"
|
||||
|
||||
namespace Ui {
|
||||
class MainWindow;
|
||||
|
@ -119,8 +121,6 @@ private slots:
|
|||
|
||||
void duplicate();
|
||||
|
||||
void openInTextEditor();
|
||||
|
||||
void onLoadMapRequested(QString, QString);
|
||||
void onMapChanged(Map *map);
|
||||
void onMapNeedsRedrawing();
|
||||
|
@ -129,6 +129,8 @@ private slots:
|
|||
void openNewMapPopupWindow(int, QVariant);
|
||||
void onNewMapCreated();
|
||||
void onMapCacheCleared();
|
||||
void onMapRulerStatusChanged(const QString &);
|
||||
void applyUserShortcuts();
|
||||
|
||||
void on_action_NewMap_triggered();
|
||||
void on_actionNew_Tileset_triggered();
|
||||
|
@ -148,6 +150,7 @@ private slots:
|
|||
void on_actionUse_Encounter_Json_triggered(bool checked);
|
||||
void on_actionMonitor_Project_Files_triggered(bool checked);
|
||||
void on_actionUse_Poryscript_triggered(bool checked);
|
||||
void on_actionEdit_Shortcuts_triggered();
|
||||
|
||||
void on_mainTabBar_tabBarClicked(int index);
|
||||
|
||||
|
@ -164,7 +167,6 @@ private slots:
|
|||
void on_actionMap_Shift_triggered();
|
||||
|
||||
void on_toolButton_deleteObject_clicked();
|
||||
void on_toolButton_Open_Scripts_clicked();
|
||||
|
||||
void addNewEvent(QString);
|
||||
void updateSelectedObjects();
|
||||
|
@ -208,6 +210,7 @@ private slots:
|
|||
|
||||
void on_lineEdit_filterBox_textChanged(const QString &arg1);
|
||||
|
||||
void moveEvent(QMoveEvent *event);
|
||||
void closeEvent(QCloseEvent *);
|
||||
|
||||
void eventTabChanged(int index);
|
||||
|
@ -218,23 +221,28 @@ private slots:
|
|||
void on_toolButton_ExpandAll_clicked();
|
||||
void on_toolButton_CollapseAll_clicked();
|
||||
void on_actionAbout_Porymap_triggered();
|
||||
void on_actionThemes_triggered();
|
||||
void on_pushButton_AddCustomHeaderField_clicked();
|
||||
void on_pushButton_DeleteCustomHeaderField_clicked();
|
||||
void on_tableWidget_CustomHeaderFields_cellChanged(int row, int column);
|
||||
void on_horizontalSlider_MetatileZoom_valueChanged(int value);
|
||||
void on_pushButton_NewWildMonGroup_clicked();
|
||||
void on_pushButton_DeleteWildMonGroup_clicked();
|
||||
void on_pushButton_ConfigureEncountersJSON_clicked();
|
||||
|
||||
void on_actionRegion_Map_Editor_triggered();
|
||||
void on_actionEdit_Preferences_triggered();
|
||||
void togglePreferenceSpecificUi();
|
||||
|
||||
private:
|
||||
Ui::MainWindow *ui;
|
||||
QLabel *label_MapRulerStatus;
|
||||
TilesetEditor *tilesetEditor = nullptr;
|
||||
RegionMapEditor *regionMapEditor = nullptr;
|
||||
ShortcutsEditor *shortcutsEditor = nullptr;
|
||||
MapImageExporter *mapImageExporter = nullptr;
|
||||
FilterChildrenProxyModel *mapListProxyModel;
|
||||
NewMapPopup *newmapprompt = nullptr;
|
||||
PreferenceEditor *preferenceEditor = nullptr;
|
||||
QStandardItemModel *mapListModel;
|
||||
QList<QStandardItem*> *mapGroupItemsList;
|
||||
QMap<QString, QModelIndex> mapListIndexes;
|
||||
|
@ -260,6 +268,7 @@ private:
|
|||
DraggablePixmapItem *selectedHealspot;
|
||||
|
||||
QList<QAction *> registeredActions;
|
||||
QVector<QToolButton *> openScriptButtons;
|
||||
|
||||
bool isProgrammaticEventTabChange;
|
||||
bool projectHasUnsavedChanges;
|
||||
|
@ -291,11 +300,12 @@ private:
|
|||
|
||||
void initWindow();
|
||||
void initCustomUI();
|
||||
void initExtraShortcuts();
|
||||
void initExtraSignals();
|
||||
void initEditor();
|
||||
void initMiscHeapObjects();
|
||||
void initMapSortOrder();
|
||||
void initShortcuts();
|
||||
void initExtraShortcuts();
|
||||
void setProjectSpecificUIVisibility();
|
||||
void loadUserSettings();
|
||||
void applyMapListFilter(QString filterText);
|
||||
|
@ -307,9 +317,16 @@ private:
|
|||
void closeSupplementaryWindows();
|
||||
void setWindowDisabled(bool);
|
||||
|
||||
void initTilesetEditor();
|
||||
bool initRegionMapEditor();
|
||||
void initShortcutsEditor();
|
||||
void connectSubEditorsToShortcutsEditor();
|
||||
|
||||
bool isProjectOpen();
|
||||
void showExportMapImageWindow(bool stitchMode);
|
||||
void redrawMetatileSelection();
|
||||
|
||||
QObjectList shortcutableObjects() const;
|
||||
};
|
||||
|
||||
enum MapListUserRoles {
|
||||
|
|
|
@ -173,8 +173,10 @@ public:
|
|||
QString fixPalettePath(QString path);
|
||||
QString fixGraphicPath(QString path);
|
||||
|
||||
QString getScriptFileExtension(bool usePoryScript);
|
||||
QString getScriptDefaultString(bool usePoryScript, QString mapName);
|
||||
QString getScriptFileExtension(bool usePoryScript) const;
|
||||
QString getScriptDefaultString(bool usePoryScript, QString mapName) const;
|
||||
QString getMapScriptsFilePath(const QString &mapName) const;
|
||||
QStringList getEventScriptsFilePaths() const;
|
||||
|
||||
bool loadMapBorder(Map *map);
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ protected:
|
|||
void mouseMoveEvent(QMouseEvent *event);
|
||||
void mouseReleaseEvent(QMouseEvent *event);
|
||||
void drawForeground(QPainter *painter, const QRectF &rect);
|
||||
void moveEvent(QMoveEvent *event);
|
||||
};
|
||||
|
||||
//Q_DECLARE_METATYPE(GraphicsView)
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
#define MAPRULER_H
|
||||
|
||||
#include <QGraphicsObject>
|
||||
#include <QPainter>
|
||||
#include <QColor>
|
||||
#include <QLine>
|
||||
|
||||
|
||||
class MapRuler : public QGraphicsObject, private QLine
|
||||
|
@ -11,20 +10,14 @@ class MapRuler : public QGraphicsObject, private QLine
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MapRuler(QColor innerColor = Qt::yellow, QColor borderColor = Qt::black) :
|
||||
innerColor(innerColor),
|
||||
borderColor(borderColor),
|
||||
mapSize(QSize())
|
||||
{
|
||||
init();
|
||||
}
|
||||
void init();
|
||||
// thickness is given in scene pixels
|
||||
MapRuler(int thickness, QColor innerColor = Qt::yellow, QColor borderColor = Qt::black);
|
||||
|
||||
QRectF boundingRect() const override;
|
||||
QPainterPath shape() const override;
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override;
|
||||
bool eventFilter(QObject *, QEvent *event) override;
|
||||
|
||||
void setEnabled(bool enabled);
|
||||
bool isAnchored() const { return anchored; }
|
||||
bool isLocked() const { return locked; }
|
||||
|
||||
|
@ -45,37 +38,33 @@ public:
|
|||
// Ruler height in metatiles
|
||||
int height() const { return qAbs(deltaY()); }
|
||||
|
||||
QString statusMessage;
|
||||
|
||||
public slots:
|
||||
void mouseEvent(QGraphicsSceneMouseEvent *event);
|
||||
void setMapDimensions(const QSize &size);
|
||||
|
||||
signals:
|
||||
void statusChanged(const QString &statusMessage);
|
||||
|
||||
private:
|
||||
QColor innerColor;
|
||||
QColor borderColor;
|
||||
const int thickness;
|
||||
const qreal half_thickness;
|
||||
const QColor innerColor;
|
||||
const QColor borderColor;
|
||||
QSize mapSize;
|
||||
QRect xRuler;
|
||||
QRect yRuler;
|
||||
QRectF xRuler;
|
||||
QRectF yRuler;
|
||||
QLineF cornerTick;
|
||||
bool anchored;
|
||||
bool locked;
|
||||
|
||||
static int thickness;
|
||||
|
||||
void reset();
|
||||
void setAnchor(const QPointF &scenePos);
|
||||
void setEndPos(const QPointF &scenePos);
|
||||
QPoint snapToWithinBounds(QPoint pos) const;
|
||||
void setAnchor(const QPointF &scenePos, const QPoint &screenPos);
|
||||
void endAnchor();
|
||||
void setEndPos(const QPointF &scenePos, const QPoint &screenPos);
|
||||
void showDimensions(const QPoint &screenPos) const;
|
||||
void hideDimensions() const;
|
||||
void updateGeometry();
|
||||
void updateStatus(Qt::Corner corner);
|
||||
int pixWidth() const { return width() * 16; }
|
||||
int pixHeight() const { return height() * 16; }
|
||||
|
||||
signals:
|
||||
void lengthChanged();
|
||||
void deactivated(const QPoint &endPos);
|
||||
};
|
||||
|
||||
#endif // MAPRULER_H
|
||||
|
|
52
include/ui/multikeyedit.h
Normal file
52
include/ui/multikeyedit.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
#ifndef MULTIKEYEDIT_H
|
||||
#define MULTIKEYEDIT_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QKeySequenceEdit>
|
||||
|
||||
class QLineEdit;
|
||||
|
||||
|
||||
// A collection of QKeySequenceEdit's laid out horizontally.
|
||||
class MultiKeyEdit : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MultiKeyEdit(QWidget *parent = nullptr, int fieldCount = 2);
|
||||
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
|
||||
int fieldCount() const;
|
||||
void setFieldCount(int count);
|
||||
QList<QKeySequence> keySequences() const;
|
||||
bool removeOne(const QKeySequence &keySequence);
|
||||
bool contains(const QKeySequence &keySequence) const;
|
||||
void setContextMenuPolicy(Qt::ContextMenuPolicy policy);
|
||||
bool isClearButtonEnabled() const;
|
||||
void setClearButtonEnabled(bool enable);
|
||||
|
||||
public slots:
|
||||
void clear();
|
||||
void setKeySequences(const QList<QKeySequence> &keySequences);
|
||||
void addKeySequence(const QKeySequence &keySequence);
|
||||
|
||||
signals:
|
||||
void keySequenceChanged(const QKeySequence &keySequence);
|
||||
void editingFinished();
|
||||
void customContextMenuRequested(const QPoint &pos);
|
||||
|
||||
private:
|
||||
QVector<QKeySequenceEdit *> keySequenceEdit_vec;
|
||||
QList<QKeySequence> keySequence_list; // Used to track changes
|
||||
|
||||
void addNewKeySequenceEdit();
|
||||
void alignKeySequencesLeft();
|
||||
void setFocusToLastNonEmptyKeySequenceEdit();
|
||||
|
||||
private slots:
|
||||
void onEditingFinished();
|
||||
void showDefaultContextMenu(QLineEdit *lineEdit, const QPoint &pos);
|
||||
};
|
||||
|
||||
#endif // MULTIKEYEDIT_H
|
37
include/ui/preferenceeditor.h
Normal file
37
include/ui/preferenceeditor.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
#ifndef PREFERENCES_H
|
||||
#define PREFERENCES_H
|
||||
|
||||
#include <QMainWindow>
|
||||
|
||||
class NoScrollComboBox;
|
||||
class QAbstractButton;
|
||||
|
||||
|
||||
namespace Ui {
|
||||
class PreferenceEditor;
|
||||
}
|
||||
|
||||
class PreferenceEditor : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PreferenceEditor(QWidget *parent = nullptr);
|
||||
~PreferenceEditor();
|
||||
|
||||
signals:
|
||||
void preferencesSaved();
|
||||
void themeChanged(const QString &theme);
|
||||
|
||||
private:
|
||||
Ui::PreferenceEditor *ui;
|
||||
NoScrollComboBox *themeSelector;
|
||||
|
||||
void populateFields();
|
||||
void saveFields();
|
||||
|
||||
private slots:
|
||||
void dialogButtonClicked(QAbstractButton *button);
|
||||
};
|
||||
|
||||
#endif // PREFERENCES_H
|
|
@ -47,6 +47,11 @@ public:
|
|||
|
||||
void resize(int width, int height);
|
||||
|
||||
QObjectList shortcutableObjects() const;
|
||||
|
||||
public slots:
|
||||
void applyUserShortcuts();
|
||||
|
||||
private:
|
||||
Ui::RegionMapEditor *ui;
|
||||
Project *project;
|
||||
|
@ -81,6 +86,7 @@ private:
|
|||
RegionMapPixmapItem *region_map_item = nullptr;
|
||||
CityMapPixmapItem *city_map_item = nullptr;
|
||||
|
||||
void initShortcuts();
|
||||
void displayRegionMap();
|
||||
void displayRegionMapImage();
|
||||
void displayRegionMapLayout();
|
||||
|
|
71
include/ui/shortcut.h
Normal file
71
include/ui/shortcut.h
Normal file
|
@ -0,0 +1,71 @@
|
|||
#ifndef SHORTCUT_H
|
||||
#define SHORTCUT_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QKeySequence>
|
||||
#include <QShortcut>
|
||||
|
||||
|
||||
// Alternative to QShortcut that adds support for multiple key sequences.
|
||||
// Use this to allow the shortcut to be editable in ShortcutsEditor.
|
||||
class Shortcut : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DECLARE_PRIVATE(QShortcut)
|
||||
Q_PROPERTY(QKeySequence key READ key WRITE setKey)
|
||||
Q_PROPERTY(QString whatsThis READ whatsThis WRITE setWhatsThis)
|
||||
Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled)
|
||||
Q_PROPERTY(bool autoRepeat READ autoRepeat WRITE setAutoRepeat)
|
||||
Q_PROPERTY(Qt::ShortcutContext context READ context WRITE setContext)
|
||||
|
||||
public:
|
||||
explicit Shortcut(QWidget *parent);
|
||||
Shortcut(const QKeySequence &key, QWidget *parent,
|
||||
const char *member = nullptr, const char *ambiguousMember = nullptr,
|
||||
Qt::ShortcutContext shortcutContext = Qt::WindowShortcut);
|
||||
Shortcut(const QList<QKeySequence> &keys, QWidget *parent,
|
||||
const char *member = nullptr, const char *ambiguousMember = nullptr,
|
||||
Qt::ShortcutContext shortcutContext = Qt::WindowShortcut);
|
||||
~Shortcut();
|
||||
|
||||
void addKey(const QKeySequence &key);
|
||||
void setKey(const QKeySequence &key);
|
||||
QKeySequence key() const;
|
||||
|
||||
void addKeys(const QList<QKeySequence> &keys);
|
||||
void setKeys(const QList<QKeySequence> &keys);
|
||||
QList<QKeySequence> keys() const;
|
||||
|
||||
void setEnabled(bool enable);
|
||||
bool isEnabled() const;
|
||||
|
||||
void setContext(Qt::ShortcutContext context);
|
||||
Qt::ShortcutContext context() const;
|
||||
|
||||
void setWhatsThis(const QString &text);
|
||||
QString whatsThis() const;
|
||||
|
||||
void setAutoRepeat(bool on);
|
||||
bool autoRepeat() const;
|
||||
|
||||
int id() const;
|
||||
QList<int> ids() const;
|
||||
|
||||
inline QWidget *parentWidget() const
|
||||
{ return static_cast<QWidget *>(QObject::parent()); }
|
||||
|
||||
signals:
|
||||
void activated();
|
||||
void activatedAmbiguously();
|
||||
|
||||
protected:
|
||||
bool event(QEvent *e) override;
|
||||
|
||||
private:
|
||||
const char *sc_member;
|
||||
const char *sc_ambiguousmember;
|
||||
Qt::ShortcutContext sc_context;
|
||||
QVector<QShortcut *> sc_vec;
|
||||
};
|
||||
|
||||
#endif // SHORTCUT_H
|
60
include/ui/shortcutseditor.h
Normal file
60
include/ui/shortcutseditor.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
#ifndef SHORTCUTSEDITOR_H
|
||||
#define SHORTCUTSEDITOR_H
|
||||
|
||||
#include "shortcut.h"
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QDialog>
|
||||
#include <QMap>
|
||||
#include <QHash>
|
||||
#include <QAction>
|
||||
|
||||
class QFormLayout;
|
||||
class MultiKeyEdit;
|
||||
class QAbstractButton;
|
||||
|
||||
|
||||
namespace Ui {
|
||||
class ShortcutsEditor;
|
||||
}
|
||||
|
||||
class ShortcutsEditor : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ShortcutsEditor(QWidget *parent = nullptr);
|
||||
explicit ShortcutsEditor(const QObjectList &shortcutableObjects, QWidget *parent = nullptr);
|
||||
~ShortcutsEditor();
|
||||
|
||||
void setShortcutableObjects(const QObjectList &shortcutableObjects);
|
||||
|
||||
signals:
|
||||
void shortcutsSaved();
|
||||
|
||||
private:
|
||||
Ui::ShortcutsEditor *ui;
|
||||
QWidget *main_container;
|
||||
QMultiMap<QString, const QObject *> labels_objects;
|
||||
QHash<QString, QFormLayout *> contexts_layouts;
|
||||
QHash<MultiKeyEdit *, const QObject *> multiKeyEdits_objects;
|
||||
|
||||
void parseObjectList(const QObjectList &objectList);
|
||||
QString getLabel(const QObject *object) const;
|
||||
bool stringPropertyIsNotEmpty(const QObject *object, const char *name) const;
|
||||
void populateMainContainer();
|
||||
QString getShortcutContext(const QObject *object) const;
|
||||
void addNewContextGroup(const QString &shortcutContext);
|
||||
void addNewMultiKeyEdit(const QObject *object, const QString &shortcutContext);
|
||||
QList<MultiKeyEdit *> siblings(MultiKeyEdit *multiKeyEdit) const;
|
||||
void promptUserOnDuplicateFound(MultiKeyEdit *current, MultiKeyEdit *sender);
|
||||
void removeKeySequence(const QKeySequence &keySequence, MultiKeyEdit *multiKeyEdit);
|
||||
void saveShortcuts();
|
||||
void resetShortcuts();
|
||||
|
||||
private slots:
|
||||
void checkForDuplicates(const QKeySequence &keySequence);
|
||||
void dialogButtonClicked(QAbstractButton *button);
|
||||
};
|
||||
|
||||
#endif // SHORTCUTSEDITOR_H
|
|
@ -42,6 +42,11 @@ public:
|
|||
void updateTilesets(QString primaryTilsetLabel, QString secondaryTilesetLabel);
|
||||
bool selectMetatile(uint16_t metatileId);
|
||||
|
||||
QObjectList shortcutableObjects() const;
|
||||
|
||||
public slots:
|
||||
void applyUserShortcuts();
|
||||
|
||||
private slots:
|
||||
void onHoveredMetatileChanged(uint16_t);
|
||||
void onHoveredMetatileCleared();
|
||||
|
@ -102,6 +107,8 @@ private:
|
|||
void initTileSelector();
|
||||
void initSelectedTileItem();
|
||||
void initMetatileLayersItem();
|
||||
void initShortcuts();
|
||||
void initExtraShortcuts();
|
||||
void restoreWindowState();
|
||||
void initMetatileHistory();
|
||||
void setTilesets(QString primaryTilesetLabel, QString secondaryTilesetLabel);
|
||||
|
@ -112,6 +119,7 @@ private:
|
|||
void refresh();
|
||||
void saveMetatileLabel();
|
||||
void closeEvent(QCloseEvent*);
|
||||
|
||||
Ui::TilesetEditor *ui;
|
||||
History<MetatileHistoryItem*> metatileHistory;
|
||||
TilesetEditorMetatileSelector *metatileSelector = nullptr;
|
||||
|
|
12
porymap.pro
12
porymap.pro
|
@ -71,6 +71,10 @@ SOURCES += src/core/block.cpp \
|
|||
src/ui/newtilesetdialog.cpp \
|
||||
src/ui/flowlayout.cpp \
|
||||
src/ui/mapruler.cpp \
|
||||
src/ui/shortcut.cpp \
|
||||
src/ui/shortcutseditor.cpp \
|
||||
src/ui/multikeyedit.cpp \
|
||||
src/ui/preferenceeditor.cpp \
|
||||
src/config.cpp \
|
||||
src/editor.cpp \
|
||||
src/main.cpp \
|
||||
|
@ -140,6 +144,10 @@ HEADERS += include/core/block.h \
|
|||
include/ui/overlay.h \
|
||||
include/ui/flowlayout.h \
|
||||
include/ui/mapruler.h \
|
||||
include/ui/shortcut.h \
|
||||
include/ui/shortcutseditor.h \
|
||||
include/ui/multikeyedit.h \
|
||||
include/ui/preferenceeditor.h \
|
||||
include/config.h \
|
||||
include/editor.h \
|
||||
include/mainwindow.h \
|
||||
|
@ -156,7 +164,9 @@ FORMS += forms/mainwindow.ui \
|
|||
forms/newmappopup.ui \
|
||||
forms/aboutporymap.ui \
|
||||
forms/newtilesetdialog.ui \
|
||||
forms/mapimageexporter.ui
|
||||
forms/mapimageexporter.ui \
|
||||
forms/shortcutseditor.ui \
|
||||
forms/preferenceeditor.ui
|
||||
|
||||
RESOURCES += \
|
||||
resources/images.qrc \
|
||||
|
|
156
src/config.cpp
156
src/config.cpp
|
@ -1,5 +1,6 @@
|
|||
#include "config.h"
|
||||
#include "log.h"
|
||||
#include "shortcut.h"
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFormLayout>
|
||||
|
@ -11,6 +12,8 @@
|
|||
#include <QTextStream>
|
||||
#include <QRegularExpression>
|
||||
#include <QStandardPaths>
|
||||
#include <QAction>
|
||||
#include <QAbstractButton>
|
||||
|
||||
KeyValueConfigBase::~KeyValueConfigBase() {
|
||||
|
||||
|
@ -36,7 +39,7 @@ void KeyValueConfigBase::load() {
|
|||
QTextStream in(&file);
|
||||
in.setCodec("UTF-8");
|
||||
QList<QString> configLines;
|
||||
QRegularExpression re("^(?<key>.+)=(?<value>.*)$");
|
||||
QRegularExpression re("^(?<key>[^=]+)=(?<value>.*)$");
|
||||
while (!in.atEnd()) {
|
||||
QString line = in.readLine().trimmed();
|
||||
int commentIndex = line.indexOf("#");
|
||||
|
@ -187,6 +190,10 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) {
|
|||
}
|
||||
} else if (key == "theme") {
|
||||
this->theme = value;
|
||||
} else if (key == "text_editor_open_directory") {
|
||||
this->textEditorOpenFolder = value;
|
||||
} else if (key == "text_editor_goto_line") {
|
||||
this->textEditorGotoLine = value;
|
||||
} else {
|
||||
logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key));
|
||||
}
|
||||
|
@ -216,6 +223,8 @@ QMap<QString, QString> PorymapConfig::getKeyValueMap() {
|
|||
map.insert("region_map_dimensions", QString("%1x%2").arg(this->regionMapDimensions.width())
|
||||
.arg(this->regionMapDimensions.height()));
|
||||
map.insert("theme", this->theme);
|
||||
map.insert("text_editor_open_directory", this->textEditorOpenFolder);
|
||||
map.insert("text_editor_goto_line", this->textEditorGotoLine);
|
||||
return map;
|
||||
}
|
||||
|
||||
|
@ -316,6 +325,16 @@ void PorymapConfig::setTheme(QString theme) {
|
|||
this->theme = theme;
|
||||
}
|
||||
|
||||
void PorymapConfig::setTextEditorOpenFolder(const QString &command) {
|
||||
this->textEditorOpenFolder = command;
|
||||
this->save();
|
||||
}
|
||||
|
||||
void PorymapConfig::setTextEditorGotoLine(const QString &command) {
|
||||
this->textEditorGotoLine = command;
|
||||
this->save();
|
||||
}
|
||||
|
||||
QString PorymapConfig::getRecentProject() {
|
||||
return this->recentProject;
|
||||
}
|
||||
|
@ -398,6 +417,14 @@ QString PorymapConfig::getTheme() {
|
|||
return this->theme;
|
||||
}
|
||||
|
||||
QString PorymapConfig::getTextEditorOpenFolder() {
|
||||
return this->textEditorOpenFolder;
|
||||
}
|
||||
|
||||
QString PorymapConfig::getTextEditorGotoLine() {
|
||||
return this->textEditorGotoLine;
|
||||
}
|
||||
|
||||
const QMap<BaseGameVersion, QString> baseGameVersionMap = {
|
||||
{BaseGameVersion::pokeruby, "pokeruby"},
|
||||
{BaseGameVersion::pokefirered, "pokefirered"},
|
||||
|
@ -414,7 +441,7 @@ ProjectConfig projectConfig;
|
|||
|
||||
QString ProjectConfig::getConfigFilepath() {
|
||||
// porymap config file is in the same directory as porymap itself.
|
||||
return QDir(this->projectDir).filePath("porymap.project.cfg");;
|
||||
return QDir(this->projectDir).filePath("porymap.project.cfg");
|
||||
}
|
||||
|
||||
void ProjectConfig::parseConfigKeyValue(QString key, QString value) {
|
||||
|
@ -703,3 +730,128 @@ void ProjectConfig::setCustomScripts(QList<QString> scripts) {
|
|||
QList<QString> ProjectConfig::getCustomScripts() {
|
||||
return this->customScripts;
|
||||
}
|
||||
|
||||
ShortcutsConfig shortcutsConfig;
|
||||
|
||||
QString ShortcutsConfig::getConfigFilepath() {
|
||||
QString settingsPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
QDir dir(settingsPath);
|
||||
if (!dir.exists())
|
||||
dir.mkpath(settingsPath);
|
||||
|
||||
QString configPath = dir.absoluteFilePath("porymap.shortcuts.cfg");
|
||||
|
||||
return configPath;
|
||||
}
|
||||
|
||||
void ShortcutsConfig::parseConfigKeyValue(QString key, QString value) {
|
||||
QStringList keySequences = value.split(' ');
|
||||
for (auto keySequence : keySequences)
|
||||
user_shortcuts.insert(key, keySequence);
|
||||
}
|
||||
|
||||
QMap<QString, QString> ShortcutsConfig::getKeyValueMap() {
|
||||
QMap<QString, QString> map;
|
||||
for (auto cfg_key : user_shortcuts.uniqueKeys()) {
|
||||
auto keySequences = user_shortcuts.values(cfg_key);
|
||||
QStringList keySequenceStrings;
|
||||
for (auto keySequence : keySequences)
|
||||
keySequenceStrings.append(keySequence.toString());
|
||||
map.insert(cfg_key, keySequenceStrings.join(' '));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
void ShortcutsConfig::setDefaultShortcuts(const QObjectList &objects) {
|
||||
storeShortcutsFromList(StoreType::Default, objects);
|
||||
save();
|
||||
}
|
||||
|
||||
QList<QKeySequence> ShortcutsConfig::defaultShortcuts(const QObject *object) const {
|
||||
return default_shortcuts.values(cfgKey(object));
|
||||
}
|
||||
|
||||
void ShortcutsConfig::setUserShortcuts(const QObjectList &objects) {
|
||||
storeShortcutsFromList(StoreType::User, objects);
|
||||
save();
|
||||
}
|
||||
|
||||
void ShortcutsConfig::setUserShortcuts(const QMultiMap<const QObject *, QKeySequence> &objects_keySequences) {
|
||||
for (auto *object : objects_keySequences.uniqueKeys())
|
||||
if (!object->objectName().isEmpty() && !object->objectName().startsWith("_q_"))
|
||||
storeShortcuts(StoreType::User, cfgKey(object), objects_keySequences.values(object));
|
||||
save();
|
||||
}
|
||||
|
||||
QList<QKeySequence> ShortcutsConfig::userShortcuts(const QObject *object) const {
|
||||
return user_shortcuts.values(cfgKey(object));
|
||||
}
|
||||
|
||||
void ShortcutsConfig::storeShortcutsFromList(StoreType storeType, const QObjectList &objects) {
|
||||
for (const auto *object : objects)
|
||||
if (!object->objectName().isEmpty() && !object->objectName().startsWith("_q_"))
|
||||
storeShortcuts(storeType, cfgKey(object), currentShortcuts(object));
|
||||
}
|
||||
|
||||
void ShortcutsConfig::storeShortcuts(
|
||||
StoreType storeType,
|
||||
const QString &cfgKey,
|
||||
const QList<QKeySequence> &keySequences)
|
||||
{
|
||||
bool storeUser = (storeType == User) || !user_shortcuts.contains(cfgKey);
|
||||
|
||||
if (storeType == Default)
|
||||
default_shortcuts.remove(cfgKey);
|
||||
if (storeUser)
|
||||
user_shortcuts.remove(cfgKey);
|
||||
|
||||
if (keySequences.isEmpty()) {
|
||||
if (storeType == Default)
|
||||
default_shortcuts.insert(cfgKey, QKeySequence());
|
||||
if (storeUser)
|
||||
user_shortcuts.insert(cfgKey, QKeySequence());
|
||||
} else {
|
||||
for (auto keySequence : keySequences) {
|
||||
if (storeType == Default)
|
||||
default_shortcuts.insert(cfgKey, keySequence);
|
||||
if (storeUser)
|
||||
user_shortcuts.insert(cfgKey, keySequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Creates a config key from the object's name prepended with the parent
|
||||
* window's object name, and converts camelCase to snake_case. */
|
||||
QString ShortcutsConfig::cfgKey(const QObject *object) const {
|
||||
auto cfg_key = QString();
|
||||
auto *parentWidget = static_cast<QWidget *>(object->parent());
|
||||
if (parentWidget)
|
||||
cfg_key = parentWidget->window()->objectName() + '_';
|
||||
cfg_key += object->objectName();
|
||||
|
||||
QRegularExpression re("[A-Z]");
|
||||
int i = cfg_key.indexOf(re, 1);
|
||||
while (i != -1) {
|
||||
if (cfg_key.at(i - 1) != '_')
|
||||
cfg_key.insert(i++, '_');
|
||||
i = cfg_key.indexOf(re, i + 1);
|
||||
}
|
||||
return cfg_key.toLower();
|
||||
}
|
||||
|
||||
QList<QKeySequence> ShortcutsConfig::currentShortcuts(const QObject *object) const {
|
||||
if (object->inherits("QAction")) {
|
||||
const auto *action = qobject_cast<const QAction *>(object);
|
||||
return action->shortcuts();
|
||||
} else if (object->inherits("Shortcut")) {
|
||||
const auto *shortcut = qobject_cast<const Shortcut *>(object);
|
||||
return shortcut->keys();
|
||||
} else if (object->inherits("QShortcut")) {
|
||||
const auto *qshortcut = qobject_cast<const QShortcut *>(object);
|
||||
return { qshortcut->key() };
|
||||
} else if (object->property("shortcut").isValid()) {
|
||||
return { object->property("shortcut").value<QKeySequence>() };
|
||||
} else {
|
||||
return { };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -450,12 +450,32 @@ void Map::magicFillCollisionElevation(int initialX, int initialY, uint16_t colli
|
|||
}
|
||||
}
|
||||
|
||||
QList<Event *> Map::getAllEvents() {
|
||||
QList<Event*> all;
|
||||
for (QList<Event*> list : events.values()) {
|
||||
all += list;
|
||||
QList<Event *> Map::getAllEvents() const {
|
||||
QList<Event *> all_events;
|
||||
for (const auto &event_list : events) {
|
||||
all_events << event_list;
|
||||
}
|
||||
return all;
|
||||
return all_events;
|
||||
}
|
||||
|
||||
QStringList Map::eventScriptLabels(const QString &event_group_type) const {
|
||||
QStringList scriptLabels;
|
||||
if (event_group_type.isEmpty()) {
|
||||
for (const auto *event : getAllEvents())
|
||||
scriptLabels << event->get("script_label");
|
||||
} else {
|
||||
for (const auto *event : events.value(event_group_type))
|
||||
scriptLabels << event->get("script_label");
|
||||
}
|
||||
|
||||
scriptLabels.removeDuplicates();
|
||||
scriptLabels.removeAll(QString());
|
||||
if (scriptLabels.contains("0x0"))
|
||||
scriptLabels.move(scriptLabels.indexOf("0x0"), scriptLabels.count() - 1);
|
||||
if (scriptLabels.contains("NULL"))
|
||||
scriptLabels.move(scriptLabels.indexOf("NULL"), scriptLabels.count() - 1);
|
||||
|
||||
return scriptLabels;
|
||||
}
|
||||
|
||||
void Map::removeEvent(Event *event) {
|
||||
|
|
|
@ -337,7 +337,7 @@ QMap<QString, QString> ParseUtil::readNamedIndexCArray(QString filename, QString
|
|||
QRegularExpression re_text(QString(R"(\b%1\b\s*(\[?[^\]]*\])?\s*=\s*\{([^\}]*)\})").arg(label));
|
||||
QString body = re_text.match(text).captured(2).replace(QRegularExpression("\\s*"), "");
|
||||
|
||||
QRegularExpression re("\\[(?<index>[A-Za-z1-9_]*)\\]=(?<value>&?[A-Za-z1-9_]*)");
|
||||
QRegularExpression re("\\[(?<index>[A-Za-z0-9_]*)\\]=(?<value>&?[A-Za-z0-9_]*)");
|
||||
QRegularExpressionMatchIterator iter = re.globalMatch(body);
|
||||
|
||||
while (iter.hasNext()) {
|
||||
|
@ -420,3 +420,118 @@ bool ParseUtil::ensureFieldsExist(QJsonObject obj, QList<QString> fields) {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int ParseUtil::getScriptLineNumber(const QString &filePath, const QString &scriptLabel) {
|
||||
if (scriptLabel.isEmpty())
|
||||
return 0;
|
||||
|
||||
if (filePath.endsWith(".inc") || filePath.endsWith(".s"))
|
||||
return getRawScriptLineNumber(readTextFile(filePath), scriptLabel);
|
||||
else if (filePath.endsWith(".pory"))
|
||||
return getPoryScriptLineNumber(readTextFile(filePath), scriptLabel);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ParseUtil::getRawScriptLineNumber(QString text, const QString &scriptLabel) {
|
||||
removeStringLiterals(text);
|
||||
removeLineComments(text, "@");
|
||||
|
||||
static const QRegularExpression re_incScriptLabel("\\b(?<label>[\\w_][\\w\\d_]*):{1,2}");
|
||||
QRegularExpressionMatchIterator it = re_incScriptLabel.globalMatch(text);
|
||||
while (it.hasNext()) {
|
||||
const QRegularExpressionMatch match = it.next();
|
||||
if (match.captured("label") == scriptLabel)
|
||||
return text.left(match.capturedStart("label")).count('\n') + 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ParseUtil::getPoryScriptLineNumber(QString text, const QString &scriptLabel) {
|
||||
removeStringLiterals(text);
|
||||
removeLineComments(text, {"//", "#"});
|
||||
|
||||
static const QRegularExpression re_poryScriptLabel("\\b(script)(\\((global|local)\\))?\\s*\\b(?<label>[\\w_][\\w\\d_]*)");
|
||||
QRegularExpressionMatchIterator it = re_poryScriptLabel.globalMatch(text);
|
||||
while (it.hasNext()) {
|
||||
const QRegularExpressionMatch match = it.next();
|
||||
if (match.captured("label") == scriptLabel)
|
||||
return text.left(match.capturedStart("label")).count('\n') + 1;
|
||||
}
|
||||
|
||||
static const QRegularExpression re_poryRawSection("\\b(raw)\\s*`(?<raw_script>[^`]*)");
|
||||
QRegularExpressionMatchIterator raw_it = re_poryRawSection.globalMatch(text);
|
||||
while (raw_it.hasNext()) {
|
||||
const QRegularExpressionMatch match = raw_it.next();
|
||||
const int relativelineNum = getRawScriptLineNumber(match.captured("raw_script"), scriptLabel);
|
||||
if (relativelineNum)
|
||||
return text.left(match.capturedStart("raw_script")).count('\n') + relativelineNum;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
QString &ParseUtil::removeStringLiterals(QString &text) {
|
||||
static const QRegularExpression re_string("\".*\"");
|
||||
return text.remove(re_string);
|
||||
}
|
||||
|
||||
QString &ParseUtil::removeLineComments(QString &text, const QString &commentSymbol) {
|
||||
const QRegularExpression re_lineComment(commentSymbol + "+.*");
|
||||
return text.remove(re_lineComment);
|
||||
}
|
||||
|
||||
QString &ParseUtil::removeLineComments(QString &text, const QStringList &commentSymbols) {
|
||||
for (const auto &commentSymbol : commentSymbols)
|
||||
removeLineComments(text, commentSymbol);
|
||||
return text;
|
||||
}
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
|
||||
#include <QProcess>
|
||||
|
||||
QStringList ParseUtil::splitShellCommand(QStringView command) {
|
||||
return QProcess::splitCommand(command);
|
||||
}
|
||||
#else
|
||||
// The source for QProcess::splitCommand() as of Qt 5.15.2
|
||||
QStringList ParseUtil::splitShellCommand(QStringView command) {
|
||||
QStringList args;
|
||||
QString tmp;
|
||||
int quoteCount = 0;
|
||||
bool inQuote = false;
|
||||
|
||||
// handle quoting. tokens can be surrounded by double quotes
|
||||
// "hello world". three consecutive double quotes represent
|
||||
// the quote character itself.
|
||||
for (int i = 0; i < command.size(); ++i) {
|
||||
if (command.at(i) == QLatin1Char('"')) {
|
||||
++quoteCount;
|
||||
if (quoteCount == 3) {
|
||||
// third consecutive quote
|
||||
quoteCount = 0;
|
||||
tmp += command.at(i);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (quoteCount) {
|
||||
if (quoteCount == 1)
|
||||
inQuote = !inQuote;
|
||||
quoteCount = 0;
|
||||
}
|
||||
if (!inQuote && command.at(i).isSpace()) {
|
||||
if (!tmp.isEmpty()) {
|
||||
args += tmp;
|
||||
tmp.clear();
|
||||
}
|
||||
} else {
|
||||
tmp += command.at(i);
|
||||
}
|
||||
}
|
||||
if (!tmp.isEmpty())
|
||||
args += tmp;
|
||||
|
||||
return args;
|
||||
}
|
||||
#endif
|
||||
|
|
128
src/editor.cpp
128
src/editor.cpp
|
@ -9,10 +9,12 @@
|
|||
#include "metatile.h"
|
||||
#include "montabwidget.h"
|
||||
#include "editcommands.h"
|
||||
#include "config.h"
|
||||
#include <QCheckBox>
|
||||
#include <QPainter>
|
||||
#include <QMouseEvent>
|
||||
#include <QDir>
|
||||
#include <QProcess>
|
||||
#include <math.h>
|
||||
|
||||
static bool selectNewEvents = false;
|
||||
|
@ -24,9 +26,8 @@ Editor::Editor(Ui::MainWindow* ui)
|
|||
this->settings = new Settings();
|
||||
this->playerViewRect = new MovableRect(&this->settings->playerViewRectEnabled, 30 * 8, 20 * 8, qRgb(255, 255, 255));
|
||||
this->cursorMapTileRect = new CursorTileRect(&this->settings->cursorTileRectEnabled, qRgb(255, 255, 255));
|
||||
this->map_ruler = new MapRuler();
|
||||
connect(this->map_ruler, &MapRuler::lengthChanged, this, &Editor::onMapRulerLengthChanged);
|
||||
connect(this->map_ruler, &MapRuler::deactivated, this, &Editor::onHoveredMapMetatileChanged);
|
||||
this->map_ruler = new MapRuler(4);
|
||||
connect(this->map_ruler, &MapRuler::statusChanged, this, &Editor::mapRulerStatusChanged);
|
||||
|
||||
/// Instead of updating the selected events after every single undo action
|
||||
/// (eg when the user rolls back several at once), only reselect events when
|
||||
|
@ -357,6 +358,44 @@ void Editor::addNewWildMonGroup(QWidget *window) {
|
|||
}
|
||||
}
|
||||
|
||||
void Editor::deleteWildMonGroup() {
|
||||
QComboBox *labelCombo = ui->comboBox_EncounterGroupLabel;
|
||||
|
||||
if (labelCombo->count() < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
QMessageBox msgBox;
|
||||
msgBox.setText("Confirm Delete");
|
||||
msgBox.setInformativeText("Are you sure you want to delete " + labelCombo->currentText() + "?");
|
||||
|
||||
QPushButton *deleteButton = msgBox.addButton("Delete", QMessageBox::DestructiveRole);
|
||||
msgBox.addButton(QMessageBox::Cancel);
|
||||
msgBox.setDefaultButton(QMessageBox::Cancel);
|
||||
msgBox.exec();
|
||||
|
||||
if (msgBox.clickedButton() == deleteButton) {
|
||||
auto it = project->wildMonData.find(map->constantName);
|
||||
if (it == project->wildMonData.end()) {
|
||||
logError(QString("Failed to find data for map %1. Unable to delete").arg(map->constantName));
|
||||
return;
|
||||
}
|
||||
|
||||
int i = project->encounterGroupLabels.indexOf(labelCombo->currentText());
|
||||
if (i < 0) {
|
||||
logError(QString("Failed to find selected wild mon group: %1. Unable to delete")
|
||||
.arg(labelCombo->currentText()));
|
||||
return;
|
||||
}
|
||||
|
||||
it.value().erase(labelCombo->currentText());
|
||||
project->encounterGroupLabels.remove(i);
|
||||
|
||||
displayWildMonTables();
|
||||
emit wildMonDataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void Editor::configureEncounterJSON(QWidget *window) {
|
||||
QVector<QWidget *> fieldSlots;
|
||||
|
||||
|
@ -905,9 +944,8 @@ void Editor::scaleMapView(int s) {
|
|||
if ((scale_exp + s) <= 5 && (scale_exp + s) >= -2) // sane limits
|
||||
{
|
||||
if (s == 0)
|
||||
scale_exp = 0;
|
||||
else
|
||||
scale_exp += s;
|
||||
s = -scale_exp;
|
||||
scale_exp += s;
|
||||
|
||||
double sfactor = pow(scale_base, s);
|
||||
ui->graphicsView_Map->scale(sfactor, sfactor);
|
||||
|
@ -936,24 +974,12 @@ void Editor::onHoveredMapMetatileChanged(const QPoint &pos) {
|
|||
}
|
||||
}
|
||||
|
||||
void Editor::onMapRulerLengthChanged() {
|
||||
const QPoint pos = map_ruler->endPos();
|
||||
ui->statusBar->showMessage(QString("X: %1, Y: %2, Scale = %3x; %4")
|
||||
.arg(pos.x())
|
||||
.arg(pos.y())
|
||||
.arg(QString::number(pow(scale_base, scale_exp), 'g', 2))
|
||||
.arg(map_ruler->statusMessage));
|
||||
}
|
||||
|
||||
void Editor::onHoveredMapMetatileCleared() {
|
||||
this->playerViewRect->setVisible(false);
|
||||
this->cursorMapTileRect->setVisible(false);
|
||||
if (map_item->paintingMode == MapPixmapItem::PaintMode::Metatiles
|
||||
|| map_item->paintingMode == MapPixmapItem::PaintMode::EventObjects) {
|
||||
this->ui->statusBar->clearMessage();
|
||||
if (this->map_ruler->isAnchored()) {
|
||||
this->ui->statusBar->showMessage(this->map_ruler->statusMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2000,6 +2026,72 @@ void Editor::deleteEvent(Event *event) {
|
|||
//updateSelectedObjects();
|
||||
}
|
||||
|
||||
void Editor::openMapScripts() const {
|
||||
const QString scriptPath = project->getMapScriptsFilePath(map->name);
|
||||
openInTextEditor(scriptPath);
|
||||
}
|
||||
|
||||
void Editor::openScript(const QString &scriptLabel) const {
|
||||
// Find the location of scriptLabel.
|
||||
QStringList scriptPaths(project->getMapScriptsFilePath(map->name));
|
||||
scriptPaths << project->getEventScriptsFilePaths();
|
||||
int lineNum = 0;
|
||||
QString scriptPath = scriptPaths.first();
|
||||
for (const auto &path : scriptPaths) {
|
||||
lineNum = ParseUtil::getScriptLineNumber(path, scriptLabel);
|
||||
if (lineNum != 0) {
|
||||
scriptPath = path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
openInTextEditor(scriptPath, lineNum);
|
||||
}
|
||||
|
||||
void Editor::openInTextEditor(const QString &path, int lineNum) const {
|
||||
QString command = porymapConfig.getTextEditorGotoLine();
|
||||
if (command.isEmpty()) {
|
||||
// Open map scripts in the system's default editor.
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||
} else {
|
||||
if (command.contains("%F")) {
|
||||
if (command.contains("%L"))
|
||||
command.replace("%L", QString::number(lineNum));
|
||||
command.replace("%F", path);
|
||||
} else {
|
||||
command += ' ' + path;
|
||||
}
|
||||
startDetachedProcess(command);
|
||||
}
|
||||
}
|
||||
|
||||
void Editor::openProjectInTextEditor() const {
|
||||
QString command = porymapConfig.getTextEditorOpenFolder();
|
||||
if (command.contains("%D"))
|
||||
command.replace("%D", project->root);
|
||||
else
|
||||
command += ' ' + project->root;
|
||||
startDetachedProcess(command);
|
||||
}
|
||||
|
||||
bool Editor::startDetachedProcess(const QString &command, const QString &workingDirectory, qint64 *pid) const {
|
||||
static QProcess process;
|
||||
logInfo("Executing command: " + command);
|
||||
#ifdef Q_OS_WIN
|
||||
// On Windows, a QProcess command must be wrapped in a cmd.exe command.
|
||||
process.setProcessEnvironment(QProcessEnvironment::systemEnvironment());
|
||||
process.setProgram(process.processEnvironment().value("COMSPEC"));
|
||||
process.setNativeArguments("/c " + command);
|
||||
process.setWorkingDirectory(workingDirectory);
|
||||
#else
|
||||
QStringList arguments = ParseUtil::splitShellCommand(command);
|
||||
process.setProgram(arguments.takeFirst());
|
||||
process.setArguments(arguments);
|
||||
process.setWorkingDirectory(workingDirectory);
|
||||
#endif
|
||||
return process.startDetached(pid);
|
||||
}
|
||||
|
||||
// It doesn't seem to be possible to prevent the mousePress event
|
||||
// from triggering both event's DraggablePixmapItem and the background mousePress.
|
||||
// Since the DraggablePixmapItem's event fires first, we can set a temp
|
||||
|
|
|
@ -14,11 +14,11 @@
|
|||
#include "draggablepixmapitem.h"
|
||||
#include "editcommands.h"
|
||||
#include "flowlayout.h"
|
||||
#include "shortcut.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QDirIterator>
|
||||
#include <QStandardItemModel>
|
||||
#include <QShortcut>
|
||||
#include <QSpinBox>
|
||||
#include <QTextEdit>
|
||||
#include <QSpacerItem>
|
||||
|
@ -29,7 +29,6 @@
|
|||
#include <QDialogButtonBox>
|
||||
#include <QScroller>
|
||||
#include <math.h>
|
||||
#include <QProcess>
|
||||
#include <QSysInfo>
|
||||
#include <QDesktopServices>
|
||||
#include <QTransform>
|
||||
|
@ -41,6 +40,7 @@
|
|||
MainWindow::MainWindow(QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
ui(new Ui::MainWindow),
|
||||
label_MapRulerStatus(nullptr),
|
||||
selectedObject(nullptr),
|
||||
selectedWarp(nullptr),
|
||||
selectedTrigger(nullptr),
|
||||
|
@ -67,54 +67,109 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
delete label_MapRulerStatus;
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void MainWindow::setWindowDisabled(bool disabled) {
|
||||
for (auto *child : findChildren<QWidget *>(QString(), Qt::FindDirectChildrenOnly)) {
|
||||
bool disableThis = disabled;
|
||||
if (child->objectName() == "menuBar") {
|
||||
// disable all but the menuFile and menuHelp
|
||||
for (auto *menu : child->findChildren<QWidget *>(QString(), Qt::FindDirectChildrenOnly)) {
|
||||
disableThis = disabled;
|
||||
if (menu->objectName() == "menuFile") {
|
||||
// disable all but the action_Open_Project and action_Exit
|
||||
for (auto *action : menu->actions()) {
|
||||
action->setDisabled(disabled);
|
||||
}
|
||||
ui->action_Open_Project->setDisabled(false);
|
||||
ui->action_Exit->setDisabled(false);
|
||||
disableThis = false;
|
||||
}
|
||||
menu->setDisabled(disableThis);
|
||||
}
|
||||
child->findChild<QWidget *>("menuHelp")->setDisabled(false);
|
||||
disableThis = false;
|
||||
}
|
||||
child->setDisabled(disableThis);
|
||||
}
|
||||
for (auto action : findChildren<QAction *>())
|
||||
action->setDisabled(disabled);
|
||||
for (auto child : findChildren<QWidget *>(QString(), Qt::FindDirectChildrenOnly))
|
||||
child->setDisabled(disabled);
|
||||
for (auto menu : ui->menuBar->findChildren<QMenu *>(QString(), Qt::FindDirectChildrenOnly))
|
||||
menu->setDisabled(disabled);
|
||||
ui->menuBar->setDisabled(false);
|
||||
ui->menuFile->setDisabled(false);
|
||||
ui->action_Open_Project->setDisabled(false);
|
||||
ui->action_Exit->setDisabled(false);
|
||||
ui->menuHelp->setDisabled(false);
|
||||
ui->actionAbout_Porymap->setDisabled(false);
|
||||
if (!disabled)
|
||||
togglePreferenceSpecificUi();
|
||||
}
|
||||
|
||||
void MainWindow::initWindow() {
|
||||
porymapConfig.load();
|
||||
this->initCustomUI();
|
||||
this->initExtraSignals();
|
||||
this->initExtraShortcuts();
|
||||
this->initEditor();
|
||||
this->initMiscHeapObjects();
|
||||
this->initMapSortOrder();
|
||||
this->initShortcuts();
|
||||
this->restoreWindowState();
|
||||
|
||||
setWindowDisabled(true);
|
||||
}
|
||||
|
||||
void MainWindow::initShortcuts() {
|
||||
initExtraShortcuts();
|
||||
|
||||
shortcutsConfig.load();
|
||||
shortcutsConfig.setDefaultShortcuts(shortcutableObjects());
|
||||
applyUserShortcuts();
|
||||
}
|
||||
|
||||
void MainWindow::initExtraShortcuts() {
|
||||
new QShortcut(QKeySequence("Ctrl+0"), this, SLOT(resetMapViewScale()));
|
||||
new QShortcut(QKeySequence("Ctrl+G"), ui->checkBox_ToggleGrid, SLOT(toggle()));
|
||||
new QShortcut(QKeySequence("Ctrl+D"), this, SLOT(duplicate()));
|
||||
new QShortcut(QKeySequence::Delete, this, SLOT(on_toolButton_deleteObject_clicked()));
|
||||
new QShortcut(QKeySequence("Backspace"), this, SLOT(on_toolButton_deleteObject_clicked()));
|
||||
ui->actionZoom_In->setShortcuts({QKeySequence("Ctrl++"), QKeySequence("Ctrl+=")});
|
||||
ui->actionZoom_In->setShortcuts({ui->actionZoom_In->shortcut(), QKeySequence("Ctrl+=")});
|
||||
|
||||
auto *shortcutReset_Zoom = new Shortcut(QKeySequence("Ctrl+0"), this, SLOT(resetMapViewScale()));
|
||||
shortcutReset_Zoom->setObjectName("shortcutZoom_Reset");
|
||||
shortcutReset_Zoom->setWhatsThis("Zoom Reset");
|
||||
|
||||
auto *shortcutToggle_Grid = new Shortcut(QKeySequence("Ctrl+G"), ui->checkBox_ToggleGrid, SLOT(toggle()));
|
||||
shortcutToggle_Grid->setObjectName("shortcutToggle_Grid");
|
||||
shortcutToggle_Grid->setWhatsThis("Toggle Grid");
|
||||
|
||||
auto *shortcutDuplicate_Events = new Shortcut(QKeySequence("Ctrl+D"), this, SLOT(duplicate()));
|
||||
shortcutDuplicate_Events->setObjectName("shortcutDuplicate_Events");
|
||||
shortcutDuplicate_Events->setWhatsThis("Duplicate Selected Event(s)");
|
||||
|
||||
auto *shortcutDelete_Object = new Shortcut(
|
||||
{QKeySequence("Del"), QKeySequence("Backspace")}, this, SLOT(on_toolButton_deleteObject_clicked()));
|
||||
shortcutDelete_Object->setObjectName("shortcutDelete_Object");
|
||||
shortcutDelete_Object->setWhatsThis("Delete Selected Event(s)");
|
||||
|
||||
auto *shortcutToggle_Border = new Shortcut(QKeySequence(), ui->checkBox_ToggleBorder, SLOT(toggle()));
|
||||
shortcutToggle_Border->setObjectName("shortcutToggle_Border");
|
||||
shortcutToggle_Border->setWhatsThis("Toggle Border");
|
||||
|
||||
auto *shortcutToggle_Smart_Paths = new Shortcut(QKeySequence(), ui->checkBox_smartPaths, SLOT(toggle()));
|
||||
shortcutToggle_Smart_Paths->setObjectName("shortcutToggle_Smart_Paths");
|
||||
shortcutToggle_Smart_Paths->setWhatsThis("Toggle Smart Paths");
|
||||
|
||||
auto *shortcutExpand_All = new Shortcut(QKeySequence(), this, SLOT(on_toolButton_ExpandAll_clicked()));
|
||||
shortcutExpand_All->setObjectName("shortcutExpand_All");
|
||||
shortcutExpand_All->setWhatsThis("Map List: Expand all folders");
|
||||
|
||||
auto *shortcutCollapse_All = new Shortcut(QKeySequence(), this, SLOT(on_toolButton_CollapseAll_clicked()));
|
||||
shortcutCollapse_All->setObjectName("shortcutCollapse_All");
|
||||
shortcutCollapse_All->setWhatsThis("Map List: Collapse all folders");
|
||||
|
||||
auto *shortcut_Open_Scripts = new Shortcut(QKeySequence(), ui->toolButton_Open_Scripts, SLOT(click()));
|
||||
shortcut_Open_Scripts->setObjectName("shortcut_Open_Scripts");
|
||||
shortcut_Open_Scripts->setWhatsThis("Open Map Scripts");
|
||||
}
|
||||
|
||||
QObjectList MainWindow::shortcutableObjects() const {
|
||||
QObjectList shortcutable_objects;
|
||||
|
||||
for (auto *action : findChildren<QAction *>())
|
||||
if (!action->objectName().isEmpty())
|
||||
shortcutable_objects.append(qobject_cast<QObject *>(action));
|
||||
for (auto *shortcut : findChildren<Shortcut *>())
|
||||
if (!shortcut->objectName().isEmpty())
|
||||
shortcutable_objects.append(qobject_cast<QObject *>(shortcut));
|
||||
|
||||
return shortcutable_objects;
|
||||
}
|
||||
|
||||
void MainWindow::applyUserShortcuts() {
|
||||
for (auto *action : findChildren<QAction *>())
|
||||
if (!action->objectName().isEmpty())
|
||||
action->setShortcuts(shortcutsConfig.userShortcuts(action));
|
||||
for (auto *shortcut : findChildren<Shortcut *>())
|
||||
if (!shortcut->objectName().isEmpty())
|
||||
shortcut->setKeys(shortcutsConfig.userShortcuts(shortcut));
|
||||
}
|
||||
|
||||
void MainWindow::initCustomUI() {
|
||||
|
@ -158,6 +213,17 @@ void MainWindow::initExtraSignals() {
|
|||
}
|
||||
delete ui->frame_mapTools->layout();
|
||||
ui->frame_mapTools->setLayout(flowLayout);
|
||||
|
||||
// Floating QLabel tool-window that displays over the map when the ruler is active
|
||||
label_MapRulerStatus = new QLabel(ui->graphicsView_Map);
|
||||
label_MapRulerStatus->setObjectName("label_MapRulerStatus");
|
||||
label_MapRulerStatus->setWindowFlags(Qt::Tool | Qt::CustomizeWindowHint | Qt::FramelessWindowHint);
|
||||
label_MapRulerStatus->setFrameShape(QFrame::Box);
|
||||
label_MapRulerStatus->setMargin(3);
|
||||
label_MapRulerStatus->setPalette(palette());
|
||||
label_MapRulerStatus->setAlignment(Qt::AlignCenter);
|
||||
label_MapRulerStatus->setTextFormat(Qt::PlainText);
|
||||
label_MapRulerStatus->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
}
|
||||
|
||||
void MainWindow::initEditor() {
|
||||
|
@ -168,13 +234,18 @@ void MainWindow::initEditor() {
|
|||
connect(this->editor, SIGNAL(warpEventDoubleClicked(QString,QString)), this, SLOT(openWarpMap(QString,QString)));
|
||||
connect(this->editor, SIGNAL(currentMetatilesSelectionChanged()), this, SLOT(currentMetatilesSelectionChanged()));
|
||||
connect(this->editor, SIGNAL(wildMonDataChanged()), this, SLOT(onWildMonDataChanged()));
|
||||
connect(this->editor, &Editor::mapRulerStatusChanged, this, &MainWindow::onMapRulerStatusChanged);
|
||||
connect(ui->toolButton_Open_Scripts, &QToolButton::pressed, this->editor, &Editor::openMapScripts);
|
||||
connect(ui->actionOpen_Project_in_Text_Editor, &QAction::triggered, this->editor, &Editor::openProjectInTextEditor);
|
||||
|
||||
this->loadUserSettings();
|
||||
|
||||
undoAction = editor->editGroup.createUndoAction(this, tr("&Undo"));
|
||||
undoAction->setObjectName("action_Undo");
|
||||
undoAction->setShortcut(QKeySequence("Ctrl+Z"));
|
||||
|
||||
redoAction = editor->editGroup.createRedoAction(this, tr("&Redo"));
|
||||
redoAction->setObjectName("action_Redo");
|
||||
redoAction->setShortcuts({QKeySequence("Ctrl+Y"), QKeySequence("Ctrl+Shift+Z")});
|
||||
|
||||
ui->menuEdit->addAction(undoAction);
|
||||
|
@ -185,7 +256,8 @@ void MainWindow::initEditor() {
|
|||
undoView->setAttribute(Qt::WA_QuitOnClose, false);
|
||||
|
||||
// Show the EditHistory dialog with Ctrl+E
|
||||
QAction *showHistory = new QAction("Show Edit History...");
|
||||
QAction *showHistory = new QAction("Show Edit History...", this);
|
||||
showHistory->setObjectName("action_ShowEditHistory");
|
||||
showHistory->setShortcut(QKeySequence("Ctrl+E"));
|
||||
connect(showHistory, &QAction::triggered, [undoView](){ undoView->show(); });
|
||||
|
||||
|
@ -229,7 +301,7 @@ void MainWindow::initMapSortOrder() {
|
|||
mapSortOrderActionGroup->addAction(ui->actionSort_by_Area);
|
||||
mapSortOrderActionGroup->addAction(ui->actionSort_by_Layout);
|
||||
|
||||
connect(ui->toolButton_MapSortOrder, &QToolButton::triggered, this, &MainWindow::mapSortOrder_changed);
|
||||
connect(mapSortOrderActionGroup, &QActionGroup::triggered, this, &MainWindow::mapSortOrder_changed);
|
||||
|
||||
QAction* sortOrder = ui->toolButton_MapSortOrder->menu()->actions()[mapSortOrder];
|
||||
ui->toolButton_MapSortOrder->setIcon(sortOrder->icon());
|
||||
|
@ -1305,16 +1377,6 @@ void MainWindow::duplicate() {
|
|||
editor->duplicateSelectedEvents();
|
||||
}
|
||||
|
||||
// Open current map scripts in system default editor for .inc files
|
||||
void MainWindow::openInTextEditor() {
|
||||
bool usePoryscript = projectConfig.getUsePoryScript();
|
||||
QString path = QDir::cleanPath("file://" + editor->project->root + QDir::separator() + "data/maps/" + editor->map->name + "/scripts");
|
||||
|
||||
// Try opening scripts file, if opening .pory failed try again with .inc
|
||||
if (!QDesktopServices::openUrl(QUrl(path + editor->project->getScriptFileExtension(usePoryscript))) && usePoryscript)
|
||||
QDesktopServices::openUrl(QUrl(path + editor->project->getScriptFileExtension(false)));
|
||||
}
|
||||
|
||||
void MainWindow::on_action_Save_triggered() {
|
||||
editor->save();
|
||||
updateMapList();
|
||||
|
@ -1415,6 +1477,48 @@ void MainWindow::on_actionUse_Poryscript_triggered(bool checked)
|
|||
projectConfig.setUsePoryScript(checked);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionEdit_Shortcuts_triggered()
|
||||
{
|
||||
if (!shortcutsEditor)
|
||||
initShortcutsEditor();
|
||||
|
||||
if (shortcutsEditor->isHidden())
|
||||
shortcutsEditor->show();
|
||||
else if (shortcutsEditor->isMinimized())
|
||||
shortcutsEditor->showNormal();
|
||||
else
|
||||
shortcutsEditor->activateWindow();
|
||||
}
|
||||
|
||||
void MainWindow::initShortcutsEditor() {
|
||||
shortcutsEditor = new ShortcutsEditor(this);
|
||||
connect(shortcutsEditor, &ShortcutsEditor::shortcutsSaved,
|
||||
this, &MainWindow::applyUserShortcuts);
|
||||
connect(shortcutsEditor, &QObject::destroyed, [=](QObject *) { shortcutsEditor = nullptr; });
|
||||
|
||||
connectSubEditorsToShortcutsEditor();
|
||||
|
||||
shortcutsEditor->setShortcutableObjects(shortcutableObjects());
|
||||
}
|
||||
|
||||
void MainWindow::connectSubEditorsToShortcutsEditor() {
|
||||
/* Initialize sub-editors so that their children are added to MainWindow's object tree and will
|
||||
* be returned by shortcutableObjects() to be passed to ShortcutsEditor. */
|
||||
if (!tilesetEditor)
|
||||
initTilesetEditor();
|
||||
connect(shortcutsEditor, &ShortcutsEditor::shortcutsSaved,
|
||||
tilesetEditor, &TilesetEditor::applyUserShortcuts);
|
||||
|
||||
// TODO: Remove this check when the region map editor supports pokefirered.
|
||||
if (projectConfig.getBaseGameVersion() != BaseGameVersion::pokefirered) {
|
||||
if (!regionMapEditor)
|
||||
initRegionMapEditor();
|
||||
if (regionMapEditor)
|
||||
connect(shortcutsEditor, &ShortcutsEditor::shortcutsSaved,
|
||||
regionMapEditor, &RegionMapEditor::applyUserShortcuts);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_actionPencil_triggered()
|
||||
{
|
||||
on_toolButton_Paint_clicked();
|
||||
|
@ -1550,6 +1654,10 @@ void MainWindow::updateSelectedObjects() {
|
|||
}
|
||||
}
|
||||
|
||||
for (auto *button : openScriptButtons)
|
||||
delete button;
|
||||
openScriptButtons.clear();
|
||||
|
||||
QMap<QString, int> event_obj_gfx_constants = editor->project->getEventObjGfxConstants();
|
||||
|
||||
QList<EventPropertiesFrame *> frames;
|
||||
|
@ -1809,6 +1917,7 @@ void MainWindow::updateSelectedObjects() {
|
|||
"normal movement behavior actions.");
|
||||
combo->setMinimumContentsLength(4);
|
||||
} else if (key == "script_label") {
|
||||
combo->addItems(editor->map->eventScriptLabels());
|
||||
combo->setToolTip("The script which is executed with this event.");
|
||||
} else if (key == "trainer_type") {
|
||||
combo->addItems(*editor->project->trainerTypes);
|
||||
|
@ -1871,7 +1980,26 @@ void MainWindow::updateSelectedObjects() {
|
|||
} else {
|
||||
combo->setCurrentText(value);
|
||||
|
||||
fl->addRow(new QLabel(field_labels[key], widget), combo);
|
||||
if (key == "script_label") {
|
||||
// Add button next to combo which opens combo's current script.
|
||||
auto *hl = new QHBoxLayout();
|
||||
hl->setSpacing(3);
|
||||
auto *openScriptButton = new QToolButton(widget);
|
||||
openScriptButtons << openScriptButton;
|
||||
openScriptButton->setFixedSize(combo->height(), combo->height());
|
||||
openScriptButton->setIcon(QFileIconProvider().icon(QFileIconProvider::File));
|
||||
openScriptButton->setToolTip("Go to this script definition in text editor.");
|
||||
connect(openScriptButton, &QToolButton::clicked,
|
||||
[this, combo]() { this->editor->openScript(combo->currentText()); });
|
||||
hl->addWidget(combo);
|
||||
hl->addWidget(openScriptButton);
|
||||
fl->addRow(new QLabel(field_labels[key], widget), hl);
|
||||
if (porymapConfig.getTextEditorGotoLine().isEmpty())
|
||||
openScriptButton->hide();
|
||||
} else {
|
||||
fl->addRow(new QLabel(field_labels[key], widget), combo);
|
||||
}
|
||||
|
||||
widget->setLayout(fl);
|
||||
frame->layout()->addWidget(widget);
|
||||
|
||||
|
@ -2106,11 +2234,6 @@ void MainWindow::on_toolButton_deleteObject_clicked() {
|
|||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_toolButton_Open_Scripts_clicked()
|
||||
{
|
||||
openInTextEditor();
|
||||
}
|
||||
|
||||
void MainWindow::on_toolButton_Paint_clicked()
|
||||
{
|
||||
if (ui->mainTabBar->currentIndex() == 0)
|
||||
|
@ -2301,6 +2424,23 @@ void MainWindow::onWildMonDataChanged() {
|
|||
projectHasUnsavedChanges = true;
|
||||
}
|
||||
|
||||
void MainWindow::onMapRulerStatusChanged(const QString &status) {
|
||||
if (status.isEmpty()) {
|
||||
label_MapRulerStatus->hide();
|
||||
} else if (label_MapRulerStatus->parentWidget()) {
|
||||
label_MapRulerStatus->setText(status);
|
||||
label_MapRulerStatus->adjustSize();
|
||||
label_MapRulerStatus->show();
|
||||
label_MapRulerStatus->move(label_MapRulerStatus->parentWidget()->mapToGlobal(QPoint(6, 6)));
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::moveEvent(QMoveEvent *event) {
|
||||
QMainWindow::moveEvent(event);
|
||||
if (label_MapRulerStatus->isVisible() && label_MapRulerStatus->parentWidget())
|
||||
label_MapRulerStatus->move(label_MapRulerStatus->parentWidget()->mapToGlobal(QPoint(6, 6)));
|
||||
}
|
||||
|
||||
void MainWindow::on_action_Export_Map_Image_triggered() {
|
||||
showExportMapImageWindow(false);
|
||||
}
|
||||
|
@ -2358,6 +2498,10 @@ void MainWindow::on_pushButton_NewWildMonGroup_clicked() {
|
|||
editor->addNewWildMonGroup(this);
|
||||
}
|
||||
|
||||
void MainWindow::on_pushButton_DeleteWildMonGroup_clicked() {
|
||||
editor->deleteWildMonGroup();
|
||||
}
|
||||
|
||||
void MainWindow::on_pushButton_ConfigureEncountersJSON_clicked() {
|
||||
editor->configureEncounterJSON(this);
|
||||
}
|
||||
|
@ -2503,9 +2647,7 @@ void MainWindow::on_checkBox_ToggleBorder_stateChanged(int selected)
|
|||
void MainWindow::on_actionTileset_Editor_triggered()
|
||||
{
|
||||
if (!this->tilesetEditor) {
|
||||
this->tilesetEditor = new TilesetEditor(this->editor->project, this->editor->map, this);
|
||||
connect(this->tilesetEditor, SIGNAL(tilesetsSaved(QString, QString)), this, SLOT(onTilesetsSaved(QString, QString)));
|
||||
connect(this->tilesetEditor, &QObject::destroyed, [=](QObject *) { this->tilesetEditor = nullptr; });
|
||||
initTilesetEditor();
|
||||
}
|
||||
|
||||
if (!this->tilesetEditor->isVisible()) {
|
||||
|
@ -2518,6 +2660,12 @@ void MainWindow::on_actionTileset_Editor_triggered()
|
|||
this->tilesetEditor->selectMetatile(this->editor->metatile_selector_item->getSelectedMetatiles()->at(0));
|
||||
}
|
||||
|
||||
void MainWindow::initTilesetEditor() {
|
||||
this->tilesetEditor = new TilesetEditor(this->editor->project, this->editor->map, this);
|
||||
connect(this->tilesetEditor, SIGNAL(tilesetsSaved(QString, QString)), this, SLOT(onTilesetsSaved(QString, QString)));
|
||||
connect(this->tilesetEditor, &QObject::destroyed, [=](QObject *) { this->tilesetEditor = nullptr; });
|
||||
}
|
||||
|
||||
void MainWindow::on_toolButton_ExpandAll_clicked()
|
||||
{
|
||||
if (ui->mapList) {
|
||||
|
@ -2539,38 +2687,40 @@ void MainWindow::on_actionAbout_Porymap_triggered()
|
|||
window->show();
|
||||
}
|
||||
|
||||
void MainWindow::on_actionThemes_triggered()
|
||||
{
|
||||
QStringList themes;
|
||||
QRegularExpression re(":/themes/([A-z0-9_-]+).qss");
|
||||
themes.append("default");
|
||||
QDirIterator it(":/themes", QDirIterator::Subdirectories);
|
||||
while (it.hasNext()) {
|
||||
QString themeName = re.match(it.next()).captured(1);
|
||||
themes.append(themeName);
|
||||
void MainWindow::on_actionEdit_Preferences_triggered() {
|
||||
if (!preferenceEditor) {
|
||||
preferenceEditor = new PreferenceEditor(this);
|
||||
connect(preferenceEditor, &PreferenceEditor::themeChanged,
|
||||
this, &MainWindow::setTheme);
|
||||
connect(preferenceEditor, &PreferenceEditor::themeChanged,
|
||||
editor, &Editor::maskNonVisibleConnectionTiles);
|
||||
connect(preferenceEditor, &PreferenceEditor::preferencesSaved,
|
||||
this, &MainWindow::togglePreferenceSpecificUi);
|
||||
connect(preferenceEditor, &QObject::destroyed, [=](QObject *) { preferenceEditor = nullptr; });
|
||||
}
|
||||
|
||||
QDialog themeSelectorWindow(this);
|
||||
QFormLayout form(&themeSelectorWindow);
|
||||
if (!preferenceEditor->isVisible()) {
|
||||
preferenceEditor->show();
|
||||
} else if (preferenceEditor->isMinimized()) {
|
||||
preferenceEditor->showNormal();
|
||||
} else {
|
||||
preferenceEditor->activateWindow();
|
||||
}
|
||||
}
|
||||
|
||||
NoScrollComboBox *themeSelector = new NoScrollComboBox();
|
||||
themeSelector->addItems(themes);
|
||||
themeSelector->setCurrentText(porymapConfig.getTheme());
|
||||
form.addRow(new QLabel("Themes"), themeSelector);
|
||||
void MainWindow::togglePreferenceSpecificUi() {
|
||||
if (porymapConfig.getTextEditorGotoLine().isEmpty()) {
|
||||
for (auto *button : openScriptButtons)
|
||||
button->hide();
|
||||
} else {
|
||||
for (auto *button : openScriptButtons)
|
||||
button->show();
|
||||
}
|
||||
|
||||
QDialogButtonBox buttonBox(QDialogButtonBox::Apply | QDialogButtonBox::Close, Qt::Horizontal, &themeSelectorWindow);
|
||||
form.addRow(&buttonBox);
|
||||
connect(&buttonBox, &QDialogButtonBox::clicked, [&buttonBox, themeSelector, this](QAbstractButton *button){
|
||||
if (button == buttonBox.button(QDialogButtonBox::Apply)) {
|
||||
QString theme = themeSelector->currentText();
|
||||
porymapConfig.setTheme(theme);
|
||||
this->setTheme(theme);
|
||||
editor->maskNonVisibleConnectionTiles();
|
||||
}
|
||||
});
|
||||
connect(&buttonBox, SIGNAL(rejected()), &themeSelectorWindow, SLOT(reject()));
|
||||
|
||||
themeSelectorWindow.exec();
|
||||
if (porymapConfig.getTextEditorOpenFolder().isEmpty())
|
||||
ui->actionOpen_Project_in_Text_Editor->setEnabled(false);
|
||||
else
|
||||
ui->actionOpen_Project_in_Text_Editor->setEnabled(true);
|
||||
}
|
||||
|
||||
void MainWindow::on_pushButton_AddCustomHeaderField_clicked()
|
||||
|
@ -2632,20 +2782,9 @@ void MainWindow::on_horizontalSlider_MetatileZoom_valueChanged(int value) {
|
|||
|
||||
void MainWindow::on_actionRegion_Map_Editor_triggered() {
|
||||
if (!this->regionMapEditor) {
|
||||
this->regionMapEditor = new RegionMapEditor(this, this->editor->project);
|
||||
bool success = this->regionMapEditor->loadRegionMapData()
|
||||
&& this->regionMapEditor->loadCityMaps();
|
||||
if (!success) {
|
||||
delete this->regionMapEditor;
|
||||
this->regionMapEditor = nullptr;
|
||||
QMessageBox msgBox(this);
|
||||
QString errorMsg = QString("There was an error opening the region map data. Please see %1 for full error details.\n\n%3")
|
||||
.arg(getLogPath())
|
||||
.arg(getMostRecentError());
|
||||
msgBox.critical(nullptr, "Error Opening Region Map Editor", errorMsg);
|
||||
if (!initRegionMapEditor()) {
|
||||
return;
|
||||
}
|
||||
connect(this->regionMapEditor, &QObject::destroyed, [=](QObject *) { this->regionMapEditor = nullptr; });
|
||||
}
|
||||
|
||||
if (!this->regionMapEditor->isVisible()) {
|
||||
|
@ -2657,6 +2796,26 @@ void MainWindow::on_actionRegion_Map_Editor_triggered() {
|
|||
}
|
||||
}
|
||||
|
||||
bool MainWindow::initRegionMapEditor() {
|
||||
this->regionMapEditor = new RegionMapEditor(this, this->editor->project);
|
||||
bool success = this->regionMapEditor->loadRegionMapData()
|
||||
&& this->regionMapEditor->loadCityMaps();
|
||||
if (!success) {
|
||||
delete this->regionMapEditor;
|
||||
this->regionMapEditor = nullptr;
|
||||
QMessageBox msgBox(this);
|
||||
QString errorMsg = QString("There was an error opening the region map data. Please see %1 for full error details.\n\n%3")
|
||||
.arg(getLogPath())
|
||||
.arg(getMostRecentError());
|
||||
msgBox.critical(nullptr, "Error Opening Region Map Editor", errorMsg);
|
||||
|
||||
return false;
|
||||
}
|
||||
connect(this->regionMapEditor, &QObject::destroyed, [=](QObject *) { this->regionMapEditor = nullptr; });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MainWindow::closeSupplementaryWindows() {
|
||||
if (this->tilesetEditor)
|
||||
delete this->tilesetEditor;
|
||||
|
@ -2666,6 +2825,8 @@ void MainWindow::closeSupplementaryWindows() {
|
|||
delete this->mapImageExporter;
|
||||
if (this->newmapprompt)
|
||||
delete this->newmapprompt;
|
||||
if (this->shortcutsEditor)
|
||||
delete this->shortcutsEditor;
|
||||
}
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent *event) {
|
||||
|
@ -2694,6 +2855,8 @@ void MainWindow::closeEvent(QCloseEvent *event) {
|
|||
this->ui->splitter_main->saveState()
|
||||
);
|
||||
porymapConfig.save();
|
||||
projectConfig.save();
|
||||
shortcutsConfig.save();
|
||||
|
||||
QMainWindow::closeEvent(event);
|
||||
}
|
||||
|
|
|
@ -1361,9 +1361,11 @@ void Project::saveMap(Map *map) {
|
|||
mapObj["requires_flash"] = map->requiresFlash.toInt() > 0 || map->requiresFlash == "TRUE";
|
||||
mapObj["weather"] = map->weather;
|
||||
mapObj["map_type"] = map->type;
|
||||
mapObj["allow_cycling"] = map->allowBiking.toInt() > 0 || map->allowBiking == "TRUE";
|
||||
mapObj["allow_escaping"] = map->allowEscapeRope.toInt() > 0 || map->allowEscapeRope == "TRUE";
|
||||
mapObj["allow_running"] = map->allowRunning.toInt() > 0 || map->allowRunning == "TRUE";
|
||||
if (projectConfig.getBaseGameVersion() != BaseGameVersion::pokeruby) {
|
||||
mapObj["allow_cycling"] = map->allowBiking.toInt() > 0 || map->allowBiking == "TRUE";
|
||||
mapObj["allow_escaping"] = map->allowEscapeRope.toInt() > 0 || map->allowEscapeRope == "TRUE";
|
||||
mapObj["allow_running"] = map->allowRunning.toInt() > 0 || map->allowRunning == "TRUE";
|
||||
}
|
||||
mapObj["show_map_name"] = map->show_location.toInt() > 0 || map->show_location == "TRUE";
|
||||
if (projectConfig.getFloorNumberEnabled()) {
|
||||
mapObj["floor_number"] = map->floorNumber;
|
||||
|
@ -2408,7 +2410,7 @@ QString Project::fixGraphicPath(QString path) {
|
|||
return path;
|
||||
}
|
||||
|
||||
QString Project::getScriptFileExtension(bool usePoryScript) {
|
||||
QString Project::getScriptFileExtension(bool usePoryScript) const {
|
||||
if(usePoryScript) {
|
||||
return ".pory";
|
||||
} else {
|
||||
|
@ -2416,13 +2418,50 @@ QString Project::getScriptFileExtension(bool usePoryScript) {
|
|||
}
|
||||
}
|
||||
|
||||
QString Project::getScriptDefaultString(bool usePoryScript, QString mapName) {
|
||||
QString Project::getScriptDefaultString(bool usePoryScript, QString mapName) const {
|
||||
if(usePoryScript)
|
||||
return QString("mapscripts %1_MapScripts {}").arg(mapName);
|
||||
else
|
||||
return QString("%1_MapScripts::\n\t.byte 0\n").arg(mapName);
|
||||
}
|
||||
|
||||
QString Project::getMapScriptsFilePath(const QString &mapName) const {
|
||||
const bool usePoryscript = projectConfig.getUsePoryScript();
|
||||
auto path = QDir::cleanPath(root + "/data/maps/" + mapName + "/scripts");
|
||||
auto extension = getScriptFileExtension(usePoryscript);
|
||||
if (usePoryscript && !QFile::exists(path + extension))
|
||||
extension = getScriptFileExtension(false);
|
||||
path += extension;
|
||||
return path;
|
||||
}
|
||||
|
||||
QStringList Project::getEventScriptsFilePaths() const {
|
||||
QStringList filePaths(QDir::cleanPath(root + "/data/event_scripts.s"));
|
||||
const QString scriptsDir = QDir::cleanPath(root + "/data/scripts");
|
||||
const QString mapsDir = QDir::cleanPath(root + "/data/maps");
|
||||
const bool usePoryscript = projectConfig.getUsePoryScript();
|
||||
|
||||
if (usePoryscript) {
|
||||
QDirIterator it_pory_shared(scriptsDir, {"*.pory"}, QDir::Files);
|
||||
while (it_pory_shared.hasNext())
|
||||
filePaths << it_pory_shared.next();
|
||||
|
||||
QDirIterator it_pory_maps(mapsDir, {"scripts.pory"}, QDir::Files, QDirIterator::Subdirectories);
|
||||
while (it_pory_maps.hasNext())
|
||||
filePaths << it_pory_maps.next();
|
||||
}
|
||||
|
||||
QDirIterator it_inc_shared(scriptsDir, {"*.inc"}, QDir::Files);
|
||||
while (it_inc_shared.hasNext())
|
||||
filePaths << it_inc_shared.next();
|
||||
|
||||
QDirIterator it_inc_maps(mapsDir, {"scripts.inc"}, QDir::Files, QDirIterator::Subdirectories);
|
||||
while (it_inc_maps.hasNext())
|
||||
filePaths << it_inc_maps.next();
|
||||
|
||||
return filePaths;
|
||||
}
|
||||
|
||||
void Project::loadEventPixmaps(QList<Event*> objects) {
|
||||
bool needs_update = false;
|
||||
for (Event *object : objects) {
|
||||
|
|
|
@ -21,3 +21,10 @@ void GraphicsView::drawForeground(QPainter *painter, const QRectF&) {
|
|||
item->render(painter);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsView::moveEvent(QMoveEvent *event) {
|
||||
QGraphicsView::moveEvent(event);
|
||||
QLabel *label_MapRulerStatus = findChild<QLabel *>("label_MapRulerStatus", Qt::FindDirectChildrenOnly);
|
||||
if (label_MapRulerStatus && label_MapRulerStatus->isVisible())
|
||||
label_MapRulerStatus->move(mapToGlobal(QPoint(6, 6)));
|
||||
}
|
||||
|
|
|
@ -1,28 +1,34 @@
|
|||
#include "mapruler.h"
|
||||
#include "metatile.h"
|
||||
|
||||
#include <QGraphicsObject>
|
||||
#include <QGraphicsSceneEvent>
|
||||
#include <QPainter>
|
||||
#include <QColor>
|
||||
#include <QToolTip>
|
||||
|
||||
int MapRuler::thickness = 3;
|
||||
#include <QVector>
|
||||
|
||||
|
||||
void MapRuler::init() {
|
||||
setVisible(false);
|
||||
setPoints(QPoint(), QPoint());
|
||||
anchored = false;
|
||||
locked = false;
|
||||
statusMessage = QString("Ruler: 0");
|
||||
xRuler = QRect();
|
||||
yRuler = QRect();
|
||||
cornerTick = QLine();
|
||||
MapRuler::MapRuler(int thickness, QColor innerColor, QColor borderColor) :
|
||||
/* The logical representation of rectangles are always one less than
|
||||
* the rendered shape, so we subtract 1 from thickness. */
|
||||
thickness(thickness - 1),
|
||||
half_thickness(qreal(thickness - 1) / 2.0),
|
||||
innerColor(innerColor),
|
||||
borderColor(borderColor),
|
||||
mapSize(QSize()),
|
||||
xRuler(QRectF()),
|
||||
yRuler(QRectF()),
|
||||
cornerTick(QLineF()),
|
||||
anchored(false),
|
||||
locked(false)
|
||||
{
|
||||
connect(this, &QGraphicsObject::enabledChanged, [this]() {
|
||||
if (!isEnabled() && anchored)
|
||||
reset();
|
||||
});
|
||||
}
|
||||
|
||||
QRectF MapRuler::boundingRect() const {
|
||||
return QRectF(-thickness, -thickness, pixWidth() + thickness * 2, pixHeight() + thickness * 2);
|
||||
return QRectF(-(half_thickness + 1), -(half_thickness + 1), pixWidth() + thickness + 2, pixHeight() + thickness + 2);
|
||||
}
|
||||
|
||||
QPainterPath MapRuler::shape() const {
|
||||
|
@ -31,10 +37,12 @@ QPainterPath MapRuler::shape() const {
|
|||
ruler.addRect(xRuler);
|
||||
ruler.addRect(yRuler);
|
||||
ruler = ruler.simplified();
|
||||
for (int x = 17.5; x < pixWidth(); x += 16)
|
||||
for (int x = 16; x < pixWidth(); x += 16)
|
||||
ruler.addRect(x, xRuler.y(), 0, thickness);
|
||||
for (int y = 17.5; y < pixHeight(); y += 16)
|
||||
for (int y = 16; y < pixHeight(); y += 16)
|
||||
ruler.addRect(yRuler.x(), y, thickness, 0);
|
||||
if (deltaX() && deltaY())
|
||||
ruler.addPolygon(QVector<QPointF>({ cornerTick.p1(), cornerTick.p2() }));
|
||||
return ruler;
|
||||
}
|
||||
|
||||
|
@ -42,20 +50,16 @@ void MapRuler::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidge
|
|||
painter->setPen(QPen(borderColor));
|
||||
painter->setBrush(QBrush(innerColor));
|
||||
painter->drawPath(shape());
|
||||
if (deltaX() && deltaY())
|
||||
painter->drawLine(cornerTick);
|
||||
}
|
||||
|
||||
bool MapRuler::eventFilter(QObject*, QEvent *event) {
|
||||
bool MapRuler::eventFilter(QObject *, QEvent *event) {
|
||||
if (!isEnabled() || mapSize.isEmpty())
|
||||
return false;
|
||||
|
||||
if (event->type() == QEvent::GraphicsSceneMousePress || event->type() == QEvent::GraphicsSceneMouseMove) {
|
||||
auto mouse_event = static_cast<QGraphicsSceneMouseEvent *>(event);
|
||||
auto *mouse_event = static_cast<QGraphicsSceneMouseEvent *>(event);
|
||||
if (mouse_event->button() == Qt::RightButton || anchored) {
|
||||
mouseEvent(mouse_event);
|
||||
event->accept();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,26 +68,53 @@ bool MapRuler::eventFilter(QObject*, QEvent *event) {
|
|||
|
||||
void MapRuler::mouseEvent(QGraphicsSceneMouseEvent *event) {
|
||||
if (!anchored && event->button() == Qt::RightButton) {
|
||||
setAnchor(event->scenePos(), event->screenPos());
|
||||
setAnchor(event->scenePos());
|
||||
} else if (anchored) {
|
||||
if (event->button() == Qt::LeftButton)
|
||||
locked = !locked;
|
||||
if (event->button() == Qt::RightButton)
|
||||
endAnchor();
|
||||
reset();
|
||||
else
|
||||
setEndPos(event->scenePos(), event->screenPos());
|
||||
setEndPos(event->scenePos());
|
||||
}
|
||||
}
|
||||
|
||||
void MapRuler::setMapDimensions(const QSize &size) {
|
||||
mapSize = size;
|
||||
init();
|
||||
reset();
|
||||
}
|
||||
|
||||
void MapRuler::setEnabled(bool enabled) {
|
||||
QGraphicsItem::setEnabled(enabled);
|
||||
if (!enabled && anchored)
|
||||
endAnchor();
|
||||
void MapRuler::reset() {
|
||||
prepareGeometryChange();
|
||||
hide();
|
||||
setPoints(QPoint(), QPoint());
|
||||
xRuler = QRectF();
|
||||
yRuler = QRectF();
|
||||
cornerTick = QLineF();
|
||||
anchored = false;
|
||||
locked = false;
|
||||
emit statusChanged(QString());
|
||||
}
|
||||
|
||||
void MapRuler::setAnchor(const QPointF &scenePos) {
|
||||
QPoint pos = Metatile::coordFromPixmapCoord(scenePos);
|
||||
pos = snapToWithinBounds(pos);
|
||||
anchored = true;
|
||||
locked = false;
|
||||
setPoints(pos, pos);
|
||||
updateGeometry();
|
||||
show();
|
||||
}
|
||||
|
||||
void MapRuler::setEndPos(const QPointF &scenePos) {
|
||||
if (locked)
|
||||
return;
|
||||
QPoint pos = Metatile::coordFromPixmapCoord(scenePos);
|
||||
pos = snapToWithinBounds(pos);
|
||||
const QPoint lastEndPos = endPos();
|
||||
setP2(pos);
|
||||
if (pos != lastEndPos)
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
QPoint MapRuler::snapToWithinBounds(QPoint pos) const {
|
||||
|
@ -98,80 +129,62 @@ QPoint MapRuler::snapToWithinBounds(QPoint pos) const {
|
|||
return pos;
|
||||
}
|
||||
|
||||
void MapRuler::setAnchor(const QPointF &scenePos, const QPoint &screenPos) {
|
||||
QPoint pos = Metatile::coordFromPixmapCoord(scenePos);
|
||||
pos = snapToWithinBounds(pos);
|
||||
anchored = true;
|
||||
locked = false;
|
||||
setPoints(pos, pos);
|
||||
updateGeometry();
|
||||
setVisible(true);
|
||||
showDimensions(screenPos);
|
||||
}
|
||||
|
||||
void MapRuler::endAnchor() {
|
||||
emit deactivated(endPos());
|
||||
hideDimensions();
|
||||
prepareGeometryChange();
|
||||
init();
|
||||
}
|
||||
|
||||
void MapRuler::setEndPos(const QPointF &scenePos, const QPoint &screenPos) {
|
||||
if (locked)
|
||||
return;
|
||||
QPoint pos = Metatile::coordFromPixmapCoord(scenePos);
|
||||
pos = snapToWithinBounds(pos);
|
||||
const QPoint lastEndPos = endPos();
|
||||
setP2(pos);
|
||||
if (pos != lastEndPos)
|
||||
updateGeometry();
|
||||
showDimensions(screenPos);
|
||||
}
|
||||
|
||||
void MapRuler::showDimensions(const QPoint &screenPos) const {
|
||||
// This is a hack to make the tool tip follow the cursor since it won't change position if the text is the same.
|
||||
QToolTip::showText(screenPos + QPoint(16, -8), statusMessage + ' ');
|
||||
QToolTip::showText(screenPos + QPoint(16, -8), statusMessage);
|
||||
}
|
||||
|
||||
void MapRuler::hideDimensions() const {
|
||||
QToolTip::hideText();
|
||||
}
|
||||
|
||||
void MapRuler::updateGeometry() {
|
||||
prepareGeometryChange();
|
||||
setPos(QPoint(left() * 16 + 7, top() * 16 + 7));
|
||||
setPos(QPoint(left() * 16 + 8, top() * 16 + 8));
|
||||
/* Determine what quadrant the end point is in relative to the anchor point. The anchor
|
||||
* point is the top-left corner of the metatile the ruler starts in, so a zero-length(s)
|
||||
* point is the top-left corner of the metatile the ruler starts in, so a zero-length
|
||||
* ruler is considered to be in the bottom-right quadrant from the anchor point. */
|
||||
if (deltaX() < 0 && deltaY() < 0) {
|
||||
// Top-left
|
||||
xRuler = QRect(0, pixHeight(), pixWidth() + thickness, thickness);
|
||||
yRuler = QRect(0, 0, thickness, pixHeight() + thickness);
|
||||
xRuler = QRectF(-half_thickness, pixHeight() - half_thickness, pixWidth() + thickness, thickness);
|
||||
yRuler = QRectF(-half_thickness, -half_thickness, thickness, pixHeight() + thickness);
|
||||
cornerTick = QLineF(yRuler.x() + 0.5, xRuler.y() + thickness - 0.5, yRuler.x() + thickness, xRuler.y());
|
||||
statusMessage = QString("Ruler: Left %1, Up %2").arg(width()).arg(height());
|
||||
updateStatus(Qt::TopLeftCorner);
|
||||
} else if (deltaX() < 0) {
|
||||
// Bottom-left
|
||||
xRuler = QRect(0, 0, pixWidth() + thickness, thickness);
|
||||
yRuler = QRect(0, 0, thickness, pixHeight() + thickness);
|
||||
xRuler = QRectF(-half_thickness, -half_thickness, pixWidth() + thickness, thickness);
|
||||
yRuler = QRectF(-half_thickness, -half_thickness, thickness, pixHeight() + thickness);
|
||||
cornerTick = QLineF(xRuler.x() + 0.5, xRuler.y() + 0.5, xRuler.x() + thickness, xRuler.y() + thickness);
|
||||
updateStatus(Qt::BottomLeftCorner);
|
||||
} else if (deltaY() < 0) {
|
||||
// Top-right
|
||||
xRuler = QRectF(-half_thickness, pixHeight() - half_thickness, pixWidth() + thickness, thickness);
|
||||
yRuler = QRectF(pixWidth() - half_thickness, -half_thickness, thickness, pixHeight() + thickness);
|
||||
cornerTick = QLineF(yRuler.x(), xRuler.y(), yRuler.x() + thickness - 0.5, xRuler.y() + thickness - 0.5);
|
||||
updateStatus(Qt::TopRightCorner);
|
||||
} else {
|
||||
// Bottom-right
|
||||
xRuler = QRectF(-half_thickness, -half_thickness, pixWidth() + thickness, thickness);
|
||||
yRuler = QRectF(pixWidth() - half_thickness, -half_thickness, thickness, pixHeight() + thickness);
|
||||
cornerTick = QLineF(yRuler.x(), yRuler.y() + thickness, yRuler.x() + thickness - 0.5, yRuler.y() + 0.5);
|
||||
updateStatus(Qt::BottomRightCorner);
|
||||
}
|
||||
}
|
||||
|
||||
void MapRuler::updateStatus(Qt::Corner corner) {
|
||||
QString statusMessage;
|
||||
switch (corner)
|
||||
{
|
||||
case Qt::TopLeftCorner:
|
||||
statusMessage = QString("Ruler: Left %1, Up %2\nStart(%3, %4), End(%5, %6)").arg(width()).arg(height())
|
||||
.arg(anchor().x()).arg(anchor().y()).arg(endPos().x()).arg(endPos().y());
|
||||
break;
|
||||
case Qt::BottomLeftCorner:
|
||||
statusMessage = QString("Ruler: Left %1").arg(width());
|
||||
if (deltaY())
|
||||
statusMessage += QString(", Down %1").arg(height());
|
||||
} else if (deltaY() < 0) {
|
||||
// Top-right
|
||||
xRuler = QRect(0, pixHeight(), pixWidth() + thickness, thickness);
|
||||
yRuler = QRect(pixWidth(), 0, thickness, pixHeight() + thickness);
|
||||
cornerTick = QLineF(yRuler.x(), xRuler.y(), yRuler.x() + thickness - 0.5, xRuler.y() + thickness - 0.5);
|
||||
statusMessage += QString("\nStart(%1, %2), End(%3, %4)")
|
||||
.arg(anchor().x()).arg(anchor().y()).arg(endPos().x()).arg(endPos().y());
|
||||
break;
|
||||
case Qt::TopRightCorner:
|
||||
statusMessage = QString("Ruler: ");
|
||||
if (deltaX())
|
||||
statusMessage += QString("Right %1, ").arg(width());
|
||||
statusMessage += QString("Up %1").arg(height());
|
||||
} else {
|
||||
// Bottom-right
|
||||
xRuler = QRect(0, 0, pixWidth() + thickness, thickness);
|
||||
yRuler = QRect(pixWidth(), 0, thickness, pixHeight() + thickness);
|
||||
cornerTick = QLineF(yRuler.x(), yRuler.y() + thickness, yRuler.x() + thickness - 0.5, yRuler.y() + 0.5);
|
||||
statusMessage += QString("Up %1\nStart(%2, %3), End(%4, %5)").arg(height())
|
||||
.arg(anchor().x()).arg(anchor().y()).arg(endPos().x()).arg(endPos().y());
|
||||
break;
|
||||
case Qt::BottomRightCorner:
|
||||
statusMessage = QString("Ruler: ");
|
||||
if (deltaX() || deltaY()) {
|
||||
if (deltaX())
|
||||
|
@ -181,9 +194,13 @@ void MapRuler::updateGeometry() {
|
|||
statusMessage += ", ";
|
||||
statusMessage += QString("Down: %1").arg(height());
|
||||
}
|
||||
statusMessage += QString("\nStart(%1, %2), End(%3, %4)")
|
||||
.arg(anchor().x()).arg(anchor().y()).arg(endPos().x()).arg(endPos().y());
|
||||
} else {
|
||||
statusMessage += QString("0");
|
||||
statusMessage += QString("0\nStart(%1, %2)")
|
||||
.arg(anchor().x()).arg(anchor().y());
|
||||
}
|
||||
break;
|
||||
}
|
||||
emit lengthChanged();
|
||||
emit statusChanged(statusMessage);
|
||||
}
|
||||
|
|
185
src/ui/multikeyedit.cpp
Normal file
185
src/ui/multikeyedit.cpp
Normal file
|
@ -0,0 +1,185 @@
|
|||
#include "multikeyedit.h"
|
||||
|
||||
#include <QLineEdit>
|
||||
#include <QHBoxLayout>
|
||||
#include <QtEvents>
|
||||
#include <QMenu>
|
||||
#include <QAction>
|
||||
|
||||
|
||||
MultiKeyEdit::MultiKeyEdit(QWidget *parent, int fieldCount) :
|
||||
QWidget(parent),
|
||||
keySequenceEdit_vec(QVector<QKeySequenceEdit *>()),
|
||||
keySequence_list(QList<QKeySequence>())
|
||||
{
|
||||
setLayout(new QHBoxLayout(this));
|
||||
layout()->setContentsMargins(0, 0, 0, 0);
|
||||
setFieldCount(fieldCount);
|
||||
}
|
||||
|
||||
bool MultiKeyEdit::eventFilter(QObject *watched, QEvent *event) {
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
auto *watched_kse = qobject_cast<QKeySequenceEdit *>(watched);
|
||||
if (!watched_kse)
|
||||
return false;
|
||||
|
||||
auto *keyEvent = static_cast<QKeyEvent *>(event);
|
||||
if (keyEvent->key() == Qt::Key_Escape) {
|
||||
watched_kse->clearFocus();
|
||||
return true;
|
||||
} else {
|
||||
watched_kse->clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::ContextMenu) {
|
||||
auto *watched_lineEdit = qobject_cast<QLineEdit *>(watched);
|
||||
if (!watched_lineEdit)
|
||||
return false;
|
||||
|
||||
auto *contextMenuEvent = static_cast<QContextMenuEvent *>(event);
|
||||
if (contextMenuPolicy() == Qt::DefaultContextMenu) {
|
||||
showDefaultContextMenu(watched_lineEdit, contextMenuEvent->pos());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int MultiKeyEdit::fieldCount() const {
|
||||
return keySequenceEdit_vec.count();
|
||||
}
|
||||
|
||||
void MultiKeyEdit::setFieldCount(int count) {
|
||||
if (count < 1)
|
||||
count = 1;
|
||||
|
||||
while (keySequenceEdit_vec.count() < count)
|
||||
addNewKeySequenceEdit();
|
||||
|
||||
while (keySequenceEdit_vec.count() > count)
|
||||
delete keySequenceEdit_vec.takeLast();
|
||||
|
||||
alignKeySequencesLeft();
|
||||
}
|
||||
|
||||
QList<QKeySequence> MultiKeyEdit::keySequences() const {
|
||||
QList<QKeySequence> current_keySequences;
|
||||
for (auto *kse : keySequenceEdit_vec)
|
||||
if (!kse->keySequence().isEmpty())
|
||||
current_keySequences.append(kse->keySequence());
|
||||
return current_keySequences;
|
||||
}
|
||||
|
||||
bool MultiKeyEdit::removeOne(const QKeySequence &keySequence) {
|
||||
for (auto *keySequenceEdit : keySequenceEdit_vec) {
|
||||
if (keySequenceEdit->keySequence() == keySequence) {
|
||||
keySequence_list.removeOne(keySequence);
|
||||
keySequenceEdit->clear();
|
||||
alignKeySequencesLeft();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MultiKeyEdit::contains(const QKeySequence &keySequence) const {
|
||||
for (auto current_keySequence : keySequences())
|
||||
if (current_keySequence == keySequence)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void MultiKeyEdit::setContextMenuPolicy(Qt::ContextMenuPolicy policy) {
|
||||
QWidget::setContextMenuPolicy(policy);
|
||||
auto lineEdit_children = findChildren<QLineEdit *>();
|
||||
for (auto *lineEdit : lineEdit_children)
|
||||
lineEdit->setContextMenuPolicy(policy);
|
||||
}
|
||||
|
||||
bool MultiKeyEdit::isClearButtonEnabled() const {
|
||||
return findChild<QLineEdit *>()->isClearButtonEnabled();
|
||||
}
|
||||
|
||||
void MultiKeyEdit::setClearButtonEnabled(bool enable) {
|
||||
for (auto *lineEdit : findChildren<QLineEdit *>())
|
||||
lineEdit->setClearButtonEnabled(enable);
|
||||
}
|
||||
|
||||
void MultiKeyEdit::clear() {
|
||||
for (auto *keySequenceEdit : keySequenceEdit_vec)
|
||||
keySequenceEdit->clear();
|
||||
keySequence_list.clear();
|
||||
}
|
||||
|
||||
void MultiKeyEdit::setKeySequences(const QList<QKeySequence> &keySequences) {
|
||||
clear();
|
||||
keySequence_list = keySequences;
|
||||
int minCount = qMin(keySequenceEdit_vec.count(), keySequence_list.count());
|
||||
for (int i = 0; i < minCount; ++i)
|
||||
keySequenceEdit_vec[i]->setKeySequence(keySequence_list[i]);
|
||||
}
|
||||
|
||||
void MultiKeyEdit::addKeySequence(const QKeySequence &keySequence) {
|
||||
keySequenceEdit_vec.last()->setKeySequence(keySequence);
|
||||
alignKeySequencesLeft();
|
||||
}
|
||||
|
||||
void MultiKeyEdit::addNewKeySequenceEdit() {
|
||||
auto *keySequenceEdit = new QKeySequenceEdit(this);
|
||||
keySequenceEdit->installEventFilter(this);
|
||||
connect(keySequenceEdit, &QKeySequenceEdit::editingFinished,
|
||||
this, &MultiKeyEdit::onEditingFinished);
|
||||
connect(keySequenceEdit, &QKeySequenceEdit::keySequenceChanged,
|
||||
this, &MultiKeyEdit::keySequenceChanged);
|
||||
|
||||
auto *lineEdit = keySequenceEdit->findChild<QLineEdit *>();
|
||||
lineEdit->setClearButtonEnabled(true);
|
||||
lineEdit->installEventFilter(this);
|
||||
connect(lineEdit, &QLineEdit::customContextMenuRequested,
|
||||
this, &MultiKeyEdit::customContextMenuRequested);
|
||||
|
||||
layout()->addWidget(keySequenceEdit);
|
||||
keySequenceEdit_vec.append(keySequenceEdit);
|
||||
}
|
||||
|
||||
// Shift all key sequences left if there are any empty QKeySequenceEdit's.
|
||||
void MultiKeyEdit::alignKeySequencesLeft() {
|
||||
blockSignals(true);
|
||||
setKeySequences(keySequences());
|
||||
blockSignals(false);
|
||||
}
|
||||
|
||||
void MultiKeyEdit::setFocusToLastNonEmptyKeySequenceEdit() {
|
||||
for (auto it = keySequenceEdit_vec.rbegin(); it != keySequenceEdit_vec.rend(); ++it) {
|
||||
if (!(*it)->keySequence().isEmpty()) {
|
||||
(*it)->setFocus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MultiKeyEdit::onEditingFinished() {
|
||||
auto *keySequenceEdit = qobject_cast<QKeySequenceEdit *>(sender());
|
||||
if (keySequenceEdit && keySequence_list.contains(keySequenceEdit->keySequence()))
|
||||
removeOne(keySequenceEdit->keySequence());
|
||||
alignKeySequencesLeft();
|
||||
setFocusToLastNonEmptyKeySequenceEdit();
|
||||
|
||||
emit editingFinished();
|
||||
}
|
||||
|
||||
/* QKeySequenceEdit doesn't send or receive context menu events, but it owns QLineEdit that does.
|
||||
* This QLineEdit hijacks those events and so we need to filter/connect to it directly, rather than
|
||||
* the QKeySequenceEdit. I wouldn't be surprised if Qt fixed this in the future, in which case any
|
||||
* context menu related code in this class might need to change. */
|
||||
void MultiKeyEdit::showDefaultContextMenu(QLineEdit *lineEdit, const QPoint &pos) {
|
||||
QMenu menu(this);
|
||||
QAction clearAction("Clear Shortcut", &menu);
|
||||
connect(&clearAction, &QAction::triggered, lineEdit, [this, &lineEdit]() {
|
||||
removeOne(lineEdit->text());
|
||||
});
|
||||
menu.addAction(&clearAction);
|
||||
menu.exec(lineEdit->mapToGlobal(pos));
|
||||
}
|
|
@ -185,7 +185,9 @@ void NewMapPopup::on_pushButton_NewMap_Accept_clicked() {
|
|||
MapLayout *layout;
|
||||
|
||||
// If map name is not unique, use default value. Also use only valid characters.
|
||||
// After stripping invalid characters, strip any leading digits.
|
||||
QString newMapName = this->ui->lineEdit_NewMap_Name->text().remove(QRegularExpression("[^a-zA-Z0-9_]+"));
|
||||
newMapName.remove(QRegularExpression("^[0-9]*"));
|
||||
if (project->mapNames->contains(newMapName) || newMapName.isEmpty()) {
|
||||
newMapName = project->getNewMapName();
|
||||
}
|
||||
|
|
72
src/ui/preferenceeditor.cpp
Normal file
72
src/ui/preferenceeditor.cpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
#include "preferenceeditor.h"
|
||||
#include "ui_preferenceeditor.h"
|
||||
#include "config.h"
|
||||
#include "noscrollcombobox.h"
|
||||
|
||||
#include <QAbstractButton>
|
||||
#include <QRegularExpression>
|
||||
#include <QDirIterator>
|
||||
#include <QFormLayout>
|
||||
|
||||
|
||||
PreferenceEditor::PreferenceEditor(QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
ui(new Ui::PreferenceEditor),
|
||||
themeSelector(nullptr)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
auto *formLayout = new QFormLayout(ui->groupBox_Themes);
|
||||
themeSelector = new NoScrollComboBox(ui->groupBox_Themes);
|
||||
formLayout->addRow("Themes", themeSelector);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::clicked,
|
||||
this, &PreferenceEditor::dialogButtonClicked);
|
||||
populateFields();
|
||||
}
|
||||
|
||||
PreferenceEditor::~PreferenceEditor()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void PreferenceEditor::populateFields() {
|
||||
QStringList themes = { "default" };
|
||||
QRegularExpression re(":/themes/([A-z0-9_-]+).qss");
|
||||
QDirIterator it(":/themes", QDirIterator::Subdirectories);
|
||||
while (it.hasNext()) {
|
||||
QString themeName = re.match(it.next()).captured(1);
|
||||
themes.append(themeName);
|
||||
}
|
||||
themeSelector->addItems(themes);
|
||||
themeSelector->setCurrentText(porymapConfig.getTheme());
|
||||
|
||||
ui->lineEdit_TextEditorOpenFolder->setText(porymapConfig.getTextEditorOpenFolder());
|
||||
|
||||
ui->lineEdit_TextEditorGotoLine->setText(porymapConfig.getTextEditorGotoLine());
|
||||
}
|
||||
|
||||
void PreferenceEditor::saveFields() {
|
||||
if (themeSelector->currentText() != porymapConfig.getTheme()) {
|
||||
const auto theme = themeSelector->currentText();
|
||||
porymapConfig.setTheme(theme);
|
||||
emit themeChanged(theme);
|
||||
}
|
||||
|
||||
porymapConfig.setTextEditorOpenFolder(ui->lineEdit_TextEditorOpenFolder->text());
|
||||
|
||||
porymapConfig.setTextEditorGotoLine(ui->lineEdit_TextEditorGotoLine->text());
|
||||
|
||||
emit preferencesSaved();
|
||||
}
|
||||
|
||||
void PreferenceEditor::dialogButtonClicked(QAbstractButton *button) {
|
||||
auto buttonRole = ui->buttonBox->buttonRole(button);
|
||||
if (buttonRole == QDialogButtonBox::AcceptRole) {
|
||||
saveFields();
|
||||
close();
|
||||
} else if (buttonRole == QDialogButtonBox::ApplyRole) {
|
||||
saveFields();
|
||||
} else if (buttonRole == QDialogButtonBox::RejectRole) {
|
||||
close();
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
#include "regionmapeditor.h"
|
||||
#include "ui_regionmapeditor.h"
|
||||
#include "imageexport.h"
|
||||
#include "shortcut.h"
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
|
||||
|
@ -24,6 +25,7 @@ RegionMapEditor::RegionMapEditor(QWidget *parent, Project *project_) :
|
|||
this->project = project_;
|
||||
this->region_map = new RegionMap;
|
||||
this->ui->action_RegionMap_Resize->setVisible(false);
|
||||
this->initShortcuts();
|
||||
this->restoreWindowState();
|
||||
}
|
||||
|
||||
|
@ -92,6 +94,39 @@ bool RegionMapEditor::loadCityMaps() {
|
|||
return true;
|
||||
}
|
||||
|
||||
void RegionMapEditor::initShortcuts() {
|
||||
auto *shortcut_RM_Options_delete = new Shortcut(
|
||||
{QKeySequence("Del"), QKeySequence("Backspace")}, this, SLOT(on_pushButton_RM_Options_delete_clicked()));
|
||||
shortcut_RM_Options_delete->setObjectName("shortcut_RM_Options_delete");
|
||||
shortcut_RM_Options_delete->setWhatsThis("Map Layout: Delete Square");
|
||||
|
||||
shortcutsConfig.load();
|
||||
shortcutsConfig.setDefaultShortcuts(shortcutableObjects());
|
||||
applyUserShortcuts();
|
||||
}
|
||||
|
||||
QObjectList RegionMapEditor::shortcutableObjects() const {
|
||||
QObjectList shortcutable_objects;
|
||||
|
||||
for (auto *action : findChildren<QAction *>())
|
||||
if (!action->objectName().isEmpty())
|
||||
shortcutable_objects.append(qobject_cast<QObject *>(action));
|
||||
for (auto *shortcut : findChildren<Shortcut *>())
|
||||
if (!shortcut->objectName().isEmpty())
|
||||
shortcutable_objects.append(qobject_cast<QObject *>(shortcut));
|
||||
|
||||
return shortcutable_objects;
|
||||
}
|
||||
|
||||
void RegionMapEditor::applyUserShortcuts() {
|
||||
for (auto *action : findChildren<QAction *>())
|
||||
if (!action->objectName().isEmpty())
|
||||
action->setShortcuts(shortcutsConfig.userShortcuts(action));
|
||||
for (auto *shortcut : findChildren<Shortcut *>())
|
||||
if (!shortcut->objectName().isEmpty())
|
||||
shortcut->setKeys(shortcutsConfig.userShortcuts(shortcut));
|
||||
}
|
||||
|
||||
void RegionMapEditor::displayRegionMap() {
|
||||
displayRegionMapTileSelector();
|
||||
displayCityMapTileSelector();
|
||||
|
|
153
src/ui/shortcut.cpp
Normal file
153
src/ui/shortcut.cpp
Normal file
|
@ -0,0 +1,153 @@
|
|||
#include "shortcut.h"
|
||||
|
||||
#include <QtEvents>
|
||||
#include <QWhatsThis>
|
||||
|
||||
|
||||
Shortcut::Shortcut(QWidget *parent) :
|
||||
QObject(parent),
|
||||
sc_member(nullptr),
|
||||
sc_ambiguousmember(nullptr),
|
||||
sc_context(Qt::WindowShortcut),
|
||||
sc_vec(QVector<QShortcut *>({new QShortcut(parent)}))
|
||||
{ }
|
||||
|
||||
Shortcut::Shortcut(const QKeySequence &key, QWidget *parent,
|
||||
const char *member, const char *ambiguousMember,
|
||||
Qt::ShortcutContext shortcutContext) :
|
||||
QObject(parent),
|
||||
sc_member(member),
|
||||
sc_ambiguousmember(ambiguousMember),
|
||||
sc_context(shortcutContext),
|
||||
sc_vec(QVector<QShortcut *>())
|
||||
{
|
||||
setKey(key);
|
||||
}
|
||||
|
||||
Shortcut::Shortcut(const QList<QKeySequence> &keys, QWidget *parent,
|
||||
const char *member, const char *ambiguousMember,
|
||||
Qt::ShortcutContext shortcutContext) :
|
||||
QObject(parent),
|
||||
sc_member(member),
|
||||
sc_ambiguousmember(ambiguousMember),
|
||||
sc_context(shortcutContext),
|
||||
sc_vec(QVector<QShortcut *>())
|
||||
{
|
||||
setKeys(keys);
|
||||
}
|
||||
|
||||
Shortcut::~Shortcut()
|
||||
{
|
||||
for (auto *sc : sc_vec)
|
||||
delete sc;
|
||||
}
|
||||
|
||||
void Shortcut::addKey(const QKeySequence &key) {
|
||||
sc_vec.append(new QShortcut(key, parentWidget(), sc_member, sc_ambiguousmember, sc_context));
|
||||
}
|
||||
|
||||
void Shortcut::setKey(const QKeySequence &key) {
|
||||
if (sc_vec.isEmpty()) {
|
||||
addKey(key);
|
||||
} else {
|
||||
while (sc_vec.count() != 1)
|
||||
delete sc_vec.takeLast();
|
||||
sc_vec.first()->setKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
QKeySequence Shortcut::key() const {
|
||||
return sc_vec.first()->key();
|
||||
}
|
||||
|
||||
void Shortcut::addKeys(const QList<QKeySequence> &keys) {
|
||||
for (auto key : keys)
|
||||
addKey(key);
|
||||
}
|
||||
|
||||
void Shortcut::setKeys(const QList<QKeySequence> &keys) {
|
||||
if (keys.isEmpty())
|
||||
return;
|
||||
|
||||
while (sc_vec.count() < keys.count())
|
||||
addKey(QKeySequence());
|
||||
|
||||
while (sc_vec.count() > keys.count())
|
||||
delete sc_vec.takeLast();
|
||||
|
||||
for (int i = 0; i < keys.count(); ++i)
|
||||
sc_vec[i]->setKey(keys[i]);
|
||||
}
|
||||
|
||||
QList<QKeySequence> Shortcut::keys() const {
|
||||
QList<QKeySequence> ks_list = QList<QKeySequence>();
|
||||
for (auto *sc : sc_vec)
|
||||
ks_list.append(sc->key());
|
||||
return ks_list;
|
||||
}
|
||||
|
||||
void Shortcut::setEnabled(bool enable) {
|
||||
for (auto *sc : sc_vec)
|
||||
sc->setEnabled(enable);
|
||||
}
|
||||
|
||||
bool Shortcut::isEnabled() const {
|
||||
return sc_vec.first()->isEnabled();
|
||||
}
|
||||
|
||||
void Shortcut::setContext(Qt::ShortcutContext context) {
|
||||
sc_context = context;
|
||||
for (auto *sc : sc_vec)
|
||||
sc->setContext(context);
|
||||
}
|
||||
|
||||
Qt::ShortcutContext Shortcut::context() const {
|
||||
return sc_context;
|
||||
}
|
||||
|
||||
void Shortcut::setWhatsThis(const QString &text) {
|
||||
for (auto *sc : sc_vec)
|
||||
sc->setWhatsThis(text);
|
||||
}
|
||||
|
||||
QString Shortcut::whatsThis() const {
|
||||
return sc_vec.first()->whatsThis();
|
||||
}
|
||||
|
||||
void Shortcut::setAutoRepeat(bool on) {
|
||||
for (auto *sc : sc_vec)
|
||||
sc->setAutoRepeat(on);
|
||||
}
|
||||
|
||||
bool Shortcut::autoRepeat() const {
|
||||
return sc_vec.first()->autoRepeat();
|
||||
}
|
||||
|
||||
int Shortcut::id() const {
|
||||
return sc_vec.first()->id();
|
||||
}
|
||||
|
||||
QList<int> Shortcut::ids() const {
|
||||
QList<int> id_list;
|
||||
for (auto *sc : sc_vec)
|
||||
id_list.append(sc->id());
|
||||
return id_list;
|
||||
}
|
||||
|
||||
bool Shortcut::event(QEvent *e) {
|
||||
if (isEnabled() && e->type() == QEvent::Shortcut) {
|
||||
auto se = static_cast<QShortcutEvent *>(e);
|
||||
if (ids().contains(se->shortcutId()) && keys().contains(se->key())) {
|
||||
if (QWhatsThis::inWhatsThisMode()) {
|
||||
QWhatsThis::showText(QCursor::pos(), whatsThis());
|
||||
} else {
|
||||
if (se->isAmbiguous())
|
||||
emit activatedAmbiguously();
|
||||
else
|
||||
emit activated();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
188
src/ui/shortcutseditor.cpp
Normal file
188
src/ui/shortcutseditor.cpp
Normal file
|
@ -0,0 +1,188 @@
|
|||
#include "shortcutseditor.h"
|
||||
#include "ui_shortcutseditor.h"
|
||||
#include "config.h"
|
||||
#include "multikeyedit.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <QFormLayout>
|
||||
#include <QAbstractButton>
|
||||
#include <QtEvents>
|
||||
#include <QMessageBox>
|
||||
#include <QRegularExpression>
|
||||
#include <QLabel>
|
||||
|
||||
|
||||
ShortcutsEditor::ShortcutsEditor(QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
ui(new Ui::ShortcutsEditor),
|
||||
main_container(nullptr)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
main_container = ui->scrollAreaWidgetContents_Shortcuts;
|
||||
auto *main_layout = new QVBoxLayout(main_container);
|
||||
main_layout->setSpacing(12);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::clicked,
|
||||
this, &ShortcutsEditor::dialogButtonClicked);
|
||||
}
|
||||
|
||||
ShortcutsEditor::ShortcutsEditor(const QObjectList &shortcutableObjects, QWidget *parent) :
|
||||
ShortcutsEditor(parent)
|
||||
{
|
||||
setShortcutableObjects(shortcutableObjects);
|
||||
}
|
||||
|
||||
ShortcutsEditor::~ShortcutsEditor()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void ShortcutsEditor::setShortcutableObjects(const QObjectList &shortcutableObjects) {
|
||||
parseObjectList(shortcutableObjects);
|
||||
populateMainContainer();
|
||||
}
|
||||
|
||||
void ShortcutsEditor::saveShortcuts() {
|
||||
QMultiMap<const QObject *, QKeySequence> objects_keySequences;
|
||||
for (auto it = multiKeyEdits_objects.cbegin(); it != multiKeyEdits_objects.cend(); ++it) {
|
||||
if (it.key()->keySequences().isEmpty())
|
||||
objects_keySequences.insert(it.value(), QKeySequence());
|
||||
for (auto keySequence : it.key()->keySequences())
|
||||
objects_keySequences.insert(it.value(), keySequence);
|
||||
}
|
||||
|
||||
shortcutsConfig.setUserShortcuts(objects_keySequences);
|
||||
emit shortcutsSaved();
|
||||
}
|
||||
|
||||
// Restores default shortcuts but doesn't save until Apply or OK is clicked.
|
||||
void ShortcutsEditor::resetShortcuts() {
|
||||
for (auto it = multiKeyEdits_objects.begin(); it != multiKeyEdits_objects.end(); ++it) {
|
||||
it.key()->blockSignals(true);
|
||||
const auto defaults = shortcutsConfig.defaultShortcuts(it.value());
|
||||
it.key()->setKeySequences(defaults);
|
||||
it.key()->blockSignals(false);
|
||||
}
|
||||
}
|
||||
|
||||
void ShortcutsEditor::parseObjectList(const QObjectList &objectList) {
|
||||
for (auto *object : objectList) {
|
||||
const auto label = getLabel(object);
|
||||
if (!label.isEmpty() && !object->objectName().isEmpty() && !object->objectName().startsWith("_q_"))
|
||||
labels_objects.insert(label, object);
|
||||
}
|
||||
}
|
||||
|
||||
QString ShortcutsEditor::getLabel(const QObject *object) const {
|
||||
if (stringPropertyIsNotEmpty(object, "text"))
|
||||
return object->property("text").toString().remove('&');
|
||||
else if (stringPropertyIsNotEmpty(object, "whatsThis"))
|
||||
return object->property("whatsThis").toString().remove('&');
|
||||
else
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool ShortcutsEditor::stringPropertyIsNotEmpty(const QObject *object, const char *name) const {
|
||||
return object->property(name).isValid() && !object->property(name).toString().isEmpty();
|
||||
}
|
||||
|
||||
void ShortcutsEditor::populateMainContainer() {
|
||||
for (auto object : labels_objects) {
|
||||
const auto shortcutContext = getShortcutContext(object);
|
||||
if (!contexts_layouts.contains(shortcutContext))
|
||||
addNewContextGroup(shortcutContext);
|
||||
|
||||
addNewMultiKeyEdit(object, shortcutContext);
|
||||
}
|
||||
}
|
||||
|
||||
// The context for which the object's shortcut is active (Displayed in group box title).
|
||||
// Uses the parent window's objectName and adds spaces between words.
|
||||
QString ShortcutsEditor::getShortcutContext(const QObject *object) const {
|
||||
auto objectParentWidget = static_cast<QWidget *>(object->parent());
|
||||
auto context = objectParentWidget->window()->objectName();
|
||||
QRegularExpression re("[A-Z]");
|
||||
int i = context.indexOf(re, 1);
|
||||
while (i != -1) {
|
||||
if (context.at(i - 1) != ' ')
|
||||
context.insert(i++, ' ');
|
||||
i = context.indexOf(re, i + 1);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
// Seperate shortcuts into context groups for duplicate checking.
|
||||
void ShortcutsEditor::addNewContextGroup(const QString &shortcutContext) {
|
||||
auto *groupBox = new QGroupBox(shortcutContext, main_container);
|
||||
main_container->layout()->addWidget(groupBox);
|
||||
auto *formLayout = new QFormLayout(groupBox);
|
||||
contexts_layouts.insert(shortcutContext, formLayout);
|
||||
}
|
||||
|
||||
void ShortcutsEditor::addNewMultiKeyEdit(const QObject *object, const QString &shortcutContext) {
|
||||
auto *container = contexts_layouts.value(shortcutContext)->parentWidget();
|
||||
auto *multiKeyEdit = new MultiKeyEdit(container);
|
||||
multiKeyEdit->setKeySequences(shortcutsConfig.userShortcuts(object));
|
||||
connect(multiKeyEdit, &MultiKeyEdit::keySequenceChanged,
|
||||
this, &ShortcutsEditor::checkForDuplicates);
|
||||
contexts_layouts.value(shortcutContext)->addRow(labels_objects.key(object), multiKeyEdit);
|
||||
multiKeyEdits_objects.insert(multiKeyEdit, object);
|
||||
}
|
||||
|
||||
void ShortcutsEditor::checkForDuplicates(const QKeySequence &keySequence) {
|
||||
if (keySequence.isEmpty())
|
||||
return;
|
||||
|
||||
auto *sender_multiKeyEdit = qobject_cast<MultiKeyEdit *>(sender());
|
||||
if (!sender_multiKeyEdit)
|
||||
return;
|
||||
|
||||
for (auto *sibling_multiKeyEdit : siblings(sender_multiKeyEdit))
|
||||
if (sibling_multiKeyEdit->contains(keySequence))
|
||||
promptUserOnDuplicateFound(sender_multiKeyEdit, sibling_multiKeyEdit);
|
||||
}
|
||||
|
||||
QList<MultiKeyEdit *> ShortcutsEditor::siblings(MultiKeyEdit *multiKeyEdit) const {
|
||||
auto list = multiKeyEdit->parent()->findChildren<MultiKeyEdit *>(QString(), Qt::FindDirectChildrenOnly);
|
||||
list.removeOne(multiKeyEdit);
|
||||
return list;
|
||||
}
|
||||
|
||||
void ShortcutsEditor::promptUserOnDuplicateFound(MultiKeyEdit *sender, MultiKeyEdit *sibling) {
|
||||
const auto duplicateKeySequence = sender->keySequences().last();
|
||||
const auto siblingLabel = getLabel(multiKeyEdits_objects.value(sibling));
|
||||
const auto message = QString(
|
||||
"Shortcut '%1' is already used by '%2', would you like to replace it?")
|
||||
.arg(duplicateKeySequence.toString()).arg(siblingLabel);
|
||||
|
||||
const auto result = QMessageBox::question(
|
||||
this, "porymap", message, QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
|
||||
if (result == QMessageBox::Yes)
|
||||
removeKeySequence(duplicateKeySequence, sibling);
|
||||
else
|
||||
removeKeySequence(duplicateKeySequence, sender);
|
||||
|
||||
activateWindow();
|
||||
}
|
||||
|
||||
void ShortcutsEditor::removeKeySequence(const QKeySequence &keySequence, MultiKeyEdit *multiKeyEdit) {
|
||||
multiKeyEdit->blockSignals(true);
|
||||
multiKeyEdit->removeOne(keySequence);
|
||||
multiKeyEdit->blockSignals(false);
|
||||
}
|
||||
|
||||
void ShortcutsEditor::dialogButtonClicked(QAbstractButton *button) {
|
||||
auto buttonRole = ui->buttonBox->buttonRole(button);
|
||||
if (buttonRole == QDialogButtonBox::AcceptRole) {
|
||||
saveShortcuts();
|
||||
close();
|
||||
} else if (buttonRole == QDialogButtonBox::ApplyRole) {
|
||||
saveShortcuts();
|
||||
} else if (buttonRole == QDialogButtonBox::RejectRole) {
|
||||
close();
|
||||
} else if (buttonRole == QDialogButtonBox::ResetRole) {
|
||||
resetShortcuts();
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
#include "paletteutil.h"
|
||||
#include "imageexport.h"
|
||||
#include "config.h"
|
||||
#include "shortcut.h"
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QDialogButtonBox>
|
||||
|
@ -86,7 +87,6 @@ void TilesetEditor::setTilesets(QString primaryTilesetLabel, QString secondaryTi
|
|||
|
||||
void TilesetEditor::initUi() {
|
||||
ui->setupUi(this);
|
||||
new QShortcut(QKeySequence("Ctrl+Shift+Z"), this, SLOT(on_actionRedo_triggered()));
|
||||
this->tileXFlip = ui->checkBox_xFlip->isChecked();
|
||||
this->tileYFlip = ui->checkBox_yFlip->isChecked();
|
||||
this->paletteId = ui->spinBox_paletteSelector->value();
|
||||
|
@ -102,6 +102,7 @@ void TilesetEditor::initUi() {
|
|||
this->initMetatileLayersItem();
|
||||
this->initTileSelector();
|
||||
this->initSelectedTileItem();
|
||||
this->initShortcuts();
|
||||
this->metatileSelector->select(0);
|
||||
this->restoreWindowState();
|
||||
}
|
||||
|
@ -209,6 +210,48 @@ void TilesetEditor::initSelectedTileItem() {
|
|||
this->ui->graphicsView_selectedTile->setFixedSize(this->selectedTilePixmapItem->pixmap().width() + 2, this->selectedTilePixmapItem->pixmap().height() + 2);
|
||||
}
|
||||
|
||||
void TilesetEditor::initShortcuts() {
|
||||
initExtraShortcuts();
|
||||
|
||||
shortcutsConfig.load();
|
||||
shortcutsConfig.setDefaultShortcuts(shortcutableObjects());
|
||||
applyUserShortcuts();
|
||||
}
|
||||
|
||||
void TilesetEditor::initExtraShortcuts() {
|
||||
ui->actionRedo->setShortcuts({ui->actionRedo->shortcut(), QKeySequence("Ctrl+Shift+Z")});
|
||||
|
||||
auto *shortcut_xFlip = new Shortcut(QKeySequence(), ui->checkBox_xFlip, SLOT(toggle()));
|
||||
shortcut_xFlip->setObjectName("shortcut_xFlip");
|
||||
shortcut_xFlip->setWhatsThis("X Flip");
|
||||
|
||||
auto *shortcut_yFlip = new Shortcut(QKeySequence(), ui->checkBox_yFlip, SLOT(toggle()));
|
||||
shortcut_yFlip->setObjectName("shortcut_yFlip");
|
||||
shortcut_yFlip->setWhatsThis("Y Flip");
|
||||
}
|
||||
|
||||
QObjectList TilesetEditor::shortcutableObjects() const {
|
||||
QObjectList shortcutable_objects;
|
||||
|
||||
for (auto *action : findChildren<QAction *>())
|
||||
if (!action->objectName().isEmpty())
|
||||
shortcutable_objects.append(qobject_cast<QObject *>(action));
|
||||
for (auto *shortcut : findChildren<Shortcut *>())
|
||||
if (!shortcut->objectName().isEmpty())
|
||||
shortcutable_objects.append(qobject_cast<QObject *>(shortcut));
|
||||
|
||||
return shortcutable_objects;
|
||||
}
|
||||
|
||||
void TilesetEditor::applyUserShortcuts() {
|
||||
for (auto *action : findChildren<QAction *>())
|
||||
if (!action->objectName().isEmpty())
|
||||
action->setShortcuts(shortcutsConfig.userShortcuts(action));
|
||||
for (auto *shortcut : findChildren<Shortcut *>())
|
||||
if (!shortcut->objectName().isEmpty())
|
||||
shortcut->setKeys(shortcutsConfig.userShortcuts(shortcut));
|
||||
}
|
||||
|
||||
void TilesetEditor::restoreWindowState() {
|
||||
logInfo("Restoring tileset editor geometry from previous session.");
|
||||
QMap<QString, QByteArray> geometry = porymapConfig.getTilesetEditorGeometry();
|
||||
|
|
Loading…
Reference in a new issue