Merge pull request #652 from GriffinRichards/new-map-dialog

Redesign creation dialogs
This commit is contained in:
GriffinR 2025-01-22 15:53:11 -05:00 committed by GitHub
commit 9047cb8a28
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
89 changed files with 5863 additions and 4323 deletions

View File

@ -45,7 +45,6 @@ The filepath that Porymap expects for each file can be overridden on the ``Files
src/data/region_map/region_map_sections.json, yes, yes, ``json_region_map_entries``,
src/data/region_map/porymap_config.json, yes, yes, ``json_region_porymap_cfg``,
include/constants/global.h, yes, no, ``constants_global``, reads ``define_obj_event_count``
include/constants/map_groups.h, no, yes, ``constants_map_groups``,
include/constants/items.h, yes, no, ``constants_items``, for Hidden Item events
include/constants/flags.h, yes, no, ``constants_flags``, for Object and Hidden Item events
include/constants/vars.h, yes, no, ``constants_vars``, for Trigger events
@ -93,6 +92,7 @@ In addition to these files, there are some specific symbol and macro names that
``symbol_spawn_npcs``, ``u8 sWhiteoutRespawnHealerNpcIds``, the type and table name for Heal Location ``Respawn NPC`` values
``symbol_attribute_table``, ``sMetatileAttrMasks``, optionally read to get settings on ``Tilesets`` tab
``symbol_tilesets_prefix``, ``gTileset_``, for new tileset names and to extract base tileset names
``symbol_dynamic_map_name``, ``Dynamic``, reserved map name to display for ``define_map_dynamic``
``define_obj_event_count``, ``OBJECT_EVENT_TEMPLATES_COUNT``, to limit total Object Events
``define_min_level``, ``MIN_LEVEL``, minimum wild encounters level
``define_max_level``, ``MAX_LEVEL``, maximum wild encounters level

View File

@ -95,11 +95,11 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="mapListPage_Areas">
<widget class="QWidget" name="mapListPage_Locations">
<attribute name="title">
<string>Areas</string>
<string>Locations</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_Areas">
<layout class="QVBoxLayout" name="verticalLayout_Locations">
<property name="spacing">
<number>0</number>
</property>
@ -116,10 +116,10 @@
<number>0</number>
</property>
<item>
<widget class="MapListToolBar" name="mapListToolBar_Areas"/>
<widget class="MapListToolBar" name="mapListToolBar_Locations"/>
</item>
<item>
<widget class="MapTree" name="areaList">
<widget class="MapTree" name="locationList">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
@ -1155,7 +1155,7 @@
<number>2</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_16">
<widget class="QLabel" name="label_Layout">
<property name="text">
<string>Layout</string>
</property>
@ -1740,7 +1740,7 @@
<x>0</x>
<y>0</y>
<width>100</width>
<height>16</height>
<height>30</height>
</rect>
</property>
<property name="sizePolicy">
@ -1834,7 +1834,7 @@
<x>0</x>
<y>0</y>
<width>100</width>
<height>16</height>
<height>30</height>
</rect>
</property>
<property name="sizePolicy">
@ -1928,7 +1928,7 @@
<x>0</x>
<y>0</y>
<width>100</width>
<height>16</height>
<height>30</height>
</rect>
</property>
<property name="sizePolicy">
@ -2028,7 +2028,7 @@
<x>0</x>
<y>0</y>
<width>100</width>
<height>16</height>
<height>30</height>
</rect>
</property>
<property name="sizePolicy">
@ -2122,7 +2122,7 @@
<x>0</x>
<y>0</y>
<width>100</width>
<height>16</height>
<height>30</height>
</rect>
</property>
<property name="sizePolicy">
@ -2220,7 +2220,7 @@
<number>0</number>
</property>
<item>
<widget class="QFrame" name="frame_3">
<widget class="QFrame" name="frame_HeaderData">
<property name="enabled">
<bool>false</bool>
</property>
@ -2236,206 +2236,10 @@
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QFormLayout" name="formLayout_2">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::FieldGrowthPolicy::FieldsStayAtSizeHint</enum>
</property>
<property name="verticalSpacing">
<number>12</number>
</property>
<layout class="QVBoxLayout" name="layout_HeaderData">
<property name="bottomMargin">
<number>9</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Song</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="NoScrollComboBox" name="comboBox_Song">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default background music for this map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Location</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="NoScrollComboBox" name="comboBox_Location">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The section of the region map which the map is grouped under. This also determines the name of the map that is display when the player enters it.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Requires Flash</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="checkBox_Visibility">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Whether or not the map is dark and requires Flash to illuminate.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Weather</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="NoScrollComboBox" name="comboBox_Weather">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default weather for this map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Type</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="NoScrollComboBox" name="comboBox_Type">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The map type is a general attribute, which is used for many different things. For example. it determines whether biking or running is allowed.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Battle scene</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="NoScrollComboBox" name="comboBox_BattleScene">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Determines the type of battle scene graphics to use.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Show Location Name</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QCheckBox" name="checkBox_ShowLocation">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Whether or not to display the location name when the player enters the map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_AllowRunning">
<property name="text">
<string>Allow Running</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QCheckBox" name="checkBox_AllowRunning">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Allows the player to use Running Shoes&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_AllowBiking">
<property name="text">
<string>Allow Biking</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QCheckBox" name="checkBox_AllowBiking">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Allows the player to use a Bike&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="label_AllowEscaping">
<property name="text">
<string>Allow Dig &amp; Escape Rope</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QCheckBox" name="checkBox_AllowEscaping">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Allows the player to use Dig or Escape Rope&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="label_FloorNumber">
<property name="text">
<string>Floor Number</string>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="QSpinBox" name="spinBox_FloorNumber">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Floor number to be used for maps with elevators.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="minimum">
<number>-128</number>
</property>
<property name="maximum">
<number>127</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -2896,8 +2700,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>204</width>
<height>16</height>
<width>100</width>
<height>30</height>
</rect>
</property>
<layout class="QVBoxLayout" name="layout_ConnectionsList">
@ -3126,7 +2930,9 @@
<addaction name="actionMap_Shift"/>
<addaction name="separator"/>
<addaction name="action_NewMap"/>
<addaction name="action_NewLayout"/>
<addaction name="actionNew_Tileset"/>
<addaction name="separator"/>
<addaction name="actionTileset_Editor"/>
<addaction name="actionRegion_Map_Editor"/>
<addaction name="separator"/>
@ -3295,7 +3101,7 @@
<string>Ctrl+T</string>
</property>
</action>
<action name="actionSort_by_Area">
<action name="actionSort_by_Location">
<property name="checkable">
<bool>true</bool>
</property>
@ -3304,7 +3110,7 @@
<normaloff>:/icons/sort_alphabet.ico</normaloff>:/icons/sort_alphabet.ico</iconset>
</property>
<property name="text">
<string>Sort by &amp;Area</string>
<string>Sort by &amp;Location</string>
</property>
</action>
<action name="actionSort_by_Group">
@ -3482,6 +3288,11 @@
<string>Grid Settings...</string>
</property>
</action>
<action name="action_NewLayout">
<property name="text">
<string>New Layout...</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>

245
forms/mapheaderform.ui Normal file
View File

@ -0,0 +1,245 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MapHeaderForm</class>
<widget class="QWidget" name="MapHeaderForm">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>407</width>
<height>349</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::FieldGrowthPolicy::FieldsStayAtSizeHint</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_Song">
<property name="text">
<string>Song</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="NoScrollComboBox" name="comboBox_Song">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default background music for this map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertPolicy::NoInsert</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_Location">
<property name="text">
<string>Location</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="NoScrollComboBox" name="comboBox_Location">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The section of the region map which the map is grouped under. This also determines the name of the map that is displayed when the player enters it.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertPolicy::NoInsert</enum>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_RequiresFlash">
<property name="text">
<string>Requires Flash</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="checkBox_RequiresFlash">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the player will need to use Flash to see fully on this map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_Weather">
<property name="text">
<string>Weather</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="NoScrollComboBox" name="comboBox_Weather">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default weather on this map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertPolicy::NoInsert</enum>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_Type">
<property name="text">
<string>Type</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="NoScrollComboBox" name="comboBox_Type">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The map type is a general attribute, which is used for many different things. For example, underground type maps will have a special transition effect when the player enters/exits the map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertPolicy::NoInsert</enum>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_BattleScene">
<property name="text">
<string>Battle Scene</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="NoScrollComboBox" name="comboBox_BattleScene">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This field is used to help determine what graphics to use in the background of battles on this map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertPolicy::NoInsert</enum>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_ShowLocationName">
<property name="text">
<string>Show Location Name</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QCheckBox" name="checkBox_ShowLocationName">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, a map name popup will appear when the player enters this map. The name that appears on this popup depends on the Location field.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_AllowRunning">
<property name="text">
<string>Allow Running</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QCheckBox" name="checkBox_AllowRunning">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the player will be allowed to run on this map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_AllowBiking">
<property name="text">
<string>Allow Biking</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QCheckBox" name="checkBox_AllowBiking">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the player will be allowed to get on their bike on this map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="label_AllowEscaping">
<property name="text">
<string>Allow Dig &amp; Escape Rope</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QCheckBox" name="checkBox_AllowEscaping">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the player will be allowed to use Dig or Escape Rope on this map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="label_FloorNumber">
<property name="text">
<string>Floor Number</string>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="NoScrollSpinBox" name="spinBox_FloorNumber">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Floor number to be used for maps with elevators.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_LocationName">
<property name="text">
<string>Location Name</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="lineEdit_LocationName"/>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>NoScrollComboBox</class>
<extends>QComboBox</extends>
<header>noscrollcombobox.h</header>
</customwidget>
<customwidget>
<class>NoScrollSpinBox</class>
<extends>QSpinBox</extends>
<header>noscrollspinbox.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

174
forms/newlayoutdialog.ui Normal file
View File

@ -0,0 +1,174 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NewLayoutDialog</class>
<widget class="QDialog" name="NewLayoutDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>264</width>
<height>173</height>
</rect>
</property>
<property name="windowTitle">
<string>New Layout Options</string>
</property>
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="frameShape">
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Plain</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>240</width>
<height>109</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<property name="verticalSpacing">
<number>10</number>
</property>
<item row="1" column="1">
<widget class="QLabel" name="label_NameError">
<property name="visible">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="NewLayoutForm" name="newLayoutForm" native="true"/>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="lineEdit_LayoutID">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The constant that will be used to refer to this layout. It cannot be the same as any other existing layout.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="placeholderText">
<string>LAYOUT_MY_NEW_LAYOUT</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_Name">
<property name="text">
<string>Layout Name</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_LayoutID">
<property name="text">
<string>Layout ID</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_Name">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The name of the new layout. The name cannot be the same as any other existing layout.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="placeholderText">
<string>MyNewLayout</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="label_LayoutIDError">
<property name="visible">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QLabel" name="label_GenericError">
<property name="visible">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok|QDialogButtonBox::StandardButton::Reset</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>NewLayoutForm</class>
<extends>QWidget</extends>
<header>newlayoutform.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

244
forms/newlayoutform.ui Normal file
View File

@ -0,0 +1,244 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NewLayoutForm</class>
<widget class="QWidget" name="NewLayoutForm">
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<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="QGroupBox" name="groupBox_MapDimensions">
<property name="title">
<string>Map Dimensions</string>
</property>
<layout class="QFormLayout" name="formLayout_7">
<item row="0" column="0">
<widget class="QLabel" name="label_MapWidth">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Width</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="NoScrollSpinBox" name="spinBox_MapWidth">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Width (in metatiles) of the new map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_MapHeight">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Height</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="NoScrollSpinBox" name="spinBox_MapHeight">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Height (in metatiles) of the new map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QLabel" name="label_MapDimensionsError">
<property name="visible">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_BorderDimensions">
<property name="title">
<string>Border Dimensions</string>
</property>
<layout class="QFormLayout" name="formLayout_6">
<item row="0" column="0">
<widget class="QLabel" name="label_BorderWidth">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Width</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="NoScrollSpinBox" name="spinBox_BorderWidth">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Width (in metatiles) of the new map's border.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="NoScrollSpinBox" name="spinBox_BorderHeight">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Height (in metatiles) of the new map's border.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_BorderHeight">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Height</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_Tilesets">
<property name="title">
<string>Tilesets</string>
</property>
<layout class="QFormLayout" name="formLayout_5">
<item row="0" column="0">
<widget class="QLabel" name="label_PrimaryTileset">
<property name="text">
<string>Primary</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="NoScrollComboBox" name="comboBox_PrimaryTileset">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The primary tileset for the new map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertPolicy::NoInsert</enum>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="label_PrimaryTilesetError">
<property name="visible">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_SecondaryTileset">
<property name="text">
<string>Secondary</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="NoScrollComboBox" name="comboBox_SecondaryTileset">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The secondary tileset for the new map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertPolicy::NoInsert</enum>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QLabel" name="label_SecondaryTilesetError">
<property name="visible">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>NoScrollComboBox</class>
<extends>QComboBox</extends>
<header>noscrollcombobox.h</header>
</customwidget>
<customwidget>
<class>NoScrollSpinBox</class>
<extends>QSpinBox</extends>
<header>noscrollspinbox.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NewLocationDialog</class>
<widget class="QDialog" name="NewLocationDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>252</width>
<height>124</height>
</rect>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QFrame" name="form">
<layout class="QFormLayout" name="formLayout">
<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 row="0" column="0">
<widget class="QLabel" name="label_IdName">
<property name="text">
<string>Location ID</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_IdName">
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_IdNameError">
<property name="visible">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="lineEdit_DisplayName"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_DisplayName">
<property name="text">
<string>Location Name</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
</property>
<property name="centerButtons">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

253
forms/newmapdialog.ui Normal file
View File

@ -0,0 +1,253 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NewMapDialog</class>
<widget class="QDialog" name="NewMapDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>559</width>
<height>614</height>
</rect>
</property>
<property name="windowTitle">
<string>New Map Options</string>
</property>
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="frameShape">
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Plain</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>535</width>
<height>550</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<property name="verticalSpacing">
<number>10</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_Name">
<property name="text">
<string>Map Name</string>
</property>
</widget>
</item>
<item row="11" column="0" colspan="2">
<widget class="QWidget" name="widget_HeaderData" native="true">
<layout class="QVBoxLayout" name="layout_HeaderData">
<property name="sizeConstraint">
<enum>QLayout::SizeConstraint::SetMinAndMaxSize</enum>
</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>
</layout>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="label_GroupError">
<property name="visible">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_NameError">
<property name="visible">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_LayoutID">
<property name="text">
<string>Layout ID</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_Group">
<property name="text">
<string>Map Group</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="NoScrollComboBox" name="comboBox_LayoutID">
<property name="editable">
<bool>true</bool>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertPolicy::NoInsert</enum>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QCheckBox" name="checkBox_CanFlyTo">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, a Heal Location will be added to this map automatically.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLabel" name="label_LayoutIDError">
<property name="visible">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<widget class="NewLayoutForm" name="newLayoutForm" native="true"/>
</item>
<item row="2" column="1">
<widget class="NoScrollComboBox" name="comboBox_Group">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The name of the group this map will be added to.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertPolicy::NoInsert</enum>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_CanFlyTo">
<property name="text">
<string>Can Fly To</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_Name">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The name of the new map. The name cannot be the same as any other existing map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="placeholderText">
<string>MyNewMap</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="12" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QLabel" name="label_GenericError">
<property name="visible">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok|QDialogButtonBox::StandardButton::Reset</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>NewLayoutForm</class>
<extends>QWidget</extends>
<header>newlayoutform.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>NoScrollComboBox</class>
<extends>QComboBox</extends>
<header>noscrollcombobox.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NewMapGroupDialog</class>
<widget class="QDialog" name="NewMapGroupDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>252</width>
<height>87</height>
</rect>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QFrame" name="form">
<layout class="QFormLayout" name="formLayout">
<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 row="0" column="0">
<widget class="QLabel" name="label_Name">
<property name="text">
<string>Map Group Name</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_Name">
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_NameError">
<property name="visible">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
</property>
<property name="centerButtons">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -1,504 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NewMapPopup</class>
<widget class="QMainWindow" name="NewMapPopup">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>410</width>
<height>687</height>
</rect>
</property>
<property name="windowTitle">
<string>New Map Options</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QFrame" name="frame_NewMap_Options">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QFormLayout" name="formLayout_2">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<property name="verticalSpacing">
<number>12</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_newMap_Name">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_NewMap_Name">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The name of the new map. If the name is invalid (red), it will be replaced with the default name of a new map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_NewMap_Group">
<property name="text">
<string>Group</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="NoScrollComboBox" name="comboBox_NewMap_Group">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;New map group.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_NewMap_Width">
<property name="text">
<string>Map Width</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="spinBox_NewMap_Width">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Width (in blocks) of the new map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="maximum">
<number>200</number>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_NewMap_Height">
<property name="text">
<string>Map Height</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QSpinBox" name="spinBox_NewMap_Height">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Height (in blocks) of the new map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="maximum">
<number>200</number>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_NewMap_BorderWidth">
<property name="text">
<string>Border Width</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QSpinBox" name="spinBox_NewMap_BorderWidth">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Width (in blocks) of the new map's border.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="maximum">
<number>255</number>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_NewMap_BorderHeight">
<property name="text">
<string>Border Height</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QSpinBox" name="spinBox_NewMap_BorderHeight">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Height (in blocks) of the new map's border.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="maximum">
<number>255</number>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_NewMap_Primary_Tileset">
<property name="text">
<string>Primary Tileset</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="NoScrollComboBox" name="comboBox_NewMap_Primary_Tileset">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The primary tileset for the new map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_NewMap_Secondary_Tileset">
<property name="text">
<string>Secondary Tileset</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="NoScrollComboBox" name="comboBox_NewMap_Secondary_Tileset">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The secondary tileset for the new map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="label_NewMap_Type">
<property name="text">
<string>Type</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="NoScrollComboBox" name="comboBox_NewMap_Type">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The map type is a general attribute, which is used for many different things. For example. it determines whether biking or running is allowed.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="label_NewMap_Location">
<property name="text">
<string>Location</string>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="NoScrollComboBox" name="comboBox_NewMap_Location">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The section of the region map which the map is grouped under. This also determines the name of the map that is displayed when the player enters it.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="label_Song">
<property name="text">
<string>Song</string>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="NoScrollComboBox" name="comboBox_NewMap_Song">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default background music for this map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="13" column="0">
<widget class="QLabel" name="label_NewMap_Flyable">
<property name="text">
<string>Can Fly To</string>
</property>
</widget>
</item>
<item row="13" column="1">
<widget class="QCheckBox" name="checkBox_NewMap_Flyable">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Whether to add a heal location to the new map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="14" column="0">
<widget class="QLabel" name="label_NewMap_Show_Location">
<property name="text">
<string>Show Location Name</string>
</property>
</widget>
</item>
<item row="14" column="1">
<widget class="QCheckBox" name="checkBox_NewMap_Show_Location">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Whether or not to display the location name when the player enters the map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="15" column="0">
<widget class="QLabel" name="label_NewMap_Allow_Running">
<property name="text">
<string>Allow Running</string>
</property>
</widget>
</item>
<item row="15" column="1">
<widget class="QCheckBox" name="checkBox_NewMap_Allow_Running">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Allows the player to use Running Shoes&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="16" column="0">
<widget class="QLabel" name="label_NewMap_Allow_Biking">
<property name="text">
<string>Allow Biking</string>
</property>
</widget>
</item>
<item row="16" column="1">
<widget class="QCheckBox" name="checkBox_NewMap_Allow_Biking">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Allows the player to use a Bike&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="17" column="0">
<widget class="QLabel" name="label_NewMap_Allow_Escape_Rope">
<property name="text">
<string>Allow Dig &amp; Escape Rope</string>
</property>
</widget>
</item>
<item row="17" column="1">
<widget class="QCheckBox" name="checkBox_NewMap_Allow_Escape_Rope">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Allows the player to use Dig or Escape Rope&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="18" column="0">
<widget class="QLabel" name="label_NewMap_Floor_Number">
<property name="text">
<string>Floor Number</string>
</property>
</widget>
</item>
<item row="18" column="1">
<widget class="QSpinBox" name="spinBox_NewMap_Floor_Number">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Floor number to be used for maps with elevators.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="maximum">
<number>127</number>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="NoScrollComboBox" name="comboBox_Layout">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Layout</string>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Use Existing Layout</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="checkBox_UseExistingLayout">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="pushButton_NewMap_Accept">
<property name="text">
<string>Accept</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QFrame" name="frame_NewMap_Warning">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="visible">
<bool>false</bool>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QToolButton" name="toolButton_NewMap_DimensionWarning">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>!</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_NewMap_WarningMessage">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>410</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<customwidgets>
<customwidget>
<class>NoScrollComboBox</class>
<extends>QComboBox</extends>
<header>noscrollcombobox.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -6,238 +6,147 @@
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>216</height>
<width>450</width>
<height>209</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Add new Tileset</string>
</property>
<widget class="QWidget" name="gridWidget" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>216</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<property name="verticalSpacing">
<number>6</number>
</property>
<item row="4" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QFrame" name="form">
<layout class="QFormLayout" name="formLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
<property name="topMargin">
<number>0</number>
</property>
</spacer>
</item>
<item row="0" column="0">
<widget class="QFrame" name="frame">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<property name="rightMargin">
<number>0</number>
</property>
<property name="minimumSize">
<size>
<width>380</width>
<height>161</height>
</size>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<widget class="QWidget" name="formLayoutWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>380</width>
<height>155</height>
</rect>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="horizontalSpacing">
<number>6</number>
<item row="0" column="0">
<widget class="QLabel" name="label_Name">
<property name="text">
<string>Name</string>
</property>
<property name="leftMargin">
<number>10</number>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_Name">
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
<property name="topMargin">
<number>10</number>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_NameError">
<property name="visible">
<bool>false</bool>
</property>
<property name="rightMargin">
<number>10</number>
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<item row="0" column="0">
<widget class="QLabel" name="nameLabel">
<property name="text">
<string>Name</string>
</property>
</widget>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_Type">
<property name="text">
<string>Type</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="NoScrollComboBox" name="comboBox_Type">
<property name="editable">
<bool>false</bool>
</property>
<item>
<property name="text">
<string>Primary</string>
</property>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="nameLineEdit">
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
<item>
<property name="text">
<string>Secondary</string>
</property>
</item>
<item row="1" column="0">
<widget class="QLabel" name="typeLabel">
<property name="text">
<string>Type</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="typeComboBox">
<item>
<property name="text">
<string>Primary</string>
</property>
</item>
<item>
<property name="text">
<string>Secondary</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="pathLabel">
<property name="text">
<string>Path</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="pathLineEdit">
<property name="enabled">
<bool>false</bool>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="symbolNameLabel">
<property name="text">
<string>Symbol Name</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="symbolNameLineEdit">
<property name="enabled">
<bool>false</bool>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="fillLabel">
<property name="text">
<string>Checkerboard Fill</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="fillCheckBox">
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="5" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
<property name="centerButtons">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_CheckerboardFill">
<property name="text">
<string>Checkerboard Fill</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="checkBox_CheckerboardFill"/>
</item>
<item row="4" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="label_GenericError">
<property name="visible">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
</property>
<property name="centerButtons">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>NoScrollComboBox</class>
<extends>QComboBox</extends>
<header>noscrollcombobox.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>NewTilesetDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>NewTilesetDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
<connections/>
</ui>

View File

@ -18,10 +18,10 @@
<item row="0" column="1">
<widget class="QFrame" name="frame_regionSelector">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
<enum>QFrame::Shadow::Plain</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
@ -46,14 +46,14 @@
<item>
<widget class="QComboBox" name="comboBox_regionSelector">
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
<enum>QComboBox::SizeAdjustPolicy::AdjustToContents</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -83,7 +83,7 @@
</size>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
<enum>Qt::FocusPolicy::StrongFocus</enum>
</property>
<property name="minimum">
<number>10</number>
@ -95,10 +95,10 @@
<number>30</number>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="tickPosition">
<enum>QSlider::NoTicks</enum>
<enum>QSlider::TickPosition::NoTicks</enum>
</property>
<property name="tickInterval">
<number>1</number>
@ -108,7 +108,7 @@
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -123,7 +123,7 @@
<item row="1" column="1">
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="childrenCollapsible">
<bool>false</bool>
@ -160,7 +160,7 @@
<x>0</x>
<y>0</y>
<width>466</width>
<height>351</height>
<height>336</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_18">
@ -182,7 +182,7 @@
<item row="1" column="2">
<spacer name="horizontalSpacer_RMBI_East">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -207,17 +207,17 @@
<bool>false</bool>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustIgnored</enum>
<enum>QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored</enum>
</property>
<property name="dragMode">
<enum>QGraphicsView::NoDrag</enum>
<enum>QGraphicsView::DragMode::NoDrag</enum>
</property>
</widget>
</item>
<item row="0" column="1">
<spacer name="verticalSpacer_RMBI_North">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -230,7 +230,7 @@
<item row="2" column="1">
<spacer name="verticalSpacer_RMBI_South">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -243,7 +243,7 @@
<item row="1" column="0">
<spacer name="horizontalSpacer_RMBI_West">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -281,7 +281,7 @@
<x>0</x>
<y>0</y>
<width>466</width>
<height>351</height>
<height>336</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_19">
@ -315,17 +315,17 @@
<bool>false</bool>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustIgnored</enum>
<enum>QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored</enum>
</property>
<property name="dragMode">
<enum>QGraphicsView::NoDrag</enum>
<enum>QGraphicsView::DragMode::NoDrag</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<spacer name="horizontalSpacer_RML_West">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -338,7 +338,7 @@
<item row="2" column="1">
<spacer name="verticalSpacer_RML_South">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -351,7 +351,7 @@
<item row="1" column="2">
<spacer name="horizontalSpacer_RML_East">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -364,7 +364,7 @@
<item row="0" column="1">
<spacer name="verticalSpacer_RML_North">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -402,7 +402,7 @@
<x>0</x>
<y>0</y>
<width>466</width>
<height>351</height>
<height>336</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_24">
@ -436,17 +436,17 @@
<bool>false</bool>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustIgnored</enum>
<enum>QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored</enum>
</property>
<property name="dragMode">
<enum>QGraphicsView::NoDrag</enum>
<enum>QGraphicsView::DragMode::NoDrag</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<spacer name="horizontalSpacer_RME_West">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -459,7 +459,7 @@
<item row="2" column="1">
<spacer name="verticalSpacer_RME_South">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -472,7 +472,7 @@
<item row="1" column="2">
<spacer name="horizontalSpacer_RME_East">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -485,7 +485,7 @@
<item row="0" column="1">
<spacer name="verticalSpacer_RME_North">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -529,19 +529,19 @@
</size>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOn</enum>
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOn</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
<enum>Qt::ScrollBarPolicy::ScrollBarAsNeeded</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustIgnored</enum>
<enum>QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignHCenter|Qt::AlignTop</set>
<set>Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop</set>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents_RM_Metatiles">
<property name="enabled">
@ -552,7 +552,7 @@
<x>8</x>
<y>0</y>
<width>278</width>
<height>342</height>
<height>327</height>
</rect>
</property>
<property name="sizePolicy">
@ -563,7 +563,7 @@
</property>
<layout class="QGridLayout" name="gridLayout_21">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
<enum>QLayout::SizeConstraint::SetDefaultConstraint</enum>
</property>
<property name="leftMargin">
<number>0</number>
@ -583,7 +583,7 @@
<item row="1" column="1">
<spacer name="verticalSpacer_RMM_South">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -596,7 +596,7 @@
<item row="0" column="2">
<spacer name="horizontalSpacer_RMM_East">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -618,20 +618,20 @@
</sizepolicy>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustIgnored</enum>
<enum>QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored</enum>
</property>
</widget>
</item>
<item row="0" column="0">
<spacer name="horizontalSpacer_RMM_West">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -648,10 +648,10 @@
<item row="0" column="0">
<widget class="QFrame" name="frame_tileOptions">
<property name="frameShape">
<enum>QFrame::Panel</enum>
<enum>QFrame::Shape::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
<enum>QFrame::Shadow::Sunken</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
@ -681,7 +681,7 @@
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBox_tilePalette">
<widget class="NoScrollSpinBox" name="spinBox_tilePalette">
<property name="maximum">
<number>15</number>
</property>
@ -719,10 +719,10 @@
</size>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
<enum>QFrame::Shadow::Raised</enum>
</property>
<property name="lineWidth">
<number>1</number>
@ -747,7 +747,7 @@
<item>
<spacer name="verticalSpacer_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -760,10 +760,10 @@
<item>
<layout class="QGridLayout" name="gridLayout_22">
<property name="sizeConstraint">
<enum>QLayout::SetNoConstraint</enum>
<enum>QLayout::SizeConstraint::SetNoConstraint</enum>
</property>
<item row="0" column="1">
<widget class="NoScrollComboBox" name="comboBox_RM_ConnectedMap" native="true">
<widget class="NoScrollComboBox" name="comboBox_RM_ConnectedMap">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
@ -771,10 +771,10 @@
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The section of the region map which the map is grouped under. This also determines the name of the map that is display when the player enters it.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The section of the region map which the map is grouped under. This also determines the name of the map that is displayed when the player enters it.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable" stdset="0">
<bool>true</bool>
<property name="editable">
<bool>false</bool>
</property>
</widget>
</item>
@ -803,7 +803,7 @@
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -816,10 +816,10 @@
<item>
<layout class="QGridLayout" name="gridLayout_8">
<item row="1" column="1">
<widget class="QSpinBox" name="spinBox_RM_LayoutHeight"/>
<widget class="NoScrollSpinBox" name="spinBox_RM_LayoutHeight"/>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="spinBox_RM_LayoutWidth"/>
<widget class="NoScrollSpinBox" name="spinBox_RM_LayoutWidth"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_7">
@ -862,10 +862,10 @@
</size>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
<enum>QFrame::Shadow::Raised</enum>
</property>
<property name="lineWidth">
<number>1</number>
@ -874,15 +874,15 @@
<item>
<layout class="QGridLayout" name="gridLayout_23">
<property name="sizeConstraint">
<enum>QLayout::SetNoConstraint</enum>
<enum>QLayout::SizeConstraint::SetNoConstraint</enum>
</property>
<item row="3" column="1">
<item row="2" column="1">
<widget class="QFrame" name="frame_2">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
@ -893,10 +893,10 @@
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="spinBox_RM_Entry_width"/>
<widget class="NoScrollSpinBox" name="spinBox_RM_Entry_width"/>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="spinBox_RM_Entry_height"/>
<widget class="NoScrollSpinBox" name="spinBox_RM_Entry_height"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_5">
@ -909,7 +909,7 @@
</widget>
</item>
<item row="0" column="1">
<widget class="NoScrollComboBox" name="comboBox_RM_Entry_MapSection" native="true">
<widget class="NoScrollComboBox" name="comboBox_RM_Entry_MapSection">
<property name="enabled">
<bool>true</bool>
</property>
@ -922,25 +922,25 @@
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The section of the region map which the map is grouped under. This also determines the name of the map that is display when the player enters it.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable" stdset="0">
<bool>true</bool>
<property name="editable">
<bool>false</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_RM_CityMap_2">
<item row="2" column="0">
<widget class="QLabel" name="label_Dimensions">
<property name="text">
<string>Dimensions</string>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="1" column="1">
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
@ -951,10 +951,10 @@
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="spinBox_RM_Entry_x"/>
<widget class="NoScrollSpinBox" name="spinBox_RM_Entry_x"/>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="spinBox_RM_Entry_y"/>
<widget class="NoScrollSpinBox" name="spinBox_RM_Entry_y"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
@ -966,7 +966,7 @@
</layout>
</widget>
</item>
<item row="2" column="0">
<item row="1" column="0">
<widget class="QLabel" name="label_RM_Location">
<property name="text">
<string>Location</string>
@ -980,16 +980,6 @@
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Map Name</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEdit_RM_MapName"/>
</item>
</layout>
</item>
<item>
@ -1002,7 +992,7 @@
<item>
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -1037,7 +1027,7 @@
</size>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
<enum>Qt::FocusPolicy::StrongFocus</enum>
</property>
<property name="minimum">
<number>10</number>
@ -1049,10 +1039,10 @@
<number>30</number>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="tickPosition">
<enum>QSlider::NoTicks</enum>
<enum>QSlider::TickPosition::NoTicks</enum>
</property>
<property name="tickInterval">
<number>1</number>
@ -1062,7 +1052,7 @@
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -1082,7 +1072,7 @@
<x>0</x>
<y>0</y>
<width>957</width>
<height>22</height>
<height>37</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
@ -1180,9 +1170,14 @@
<customwidgets>
<customwidget>
<class>NoScrollComboBox</class>
<extends>QWidget</extends>
<extends>QComboBox</extends>
<header>noscrollcombobox.h</header>
</customwidget>
<customwidget>
<class>NoScrollSpinBox</class>
<extends>QSpinBox</extends>
<header>noscrollspinbox.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>

View File

@ -71,6 +71,7 @@ public:
this->showTilesetEditorDivider = false;
this->monitorFiles = true;
this->tilesetCheckerboardFill = true;
this->newMapHeaderSectionExpanded = false;
this->theme = "default";
this->wildMonChartTheme = "";
this->textEditorOpenFolder = "";
@ -123,6 +124,7 @@ public:
bool showTilesetEditorDivider;
bool monitorFiles;
bool tilesetCheckerboardFill;
bool newMapHeaderSectionExpanded;
QString theme;
QString wildMonChartTheme;
QString textEditorOpenFolder;
@ -135,6 +137,8 @@ public:
QVersionNumber lastUpdateCheckVersion;
QMap<QUrl, QDateTime> rateLimitTimes;
QByteArray wildMonChartGeometry;
QByteArray newMapDialogGeometry;
QByteArray newLayoutDialogGeometry;
protected:
virtual QString getConfigFilepath() override;
@ -187,6 +191,7 @@ enum ProjectIdentifier {
symbol_spawn_npcs,
symbol_attribute_table,
symbol_tilesets_prefix,
symbol_dynamic_map_name,
define_obj_event_count,
define_min_level,
define_max_level,
@ -256,7 +261,6 @@ enum ProjectFilePath {
data_pokemon_gfx,
data_heal_locations,
constants_global,
constants_map_groups,
constants_items,
constants_flags,
constants_vars,
@ -328,7 +332,7 @@ public:
QString getCustomFilePath(ProjectFilePath pathId);
QString getCustomFilePath(const QString &pathId);
QString getFilePath(ProjectFilePath pathId);
void setIdentifier(ProjectIdentifier id, const QString &text);
void setIdentifier(ProjectIdentifier id, QString text);
void setIdentifier(const QString &id, const QString &text);
QString getCustomIdentifier(ProjectIdentifier id);
QString getCustomIdentifier(const QString &id);

View File

@ -0,0 +1,18 @@
#ifndef ADVANCEMAPPARSER_H
#define ADVANCEMAPPARSER_H
#include <QList>
#include <QString>
#include <QRgb>
class Project;
class Layout;
class Metatile;
namespace AdvanceMapParser {
Layout *parseLayout(const QString &filepath, bool *error, const Project *project);
QList<Metatile*> parseMetatiles(const QString &filepath, bool *error, bool primaryTileset);
QList<QRgb> parsePalette(const QString &filepath, bool *error);
};
#endif // ADVANCEMAPPARSER_H

View File

@ -7,6 +7,7 @@
#include "maplayout.h"
#include "tileset.h"
#include "events.h"
#include "mapheader.h"
#include <QUndoStack>
#include <QPixmap>
@ -34,61 +35,56 @@ class Map : public QObject
Q_OBJECT
public:
explicit Map(QObject *parent = nullptr);
explicit Map(const Map &other, QObject *parent = nullptr);
~Map();
public:
QString name;
QString constantName;
void setName(const QString &mapName) { m_name = mapName; }
QString name() const { return m_name; }
QString song;
QString layoutId;
QString location;
bool requiresFlash;
QString weather;
QString type;
bool show_location;
bool allowRunning;
bool allowBiking;
bool allowEscaping;
int floorNumber = 0;
QString battle_scene;
void setConstantName(const QString &constantName) { m_constantName = constantName; }
QString constantName() const { return m_constantName; }
QString sharedEventsMap = "";
QString sharedScriptsMap = "";
QStringList scriptsFileLabels;
QMap<QString, QJsonValue> customHeaders;
Layout *layout = nullptr;
void setLayout(Layout *layout);
bool isPersistedToFile = true;
bool hasUnsavedDataChanges = false;
bool needsLayoutDir = true;
bool needsHealLocation = false;
bool scriptsLoaded = false;
QMap<Event::Group, QList<Event *>> events;
QList<Event *> ownedEvents; // for memory management
QList<int> metatileLayerOrder;
QList<float> metatileLayerOpacity;
void setName(QString mapName);
static QString mapConstantFromName(QString mapName, bool includePrefix = true);
int getWidth();
int getHeight();
int getBorderWidth();
int getBorderHeight();
void setLayout(Layout *layout);
Layout* layout() const { return m_layout; }
QList<Event *> getAllEvents() const;
void setLayoutId(const QString &layoutId) { m_layoutId = layoutId; }
QString layoutId() const { return m_layoutId; }
int getWidth() const;
int getHeight() const;
int getBorderWidth() const;
int getBorderHeight() const;
void setHeader(const MapHeader &header) { *m_header = header; }
MapHeader* header() const { return m_header; }
void setSharedEventsMap(const QString &sharedEventsMap) { m_sharedEventsMap = sharedEventsMap; }
void setSharedScriptsMap(const QString &sharedScriptsMap) { m_sharedScriptsMap = sharedScriptsMap; }
QString sharedEventsMap() const { return m_sharedEventsMap; }
QString sharedScriptsMap() const { return m_sharedScriptsMap; }
void setNeedsHealLocation(bool needsHealLocation) { m_needsHealLocation = needsHealLocation; }
void setIsPersistedToFile(bool persistedToFile) { m_isPersistedToFile = persistedToFile; }
void setHasUnsavedDataChanges(bool unsavedDataChanges) { m_hasUnsavedDataChanges = unsavedDataChanges; }
bool needsHealLocation() const { return m_needsHealLocation; }
bool isPersistedToFile() const { return m_isPersistedToFile; }
bool hasUnsavedDataChanges() const { return m_hasUnsavedDataChanges; }
void resetEvents();
QList<Event *> getEvents(Event::Group group = Event::Group::None) const;
Event* getEvent(Event::Group group, int index) const;
int getNumEvents(Event::Group group = Event::Group::None) const;
QStringList getScriptLabels(Event::Group group = Event::Group::None);
QString getScriptsFilePath() const;
void openScript(QString label);
void removeEvent(Event *);
void addEvent(Event *);
int getIndexOfEvent(Event *) const;
void deleteConnections();
QList<MapConnection*> getConnections() const;
@ -98,18 +94,47 @@ public:
QRect getConnectionRect(const QString &direction, Layout *fromLayout = nullptr);
QPixmap renderConnection(const QString &direction, Layout *fromLayout = nullptr);
QUndoStack editHistory;
QUndoStack* editHistory() const { return m_editHistory; }
void commit(QUndoCommand*);
void modify();
void clean();
void setClean();
bool hasUnsavedChanges() const;
void pruneEditHistory();
void setCustomAttributes(const QMap<QString, QJsonValue> &attributes) { m_customAttributes = attributes; }
QMap<QString, QJsonValue> customAttributes() const { return m_customAttributes; }
private:
QString m_name;
QString m_constantName;
QString m_layoutId;
QString m_sharedEventsMap = "";
QString m_sharedScriptsMap = "";
QStringList m_scriptsFileLabels;
QMap<QString, QJsonValue> m_customAttributes;
MapHeader *m_header = nullptr;
Layout *m_layout = nullptr;
bool m_isPersistedToFile = true;
bool m_hasUnsavedDataChanges = false;
bool m_needsHealLocation = false;
bool m_scriptsLoaded = false;
QMap<Event::Group, QList<Event *>> m_events;
QSet<Event *> m_ownedEvents; // for memory management
QList<int> m_metatileLayerOrder;
QList<float> m_metatileLayerOpacity;
void trackConnection(MapConnection*);
// MapConnections in 'ownedConnections' but not 'connections' persist in the edit history.
QList<MapConnection*> connections;
QSet<MapConnection*> ownedConnections;
QList<MapConnection*> m_connections;
QSet<MapConnection*> m_ownedConnections;
QUndoStack *m_editHistory = nullptr;
signals:
void modified();

83
include/core/mapheader.h Normal file
View File

@ -0,0 +1,83 @@
#ifndef MAPHEADER_H
#define MAPHEADER_H
#include <QObject>
class MapHeader : public QObject
{
Q_OBJECT
public:
MapHeader(QObject *parent = nullptr) : QObject(parent) {};
~MapHeader() {};
MapHeader(const MapHeader& other);
MapHeader& operator=(const MapHeader& other);
bool operator==(const MapHeader& other) const {
return m_song == other.m_song
&& m_location == other.m_location
&& m_requiresFlash == other.m_requiresFlash
&& m_weather == other.m_weather
&& m_type == other.m_type
&& m_showsLocationName == other.m_showsLocationName
&& m_allowsRunning == other.m_allowsRunning
&& m_allowsBiking == other.m_allowsBiking
&& m_allowsEscaping == other.m_allowsEscaping
&& m_floorNumber == other.m_floorNumber
&& m_battleScene == other.m_battleScene;
}
bool operator!=(const MapHeader& other) const {
return !(operator==(other));
}
void setSong(const QString &song);
void setLocation(const QString &location);
void setRequiresFlash(bool requiresFlash);
void setWeather(const QString &weather);
void setType(const QString &type);
void setShowsLocationName(bool showsLocationName);
void setAllowsRunning(bool allowsRunning);
void setAllowsBiking(bool allowsBiking);
void setAllowsEscaping(bool allowsEscaping);
void setFloorNumber(int floorNumber);
void setBattleScene(const QString &battleScene);
QString song() const { return m_song; }
QString location() const { return m_location; }
bool requiresFlash() const { return m_requiresFlash; }
QString weather() const { return m_weather; }
QString type() const { return m_type; }
bool showsLocationName() const { return m_showsLocationName; }
bool allowsRunning() const { return m_allowsRunning; }
bool allowsBiking() const { return m_allowsBiking; }
bool allowsEscaping() const { return m_allowsEscaping; }
int floorNumber() const { return m_floorNumber; }
QString battleScene() const { return m_battleScene; }
signals:
void songChanged(QString);
void locationChanged(QString);
void requiresFlashChanged(bool);
void weatherChanged(QString);
void typeChanged(QString);
void showsLocationNameChanged(bool);
void allowsRunningChanged(bool);
void allowsBikingChanged(bool);
void allowsEscapingChanged(bool);
void floorNumberChanged(int);
void battleSceneChanged(QString);
void modified();
private:
QString m_song;
QString m_location;
bool m_requiresFlash = false;
QString m_weather;
QString m_type;
bool m_showsLocationName = false;
bool m_allowsRunning = false;
bool m_allowsBiking = false;
bool m_allowsEscaping = false;
int m_floorNumber = 0;
QString m_battleScene;
};
#endif // MAPHEADER_H

View File

@ -18,8 +18,11 @@ class Layout : public QObject {
Q_OBJECT
public:
Layout() {}
Layout(const Layout &other);
static QString layoutConstantFromName(QString mapName);
static QString defaultSuffix();
bool loaded = false;
@ -70,24 +73,28 @@ public:
QUndoStack editHistory;
// to simplify new layout settings transfer between functions
struct SimpleSettings {
struct Settings {
QString id;
QString name;
// The name of a new layout's folder in `data/layouts/` is not always the same as the layout's name
// (e.g. the majority of the default layouts use the name of their associated map).
QString folderName;
int width;
int height;
QString tileset_primary_label;
QString tileset_secondary_label;
QString from_id = QString();
int borderWidth;
int borderHeight;
QString primaryTilesetLabel;
QString secondaryTilesetLabel;
};
Settings settings() const;
public:
Layout *copy();
void copyFrom(Layout *other);
Layout *copy() const;
void copyFrom(const Layout *other);
int getWidth();
int getHeight();
int getBorderWidth();
int getBorderHeight();
int getWidth() const { return width; }
int getHeight() const { return height; }
int getBorderWidth() const { return border_width; }
int getBorderHeight() const { return border_height; }
bool isWithinBounds(int x, int y);
bool isWithinBorderBounds(int x, int y);

View File

@ -1,16 +0,0 @@
#ifndef MAPPARSER_H
#define MAPPARSER_H
#include "maplayout.h"
#include "project.h"
#include <QList>
#include <QString>
class MapParser
{
public:
MapParser();
Layout *parse(QString filepath, bool *error, Project *project);
};
#endif // MAPPARSER_H

View File

@ -1,12 +0,0 @@
#pragma once
#ifndef METATILEPARSER_H
#define METATILEPARSER_H
#include "metatile.h"
#include <QList>
namespace MetatileParser {
QList<Metatile*> parse(QString filepath, bool *error, bool primaryTileset);
}
#endif // METATILEPARSER_H

View File

@ -5,6 +5,7 @@
#include "heallocation.h"
#include "log.h"
#include "orderedjson.h"
#include "orderedmap.h"
#include <QString>
#include <QList>
@ -57,7 +58,7 @@ public:
QMap<QString, int> readCDefinesByRegex(const QString &filename, const QStringList &regexList);
QMap<QString, int> readCDefinesByName(const QString &filename, const QStringList &names);
QStringList readCDefineNames(const QString &filename, const QStringList &regexList);
QMap<QString, QHash<QString, QString>> readCStructs(const QString &, const QString & = "", const QHash<int, QString> = { });
tsl::ordered_map<QString, QHash<QString, QString>> readCStructs(const QString &, const QString & = "", const QHash<int, QString>& = {});
QList<QStringList> getLabelMacros(const QList<QStringList>&, const QString&);
QStringList getLabelValues(const QList<QStringList>&, const QString&);
bool tryParseJsonFile(QJsonDocument *out, const QString &filepath);

View File

@ -33,7 +33,6 @@ struct LayoutSquare
struct MapSectionEntry
{
QString name = "";
int x = 0;
int y = 0;
int width = 1;

View File

@ -38,8 +38,6 @@ public:
QList<QList<QRgb>> palettes;
QList<QList<QRgb>> palettePreviews;
bool hasUnsavedTilesImage;
static Tileset* getMetatileTileset(int, Tileset*, Tileset*);
static Tileset* getTileTileset(int, Tileset*, Tileset*);
static Metatile* getMetatile(int, Tileset*, Tileset*);
@ -56,10 +54,25 @@ public:
static QHash<int, QString> getHeaderMemberMap(bool usingAsm);
static QString getExpectedDir(QString tilesetName, bool isSecondary);
QString getExpectedDir();
void load();
void loadMetatiles();
void loadMetatileAttributes();
void loadTilesImage(QImage *importedImage = nullptr);
void loadPalettes();
void save();
void saveMetatileAttributes();
void saveMetatiles();
void saveTilesImage();
void savePalettes();
bool appendToHeaders(QString root, QString friendlyName, bool usingAsm);
bool appendToGraphics(QString root, QString friendlyName, bool usingAsm);
bool appendToMetatiles(QString root, QString friendlyName, bool usingAsm);
void setTilesImage(const QImage &image);
void setMetatiles(const QList<Metatile*> &metatiles);
void addMetatile(Metatile* metatile);
@ -72,6 +85,7 @@ public:
private:
QList<Metatile*> m_metatiles;
bool m_hasUnsavedTilesImage = false;
};
#endif // TILESET_H

66
include/core/validator.h Normal file
View File

@ -0,0 +1,66 @@
#ifndef VALIDATOR_H
#define VALIDATOR_H
/*
This file contains our subclasses of QValidator.
- PrefixValidator is for input widgets that want to enforce a particular prefix.
It differs from a QRegularExpressionValidator with a prefix in the regex because
it will automatically enforce the prefix in fixup() if it isn't present.
It's preferable to QLineEdit's input mask because it won't affect cursor behavior.
- IdentifierValidator is for validating that input text can be used for the name of an identifier in the project.
(i.e., starts with a letter or underscore, then may continue with letters, numbers, or underscores).
Unless a prefix is specified this is a normal QRegularExpressionValidator, we only have a subclass because we use it so often.
- UppercaseValidator is just a validator that uppercases input text.
*/
#include <QValidator>
class PrefixValidator : public QRegularExpressionValidator {
Q_OBJECT
public:
explicit PrefixValidator(const QString &prefix, QObject *parent = nullptr)
: QRegularExpressionValidator(parent), m_prefix(prefix) {};
explicit PrefixValidator(const QString &prefix, const QRegularExpression &re, QObject *parent = nullptr)
: QRegularExpressionValidator(re, parent), m_prefix(prefix) {};
~PrefixValidator() {};
virtual QValidator::State validate(QString &input, int &) const override;
virtual void fixup(QString &input) const override;
QString prefix() const { return m_prefix; }
void setPrefix(const QString &prefix);
bool isValid(QString &input) const;
private:
QString m_prefix;
bool missingPrefix(const QString &input) const;
};
class IdentifierValidator : public PrefixValidator {
Q_OBJECT
public:
explicit IdentifierValidator(QObject *parent = nullptr)
: PrefixValidator("", re_identifier, parent) {};
explicit IdentifierValidator(const QString &prefix, QObject *parent = nullptr)
: PrefixValidator(prefix, re_identifier, parent) {};
~IdentifierValidator() {};
private:
static const QRegularExpression re_identifier;
};
class UppercaseValidator : public QValidator {
virtual QValidator::State validate(QString &input, int &) const override {
input = input.toUpper();
return QValidator::Acceptable;
}
};
#endif // VALIDATOR_H

View File

@ -0,0 +1,69 @@
/*
Elypson/qt-collapsible-section
(c) 2016 Michael A. Voelkel - michael.alexander.voelkel@gmail.com
This file is part of Elypson/qt-collapsible section.
Elypson/qt-collapsible-section is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Elypson/qt-collapsible-section is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU General Public License
along with Elypson/qt-collapsible-section. If not, see <http://www.gnu.org/licenses/>.
PORYMAP NOTE: Modified to support having the section expanded by default, to stop the contents
squashing during the collapse animation, and to add some guard rails against crashes.
*/
#ifndef COLLAPSIBLESECTION_H
#define COLLAPSIBLESECTION_H
#include <QFrame>
#include <QGridLayout>
#include <QParallelAnimationGroup>
#include <QPropertyAnimation>
#include <QSet>
#include <QScrollArea>
#include <QToolButton>
#include <QWidget>
class CollapsibleSection : public QWidget
{
Q_OBJECT
public:
explicit CollapsibleSection(const QString& title = "", const bool expanded = false, const int animationDuration = 0, QWidget* parent = 0);
void setContentLayout(QLayout* contentLayout);
void setTitle(const QString &title);
bool isExpanded() const { return this->expanded; }
public slots:
void toggle(bool collapsed);
private:
QGridLayout* mainLayout;
QToolButton* toggleButton;
QFrame* headerLine;
QParallelAnimationGroup* toggleAnimation;
QSet<QPropertyAnimation*> sectionAnimations;
QPropertyAnimation* contentAnimation;
QScrollArea* contentArea;
int animationDuration;
int collapsedHeight;
bool expanded;
void updateToggleButton();
void updateAnimationTargets();
int getContentHeight() const;
};
#endif // COLLAPSIBLESECTION_H

View File

@ -9,6 +9,7 @@
#include "array_value.h"
#include "define_statement.h"
#include "lexer.h"
#include "orderedmap.h"
namespace fex
{
@ -19,9 +20,9 @@ namespace fex
std::vector<DefineStatement> Parse(std::vector<Token> tokens);
std::vector<Array> ParseTopLevelArrays(std::vector<Token> tokens);
std::map<std::string, ArrayValue> ParseTopLevelObjects(std::vector<Token> tokens);
tsl::ordered_map<std::string, ArrayValue> ParseTopLevelObjects(std::vector<Token> tokens);
std::map<std::string, int> ReadDefines(const QString &filename, std::vector<std::string> matching);
tsl::ordered_map<std::string, int> ReadDefines(const QString &filename, std::vector<std::string> matching);
private:
int EvaluateExpression(std::vector<Token> tokens);

View File

@ -22,8 +22,6 @@
#include "mapimageexporter.h"
#include "filterchildrenproxymodel.h"
#include "maplistmodels.h"
#include "newmappopup.h"
#include "newtilesetdialog.h"
#include "shortcutseditor.h"
#include "preferenceeditor.h"
#include "projectsettingseditor.h"
@ -32,6 +30,8 @@
#include "wildmonchart.h"
#include "updatepromoter.h"
#include "aboutporymap.h"
#include "mapheaderform.h"
#include "newlayoutdialog.h"
@ -180,36 +180,29 @@ private slots:
void duplicate();
void setClipboardData(poryjson::Json::object);
void setClipboardData(QImage);
void setClipboardData(const QString &text);
void copy();
void paste();
void onLayoutChanged(Layout *layout);
void onOpenConnectedMap(MapConnection*);
void onTilesetsSaved(QString, QString);
void openNewMapPopupWindow();
void onNewMapCreated();
void onNewMapCreated(Map *newMap, const QString &groupName);
void onNewMapGroupCreated(const QString &groupName);
void onNewMapSectionCreated(const QString &idName);
void onMapSectionDisplayNameChanged(const QString &idName, const QString &displayName);
void onNewLayoutCreated(Layout *layout);
void onNewTilesetCreated(Tileset *tileset);
void onMapLoaded(Map *map);
void importMapFromAdvanceMap1_92();
void onMapRulerStatusChanged(const QString &);
void applyUserShortcuts();
void markMapEdited();
void markSpecificMapEdited(Map*);
void on_action_NewMap_triggered();
void on_actionNew_Tileset_triggered();
void on_action_Save_triggered();
void on_action_Exit_triggered();
void on_comboBox_Song_currentTextChanged(const QString &arg1);
void on_comboBox_Location_currentTextChanged(const QString &arg1);
void on_comboBox_Weather_currentTextChanged(const QString &arg1);
void on_comboBox_Type_currentTextChanged(const QString &arg1);
void on_comboBox_BattleScene_currentTextChanged(const QString &arg1);
void on_comboBox_LayoutSelector_currentTextChanged(const QString &arg1);
void on_checkBox_ShowLocation_stateChanged(int selected);
void on_checkBox_AllowRunning_stateChanged(int selected);
void on_checkBox_AllowBiking_stateChanged(int selected);
void on_checkBox_AllowEscaping_stateChanged(int selected);
void on_spinBox_FloorNumber_valueChanged(int offset);
void on_comboBox_LayoutSelector_currentTextChanged(const QString &text);
void on_actionShortcuts_triggered();
void on_actionZoom_In_triggered();
@ -254,7 +247,6 @@ private slots:
void on_comboBox_SecondaryTileset_currentTextChanged(const QString &arg1);
void on_pushButton_ChangeDimensions_clicked();
void on_checkBox_smartPaths_stateChanged(int selected);
void on_checkBox_Visibility_stateChanged(int selected);
void on_checkBox_ToggleBorder_stateChanged(int selected);
void resetMapViewScale();
@ -313,7 +305,6 @@ private:
QPointer<RegionMapEditor> regionMapEditor = nullptr;
QPointer<ShortcutsEditor> shortcutsEditor = nullptr;
QPointer<MapImageExporter> mapImageExporter = nullptr;
QPointer<NewMapPopup> newMapPrompt = nullptr;
QPointer<PreferenceEditor> preferenceEditor = nullptr;
QPointer<ProjectSettingsEditor> projectSettingsEditor = nullptr;
QPointer<GridSettingsDialog> gridSettingsDialog = nullptr;
@ -321,8 +312,8 @@ private:
QPointer<FilterChildrenProxyModel> groupListProxyModel = nullptr;
QPointer<MapGroupModel> mapGroupModel = nullptr;
QPointer<FilterChildrenProxyModel> areaListProxyModel = nullptr;
QPointer<MapAreaModel> mapAreaModel = nullptr;
QPointer<FilterChildrenProxyModel> locationListProxyModel = nullptr;
QPointer<MapLocationModel> mapLocationModel = nullptr;
QPointer<FilterChildrenProxyModel> layoutListProxyModel = nullptr;
QPointer<LayoutTreeModel> layoutTreeModel = nullptr;
@ -337,10 +328,11 @@ private:
QAction *copyAction = nullptr;
QAction *pasteAction = nullptr;
MapHeaderForm *mapHeaderForm = nullptr;
QMap<Event::Group, DraggablePixmapItem*> lastSelectedEvent;
bool isProgrammaticEventTabChange;
bool newMapDefaultsSet = false;
bool tilesetNeedsRedraw = false;
@ -360,8 +352,15 @@ private:
bool setProjectUI();
void clearProjectUI();
void openNewMapDialog();
void openDuplicateMapDialog(const QString &mapName);
NewLayoutDialog* createNewLayoutDialog(const Layout *layoutToCopy = nullptr);
void openNewLayoutDialog();
void openDuplicateLayoutDialog(const QString &layoutId);
void openNewMapGroupDialog();
void openNewLocationDialog();
void openSubWindow(QWidget * window);
void scrollMapList(MapTree *list, QString itemName);
void scrollMapList(MapTree *list, const QString &itemName);
void scrollMapListToCurrentMap(MapTree *list);
void scrollMapListToCurrentLayout(MapTree *list);
void resetMapListFilters();
@ -369,7 +368,9 @@ private:
QString getExistingDirectory(QString);
bool openProject(QString dir, bool initial = false);
bool closeProject();
void showRecentError(const QString &baseMessage);
void showProjectOpenFailure();
void showMapsExcludedAlert(const QStringList &excludedMapNames);
bool setInitialMap();
void saveGlobalConfigs();
@ -377,9 +378,6 @@ private:
void refreshRecentProjectsMenu();
void updateMapList();
void mapListAddGroup();
void mapListAddLayout();
void mapListAddArea();
void openMapListItem(const QModelIndex &index);
void saveMapListTab(int index);
@ -419,11 +417,10 @@ private:
void scrollMetatileSelectorToSelection();
MapListToolBar* getCurrentMapListToolBar();
MapTree* getCurrentMapList();
void refreshLocationsComboBox();
void setLocationComboBoxes(const QStringList &locations);
QObjectList shortcutableObjects() const;
void addCustomHeaderValue(QString key, QJsonValue value, bool isNew = false);
int insertTilesetLabel(QStringList * list, QString label);
void checkForUpdates(bool requestedByUser);
void setDivingMapsVisible(bool visible);
@ -450,7 +447,7 @@ struct MapViewTab {
struct MapListTab {
enum {
Groups = 0, Areas, Layouts
Groups = 0, Locations, Layouts
};
};

View File

@ -18,9 +18,6 @@
#include <QVariant>
#include <QFileSystemWatcher>
// The displayed name of the special map value used by warps with multiple potential destinations
static QString DYNAMIC_MAP_NAME = "Dynamic";
class Project : public QObject
{
Q_OBJECT
@ -35,18 +32,18 @@ public:
public:
QString root;
QStringList groupNames;
QMap<QString, int> mapGroups;
QList<QStringList> groupedMapNames;
QStringList mapNames;
QStringList groupNames;
QMap<QString, QStringList> groupNameToMapNames;
QList<HealLocation> healLocations;
QMap<QString, int> healLocationNameToValue;
QMap<QString, QString> mapConstantsToMapNames;
QMap<QString, QString> mapNamesToMapConstants;
QStringList mapLayoutsTable;
QStringList mapLayoutsTableMaster;
QMap<QString, QString> mapNameToLayoutId;
QMap<QString, QString> mapNameToMapSectionName;
QString layoutsLabel;
QMap<QString, QString> layoutIdsToNames;
QStringList layoutIds;
QStringList layoutIdsMaster;
QMap<QString, Layout*> mapLayouts;
QMap<QString, Layout*> mapLayoutsMaster;
QMap<QString, EventGraphics*> eventGraphicsMap;
@ -65,6 +62,7 @@ public:
QStringList bgEventFacingDirections;
QStringList trainerTypes;
QStringList globalScriptLabels;
QStringList mapSectionIdNamesSaveOrder;
QStringList mapSectionIdNames;
QMap<QString, MapSectionEntry> regionMapEntries;
QMap<QString, QMap<QString, uint16_t>> metatileLabelsMap;
@ -77,11 +75,11 @@ public:
QMap<QString, qint64> modifiedFileTimestamps;
bool usingAsmTilesets;
QSet<QString> disabledSettingsNames;
QSet<QString> topLevelMapFields;
int pokemonMinLevel;
int pokemonMaxLevel;
int maxEncounterRate;
bool wildEncountersLoaded;
bool saveEmptyMapsec;
void set_root(QString);
@ -113,7 +111,7 @@ public:
QStringList secondaryTilesetLabels;
QStringList tilesetLabelsOrdered;
Blockdata readBlockdata(QString);
Blockdata readBlockdata(QString, bool *ok = nullptr);
bool loadBlockdata(Layout *);
bool loadLayoutBorder(Layout *);
@ -122,12 +120,29 @@ public:
void deleteFile(QString path);
bool readMapGroups();
Map* addNewMapToGroup(QString, int, Map*, bool, bool);
QString getNewMapName();
QString getProjectTitle();
void addNewMapGroup(const QString &groupName);
QString mapNameToMapGroup(const QString &mapName);
QString readMapLayoutId(QString map_name);
QString readMapLocation(QString map_name);
struct NewMapSettings {
QString name;
QString group;
bool canFlyTo;
Layout::Settings layout;
MapHeader header;
};
NewMapSettings newMapSettings;
Layout::Settings newLayoutSettings;
void initNewMapSettings();
void initNewLayoutSettings();
Map *createNewMap(const Project::NewMapSettings &mapSettings, const Map* toDuplicate = nullptr);
Layout *createNewLayout(const Layout::Settings &layoutSettings, const Layout* toDuplicate = nullptr);
Tileset *createNewTileset(QString name, bool secondary, bool checkerboardFill);
bool isIdentifierUnique(const QString &identifier) const;
bool isValidNewIdentifier(QString identifier) const;
QString toUniqueIdentifier(const QString &identifier) const;
QString getProjectTitle();
bool readWildMonData();
tsl::ordered_map<QString, tsl::ordered_map<QString, WildPokemonHeader>> wildMonData;
@ -139,25 +154,24 @@ public:
bool readSpeciesIconPaths();
QMap<QString, QString> speciesToIconPath;
void addNewMapsec(const QString &name);
void removeMapsec(const QString &name);
void addNewMapsec(const QString &idName);
void removeMapsec(const QString &idName);
QString getMapsecDisplayName(const QString &idName) const { return this->mapSectionDisplayNames.value(idName); }
void setMapsecDisplayName(const QString &idName, const QString &displayName);
bool hasUnsavedChanges();
bool hasUnsavedDataChanges = false;
QSet<QString> getTopLevelMapFields();
void initTopLevelMapFields();
bool readMapJson(const QString &mapName, QJsonDocument * out);
bool loadMapData(Map*);
bool readMapLayouts();
Layout *loadLayout(QString layoutId);
Layout *createNewLayout(Layout::SimpleSettings &layoutSettings);
bool loadLayout(Layout *);
bool loadMapLayout(Map*);
bool loadLayoutTilesets(Layout *);
void loadTilesetAssets(Tileset*);
void loadTilesetTiles(Tileset*, QImage);
void loadTilesetMetatiles(Tileset*);
void loadTilesetMetatileLabels(Tileset*);
void loadTilesetPalettes(Tileset*);
void readTilesetPaths(Tileset* tileset);
void saveLayout(Layout *);
@ -172,15 +186,10 @@ public:
void saveMapGroups();
void saveRegionMapSections();
void saveWildMonData();
void saveMapConstantsHeader();
void saveHealLocations(Map*);
void saveTilesets(Tileset*, Tileset*);
void saveTilesetMetatileLabels(Tileset*, Tileset*);
void saveTilesetMetatileAttributes(Tileset*);
void saveTilesetMetatiles(Tileset*);
void saveTilesetTilesImage(Tileset*);
void saveTilesetPalettes(Tileset*);
void appendTilesetLabel(QString label, QString isSecondaryStr);
void appendTilesetLabel(const QString &label, const QString &isSecondaryStr);
bool readTilesetLabels();
bool readTilesetMetatileLabels();
bool readRegionMapSections();
@ -217,9 +226,8 @@ public:
QString getScriptDefaultString(bool usePoryScript, QString mapName) const;
QStringList getEventScriptsFilePaths() const;
QString getDefaultPrimaryTilesetLabel();
QString getDefaultSecondaryTilesetLabel();
QString getDynamicMapDefineName();
QString getDefaultPrimaryTilesetLabel() const;
QString getDefaultSecondaryTilesetLabel() const;
void updateTilesetMetatileLabels(Tileset *tileset);
QString buildMetatileLabelsText(const QMap<QString, uint16_t> defines);
QString findMetatileLabelsTileset(QString label);
@ -227,6 +235,9 @@ public:
static QString getExistingFilepath(QString filepath);
void applyParsedLimits();
static QString getEmptyMapDefineName();
static QString getDynamicMapDefineName();
static QString getDynamicMapName();
static int getNumTilesPrimary();
static int getNumTilesTotal();
static int getNumMetatilesPrimary();
@ -234,7 +245,7 @@ public:
static int getNumPalettesPrimary();
static int getNumPalettesTotal();
static int getMaxMapDataSize();
static int getDefaultMapSize();
static int getDefaultMapDimension();
static int getMaxMapWidth();
static int getMaxMapHeight();
static int getMapDataSize(int width, int height);
@ -242,14 +253,17 @@ public:
bool calculateDefaultMapSize();
static int getMaxObjectEvents();
static QString getEmptyMapsecName();
static QString getMapGroupPrefix();
static void numericalModeSort(QStringList &list);
private:
QMap<QString, QString> mapSectionDisplayNames;
void updateLayout(Layout *);
void setNewLayoutBlockdata(Layout *layout);
void setNewLayoutBorder(Layout *layout);
void setNewMapEvents(Map *map);
void setNewMapConnections(Map *map);
void saveHealLocationsData(Map *map);
void saveHealLocationsConstants();
@ -262,13 +276,20 @@ private:
static int num_pals_primary;
static int num_pals_total;
static int max_map_data_size;
static int default_map_size;
static int default_map_dimension;
static int max_object_events;
signals:
void fileChanged(QString filepath);
void mapSectionIdNamesChanged();
void mapLoaded(Map *map);
void mapCreated(Map *newMap, const QString &groupName);
void layoutCreated(Layout *newLayout);
void tilesetCreated(Tileset *newTileset);
void mapGroupAdded(const QString &groupName);
void mapSectionAdded(const QString &idName);
void mapSectionDisplayNameChanged(const QString &idName, const QString &displayName);
void mapSectionIdNamesChanged(const QStringList &idNames);
void mapsExcluded(const QStringList &excludedMapNames);
};
#endif // PROJECT_H

View File

@ -2,7 +2,6 @@
#define ABOUTPORYMAP_H
#include <QString>
#include <QRegularExpression>
#include <QDialog>
namespace Ui {

View File

@ -2,7 +2,6 @@
#define COLORINPUTWIDGET_H
#include <QGroupBox>
#include <QValidator>
namespace Ui {
class ColorInputWidget;

View File

@ -0,0 +1,84 @@
#ifndef MAPHEADERFORM_H
#define MAPHEADERFORM_H
/*
This is the UI class used to edit the fields in a map's header.
It's intended to be used anywhere the UI needs to present an editor for a map's header,
e.g. for the current map in the main editor or in the new map dialog.
*/
#include <QWidget>
#include <QPointer>
#include "mapheader.h"
class Project;
namespace Ui {
class MapHeaderForm;
}
class MapHeaderForm : public QWidget
{
Q_OBJECT
public:
explicit MapHeaderForm(QWidget *parent = nullptr);
~MapHeaderForm();
void setProject(Project * project, bool allowProjectChanges = true);
void clear();
void setHeader(MapHeader *header);
void setHeaderData(const MapHeader &header);
MapHeader headerData() const;
void setSong(const QString &song);
void setLocation(const QString &location);
void setLocationName(const QString &locationName);
void setRequiresFlash(bool requiresFlash);
void setWeather(const QString &weather);
void setType(const QString &type);
void setBattleScene(const QString &battleScene);
void setShowsLocationName(bool showsLocationName);
void setAllowsRunning(bool allowsRunning);
void setAllowsBiking(bool allowsBiking);
void setAllowsEscaping(bool allowsEscaping);
void setFloorNumber(int floorNumber);
QString song() const;
QString location() const;
QString locationName() const;
bool requiresFlash() const;
QString weather() const;
QString type() const;
QString battleScene() const;
bool showsLocationName() const;
bool allowsRunning() const;
bool allowsBiking() const;
bool allowsEscaping() const;
int floorNumber() const;
private:
Ui::MapHeaderForm *ui;
QPointer<MapHeader> m_header = nullptr;
QPointer<Project> m_project = nullptr;
bool m_allowProjectChanges = true;
void setLocations(const QStringList &locations);
void updateLocationName();
void onSongUpdated(const QString &song);
void onLocationChanged(const QString &location);
void onLocationNameChanged(const QString &locationName);
void onWeatherChanged(const QString &weather);
void onTypeChanged(const QString &type);
void onBattleSceneChanged(const QString &battleScene);
void onRequiresFlashChanged(int selected);
void onShowLocationNameChanged(int selected);
void onAllowRunningChanged(int selected);
void onAllowBikingChanged(int selected);
void onAllowEscapingChanged(int selected);
void onFloorNumberChanged(int offset);
};
#endif // MAPHEADERFORM_H

View File

@ -45,22 +45,25 @@ public:
private:
Ui::MapImageExporter *ui;
Layout *layout = nullptr;
Map *map = nullptr;
Editor *editor = nullptr;
QGraphicsScene *scene = nullptr;
Layout *m_layout = nullptr;
Map *m_map = nullptr;
Editor *m_editor = nullptr;
QGraphicsScene *m_scene = nullptr;
QPixmap preview;
QPixmap m_preview;
ImageExporterSettings settings;
ImageExporterMode mode = ImageExporterMode::Normal;
ImageExporterSettings m_settings;
ImageExporterMode m_mode = ImageExporterMode::Normal;
void updatePreview();
void scalePreview();
void updateShowBorderState();
void saveImage();
QPixmap getStitchedImage(QProgressDialog *progress, bool includeBorder);
QPixmap getFormattedMapPixmap();
QPixmap getFormattedMapPixmap(Map *map, bool ignoreBorder = false);
QPixmap getFormattedLayoutPixmap(Layout *layout, bool ignoreBorder = false, bool ignoreGrid = false);
void paintGrid(QPixmap *pixmap, bool ignoreBorder = false);
bool historyItemAppliesToFrame(const QUndoCommand *command);
protected:

View File

@ -13,9 +13,8 @@
class Project;
enum MapListUserRoles {
GroupRole = Qt::UserRole + 1, // Used to hold the map group number.
TypeRole, // Used to differentiate between the different layers of the map list tree view.
TypeRole2, // Used for various extra data needed.
NameRole = Qt::UserRole, // Holds the name of the item in the list
TypeRole, // Used to differentiate between the different layers of the map list tree view.
};
@ -55,22 +54,47 @@ private:
};
class QRegularExpressionValidator;
class MapListModel : public QStandardItemModel {
Q_OBJECT
public:
MapListModel(QObject *parent = nullptr) : QStandardItemModel(parent) {};
MapListModel(Project *project, QObject *parent = nullptr);
~MapListModel() { }
virtual QModelIndex indexOf(QString id) const = 0;
void setActiveItem(const QString &itemName) { this->activeItemName = itemName; }
virtual QStandardItem *insertMapItem(const QString &mapName, const QString &folderName);
virtual QStandardItem *insertMapFolderItem(const QString &folderName);
virtual QModelIndex indexOf(const QString &itemName) const;
virtual void removeItemAt(const QModelIndex &index);
virtual QStandardItem *getItem(const QModelIndex &index) const = 0;
virtual QStandardItem *itemAt(const QModelIndex &index) const;
virtual QStandardItem *itemAt(const QString &itemName) const;
virtual QVariant data(const QModelIndex &index, int role) const override;
protected:
virtual void removeItem(QStandardItem *item) = 0;
Project *project;
QStandardItem *root = nullptr;
QString activeItemName;
QString folderTypeName;
bool sortingEnabled = false;
bool editable = false;
QIcon mapGrayIcon;
QIcon mapIcon;
QIcon mapEditedIcon;
QIcon mapOpenedIcon;
QIcon mapFolderIcon;
QIcon emptyMapFolderIcon;
QMap<QString, QStandardItem *> mapFolderItems;
QMap<QString, QStandardItem *> mapItems;
virtual QStandardItem *createMapItem(const QString &mapName, QStandardItem *map = nullptr);
virtual QStandardItem *createMapFolderItem(const QString &groupName, QStandardItem *fromItem = nullptr);
virtual void removeItem(QStandardItem *item) = 0;
};
class MapGroupModel : public MapListModel {
@ -81,87 +105,37 @@ public:
~MapGroupModel() { }
QVariant data(const QModelIndex &index, int role) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
Qt::DropActions supportedDropActions() const override;
QStringList mimeTypes() const override;
virtual QMimeData *mimeData(const QModelIndexList &indexes) const override;
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
public:
void setMap(QString mapName) { this->openMap = mapName; }
QStandardItem *createGroupItem(QString groupName, int groupIndex, QStandardItem *fromItem = nullptr);
QStandardItem *createMapItem(QString mapName, QStandardItem *fromItem = nullptr);
QStandardItem *insertGroupItem(QString groupName);
QStandardItem *insertMapItem(QString mapName, QString groupName);
virtual QStandardItem *getItem(const QModelIndex &index) const override;
virtual QModelIndex indexOf(QString mapName) const override;
void initialize();
QMimeData *mimeData(const QModelIndexList &indexes) const override;
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
protected:
virtual void removeItem(QStandardItem *item) override;
void removeItem(QStandardItem *item) override;
private:
friend class MapTree;
void updateProject();
private:
Project *project;
QStandardItem *root = nullptr;
QMap<QString, QStandardItem *> groupItems;
QMap<QString, QStandardItem *> mapItems;
QString openMap;
signals:
void dragMoveCompleted();
};
class MapAreaModel : public MapListModel {
class MapLocationModel : public MapListModel {
Q_OBJECT
public:
MapAreaModel(Project *project, QObject *parent = nullptr);
~MapAreaModel() {}
MapLocationModel(Project *project, QObject *parent = nullptr);
~MapLocationModel() {}
QVariant data(const QModelIndex &index, int role) const override;
public:
void setMap(QString mapName) { this->openMap = mapName; }
QStandardItem *createAreaItem(QString areaName);
QStandardItem *createMapItem(QString mapName, int areaIndex, int mapIndex);
QStandardItem *insertAreaItem(QString areaName);
QStandardItem *insertMapItem(QString mapName, QString areaName, int groupIndex);
virtual QStandardItem *getItem(const QModelIndex &index) const override;
virtual QModelIndex indexOf(QString mapName) const override;
void initialize();
QStandardItem *createMapFolderItem(const QString &folderName, QStandardItem *folder) override;
protected:
virtual void removeItem(QStandardItem *item) override;
private:
Project *project;
QStandardItem *root = nullptr;
QMap<QString, QStandardItem *> areaItems;
QMap<QString, QStandardItem *> mapItems;
QString openMap;
signals:
void edited();
void removeItem(QStandardItem *item) override;
};
@ -174,35 +148,10 @@ public:
~LayoutTreeModel() {}
QVariant data(const QModelIndex &index, int role) const override;
public:
void setLayout(QString layoutId) { this->openLayout = layoutId; }
QStandardItem *createLayoutItem(QString layoutId);
QStandardItem *createMapItem(QString mapName);
QStandardItem *insertLayoutItem(QString layoutId);
QStandardItem *insertMapItem(QString mapName, QString layoutId);
virtual QStandardItem *getItem(const QModelIndex &index) const override;
virtual QModelIndex indexOf(QString layoutName) const override;
void initialize();
QStandardItem *createMapFolderItem(const QString &folderName, QStandardItem *folder) override;
protected:
virtual void removeItem(QStandardItem *item) override;
private:
Project *project;
QStandardItem *root = nullptr;
QMap<QString, QStandardItem *> layoutItems;
QMap<QString, QStandardItem *> mapItems;
QString openLayout;
signals:
void edited();
void removeItem(QStandardItem *item) override;
};
#endif // MAPLISTMODELS_H

60
include/ui/message.h Normal file
View File

@ -0,0 +1,60 @@
#pragma once
#ifndef MESSAGE_H
#define MESSAGE_H
/*
These classes are thin wrappers around QMessageBox for convenience.
The base Message class is a regular window-modal QMessageBox with "porymap" as the window title.
QMessageBox's static functions enforce application modality (among other things), which changes the style of the message boxes on macOS.
With these equivalent static functions we have more control over the appearance and behavior of the window,
and we keep the convenience of not needing to provide all the arguments.
If more control is needed (like adding custom buttons to the window) use the constructors as you would for a normal QMessageBox.
*/
#include <QMessageBox>
class Message : public QMessageBox {
public:
Message(QMessageBox::Icon icon, const QString &text, QMessageBox::StandardButtons buttons, QWidget *parent);
};
// Basic error message with an 'Ok' button.
class ErrorMessage : public Message {
public:
ErrorMessage(const QString &message, QWidget *parent);
static int show(const QString &message, QWidget *parent);
};
// Basic warning message with an 'Ok' button.
class WarningMessage : public Message {
public:
WarningMessage(const QString &message, QWidget *parent);
static int show(const QString &message, QWidget *parent);
};
// Basic informational message with a 'Close' button.
class InfoMessage : public Message {
public:
InfoMessage(const QString &message, QWidget *parent);
static int show(const QString &message, QWidget *parent);
};
// Basic question message with a 'Yes' and 'No' button.
class QuestionMessage : public Message {
public:
QuestionMessage(const QString &message, QWidget *parent);
static int show(const QString &message, QWidget *parent);
};
// Error message directing users to their log file.
// Shows the most recent error as detailed text.
class RecentErrorMessage : public ErrorMessage {
public:
RecentErrorMessage(const QString &message, QWidget *parent);
static int show(const QString &message, QWidget *parent);
};
#endif // MESSAGE_H

View File

@ -0,0 +1,49 @@
#ifndef NEWLAYOUTDIALOG_H
#define NEWLAYOUTDIALOG_H
#include <QDialog>
#include <QString>
#include "editor.h"
#include "project.h"
#include "map.h"
#include "mapheaderform.h"
#include "newlayoutform.h"
#include "lib/collapsiblesection.h"
namespace Ui {
class NewLayoutDialog;
}
class NewLayoutDialog : public QDialog
{
Q_OBJECT
public:
explicit NewLayoutDialog(Project *project, QWidget *parent = nullptr);
explicit NewLayoutDialog(Project *project, const Layout *layoutToCopy, QWidget *parent = nullptr);
~NewLayoutDialog();
virtual void accept() override;
signals:
void applied(const QString &newLayoutId);
private:
Ui::NewLayoutDialog *ui;
Project *project;
const Layout *layoutToCopy;
// Each of these validation functions will allow empty names up until `OK` is selected,
// because clearing the text during editing is common and we don't want to flash errors for this.
bool validateLayoutID(bool allowEmpty = false);
bool validateName(bool allowEmpty = false);
void refresh();
void saveSettings();
private slots:
void dialogButtonClicked(QAbstractButton *button);
void on_lineEdit_Name_textChanged(const QString &);
void on_lineEdit_LayoutID_textChanged(const QString &);
};
#endif // NEWLAYOUTDIALOG_H

View File

@ -0,0 +1,40 @@
#ifndef NEWLAYOUTFORM_H
#define NEWLAYOUTFORM_H
#include <QWidget>
#include "maplayout.h"
class Project;
namespace Ui {
class NewLayoutForm;
}
class NewLayoutForm : public QWidget
{
Q_OBJECT
public:
explicit NewLayoutForm(QWidget *parent = nullptr);
~NewLayoutForm();
void initUi(Project *project);
void setSettings(const Layout::Settings &settings);
Layout::Settings settings() const;
void setDisabled(bool disabled);
bool validate();
private:
Ui::NewLayoutForm *ui;
Project *m_project;
bool validateMapDimensions();
bool validatePrimaryTileset(bool allowEmpty = false);
bool validateSecondaryTileset(bool allowEmpty = false);
};
#endif // NEWLAYOUTFORM_H

View File

@ -0,0 +1,34 @@
#ifndef NEWLOCATIONDIALOG_H
#define NEWLOCATIONDIALOG_H
#include <QDialog>
#include <QAbstractButton>
#include <QPointer>
class Project;
namespace Ui {
class NewLocationDialog;
}
class NewLocationDialog : public QDialog
{
Q_OBJECT
public:
explicit NewLocationDialog(Project *project = nullptr, QWidget *parent = nullptr);
~NewLocationDialog();
virtual void accept() override;
private:
Ui::NewLocationDialog *ui;
QPointer<Project> project = nullptr;
const QString namePrefix;
bool validateIdName(bool allowEmpty = false);
void onIdNameChanged(const QString &name);
void dialogButtonClicked(QAbstractButton *button);
};
#endif // NEWLOCATIONDIALOG_H

51
include/ui/newmapdialog.h Normal file
View File

@ -0,0 +1,51 @@
#ifndef NEWMAPDIALOG_H
#define NEWMAPDIALOG_H
#include <QDialog>
#include <QString>
#include "editor.h"
#include "project.h"
#include "map.h"
#include "mapheaderform.h"
#include "newlayoutform.h"
#include "lib/collapsiblesection.h"
namespace Ui {
class NewMapDialog;
}
class NewMapDialog : public QDialog
{
Q_OBJECT
public:
explicit NewMapDialog(Project *project, QWidget *parent = nullptr);
explicit NewMapDialog(Project *project, const Map *mapToCopy = nullptr, QWidget *parent = nullptr);
explicit NewMapDialog(Project *project, int mapListTab, const QString &mapListItem, QWidget *parent = nullptr);
~NewMapDialog();
virtual void accept() override;
private:
Ui::NewMapDialog *ui;
Project *project;
CollapsibleSection *headerSection;
MapHeaderForm *headerForm;
const Map *mapToCopy;
// Each of these validation functions will allow empty names up until `OK` is selected,
// because clearing the text during editing is common and we don't want to flash errors for this.
bool validateName(bool allowEmpty = false);
bool validateGroup(bool allowEmpty = false);
bool validateLayoutID(bool allowEmpty = false);
void refresh();
void saveSettings();
private slots:
void dialogButtonClicked(QAbstractButton *button);
void on_lineEdit_Name_textChanged(const QString &);
void on_comboBox_Group_currentTextChanged(const QString &text);
void on_comboBox_LayoutID_currentTextChanged(const QString &text);
};
#endif // NEWMAPDIALOG_H

View File

@ -0,0 +1,36 @@
#ifndef NEWMAPGROUPDIALOG_H
#define NEWMAPGROUPDIALOG_H
/*
This is a generic dialog for requesting a new unique name from the user.
*/
#include <QDialog>
#include <QAbstractButton>
class Project;
namespace Ui {
class NewMapGroupDialog;
}
class NewMapGroupDialog : public QDialog
{
Q_OBJECT
public:
explicit NewMapGroupDialog(Project *project = nullptr, QWidget *parent = nullptr);
~NewMapGroupDialog();
virtual void accept() override;
private:
Ui::NewMapGroupDialog *ui;
Project *project = nullptr;
bool validateName(bool allowEmpty = false);
void onNameChanged(const QString &name);
void dialogButtonClicked(QAbstractButton *button);
};
#endif // NEWMAPGROUPDIALOG_H

View File

@ -1,70 +0,0 @@
#ifndef NEWMAPPOPUP_H
#define NEWMAPPOPUP_H
#include <QMainWindow>
#include <QString>
#include "editor.h"
#include "project.h"
#include "map.h"
namespace Ui {
class NewMapPopup;
}
class NewMapPopup : public QMainWindow
{
Q_OBJECT
public:
explicit NewMapPopup(QWidget *parent = nullptr, Project *project = nullptr);
~NewMapPopup();
Map *map;
int group;
bool existingLayout;
bool importedMap;
QString layoutId;
void init();
void initUi();
void init(int tabIndex, QString data);
void init(Layout *);
static void setDefaultSettings(Project *project);
signals:
void applied();
private:
Ui::NewMapPopup *ui;
Project *project;
bool checkNewMapDimensions();
bool checkNewMapGroup();
void saveSettings();
void useLayout(QString layoutId);
void useLayoutSettings(Layout *mapLayout);
struct Settings {
QString group;
int width;
int height;
int borderWidth;
int borderHeight;
QString primaryTilesetLabel;
QString secondaryTilesetLabel;
QString type;
QString location;
QString song;
bool canFlyTo;
bool showLocationName;
bool allowRunning;
bool allowBiking;
bool allowEscaping;
int floorNumber;
};
static struct Settings settings;
private slots:
void on_checkBox_UseExistingLayout_stateChanged(int state);
void on_comboBox_Layout_currentTextChanged(const QString &text);
void on_pushButton_NewMap_Accept_clicked();
void on_lineEdit_NewMap_Name_textChanged(const QString &);
};
#endif // NEWMAPPOPUP_H

View File

@ -2,7 +2,10 @@
#define NEWTILESETDIALOG_H
#include <QDialog>
#include "project.h"
#include <QAbstractButton>
class Project;
class Tileset;
namespace Ui {
class NewTilesetDialog;
@ -15,20 +18,20 @@ class NewTilesetDialog : public QDialog
public:
explicit NewTilesetDialog(Project *project, QWidget *parent = nullptr);
~NewTilesetDialog();
QString path;
QString fullSymbolName;
QString friendlyName;
bool isSecondary;
bool checkerboardFill;
private slots:
void NameOrSecondaryChanged();
void SecondaryChanged();
void FillChanged();
virtual void accept() override;
signals:
void applied(Tileset *tileset);
private:
Ui::NewTilesetDialog *ui;
Project *project = nullptr;
const QString symbolPrefix;
bool validateName(bool allowEmpty = false);
void onNameChanged(const QString &name);
void dialogButtonClicked(QAbstractButton *button);
};
#endif // NEWTILESETDIALOG_H

View File

@ -116,6 +116,7 @@ private:
void displayRegionMapEntryOptions();
void updateRegionMapEntryOptions(QString);
void setRegionMap(RegionMap *map);
void setLocations(const QStringList &locations);
void restoreWindowState();
void closeEvent(QCloseEvent* event);
@ -133,7 +134,7 @@ private slots:
void on_tabWidget_Region_Map_currentChanged(int);
void on_pushButton_RM_Options_delete_clicked();
void on_comboBox_RM_ConnectedMap_textActivated(const QString &);
void on_comboBox_RM_Entry_MapSection_textActivated(const QString &);
void on_comboBox_RM_Entry_MapSection_currentTextChanged(const QString &);
void on_comboBox_regionSelector_textActivated(const QString &);
void on_comboBox_layoutLayer_textActivated(const QString &);
void on_spinBox_RM_Entry_x_valueChanged(int);
@ -148,7 +149,6 @@ private slots:
void on_checkBox_tileVFlip_stateChanged(int);
void on_verticalSlider_Zoom_Map_Image_valueChanged(int);
void on_verticalSlider_Zoom_Image_Tiles_valueChanged(int);
void on_lineEdit_RM_MapName_textEdited(const QString &);
void onHoveredRegionMapTileChanged(int x, int y);
void onHoveredRegionMapTileCleared();
void mouseEvent_region_map(QGraphicsSceneMouseEvent *event, RegionMapPixmapItem *item);

View File

@ -121,9 +121,7 @@ private slots:
void on_horizontalSlider_TilesZoom_valueChanged(int value);
private:
void initUi();
void setAttributesUi();
void setMetatileLabelValidator();
void initMetatileSelector();
void initTileSelector();
void initSelectedTileItem();

View File

@ -33,7 +33,8 @@ DEFINES += PORYMAP_LATEST_COMMIT=\\\"$$LATEST_COMMIT\\\"
VERSION = 5.4.1
DEFINES += PORYMAP_VERSION=\\\"$$VERSION\\\"
SOURCES += src/core/block.cpp \
SOURCES += src/core/advancemapparser.cpp \
src/core/block.cpp \
src/core/bitpacker.cpp \
src/core/blockdata.cpp \
src/core/events.cpp \
@ -42,20 +43,21 @@ SOURCES += src/core/block.cpp \
src/core/imageexport.cpp \
src/core/map.cpp \
src/core/mapconnection.cpp \
src/core/mapheader.cpp \
src/core/maplayout.cpp \
src/core/mapparser.cpp \
src/core/metatile.cpp \
src/core/metatileparser.cpp \
src/core/network.cpp \
src/core/paletteutil.cpp \
src/core/parseutil.cpp \
src/core/tile.cpp \
src/core/tileset.cpp \
src/core/validator.cpp \
src/core/regionmap.cpp \
src/core/wildmoninfo.cpp \
src/core/editcommands.cpp \
src/lib/fex/lexer.cpp \
src/lib/fex/parser.cpp \
src/lib/collapsiblesection.cpp \
src/lib/orderedjson.cpp \
src/core/regionmapeditcommands.cpp \
src/scriptapi/apimap.cpp \
@ -87,17 +89,23 @@ SOURCES += src/core/block.cpp \
src/ui/filterchildrenproxymodel.cpp \
src/ui/maplistmodels.cpp \
src/ui/maplisttoolbar.cpp \
src/ui/message.cpp \
src/ui/graphicsview.cpp \
src/ui/imageproviders.cpp \
src/ui/layoutpixmapitem.cpp \
src/ui/prefabcreationdialog.cpp \
src/ui/regionmappixmapitem.cpp \
src/ui/citymappixmapitem.cpp \
src/ui/mapheaderform.cpp \
src/ui/metatilelayersitem.cpp \
src/ui/metatileselector.cpp \
src/ui/movablerect.cpp \
src/ui/movementpermissionsselector.cpp \
src/ui/neweventtoolbutton.cpp \
src/ui/newlayoutdialog.cpp \
src/ui/newlayoutform.cpp \
src/ui/newlocationdialog.cpp \
src/ui/newmapgroupdialog.cpp \
src/ui/noscrollcombobox.cpp \
src/ui/noscrollspinbox.cpp \
src/ui/montabwidget.cpp \
@ -110,7 +118,7 @@ SOURCES += src/core/block.cpp \
src/ui/tileseteditortileselector.cpp \
src/ui/tilemaptileselector.cpp \
src/ui/regionmapeditor.cpp \
src/ui/newmappopup.cpp \
src/ui/newmapdialog.cpp \
src/ui/mapimageexporter.cpp \
src/ui/newtilesetdialog.cpp \
src/ui/flowlayout.cpp \
@ -133,7 +141,8 @@ SOURCES += src/core/block.cpp \
src/ui/updatepromoter.cpp \
src/ui/wildmonchart.cpp
HEADERS += include/core/block.h \
HEADERS += include/core/advancemapparser.h \
include/core/block.h \
include/core/bitpacker.h \
include/core/blockdata.h \
include/core/events.h \
@ -143,15 +152,15 @@ HEADERS += include/core/block.h \
include/core/imageexport.h \
include/core/map.h \
include/core/mapconnection.h \
include/core/mapheader.h \
include/core/maplayout.h \
include/core/mapparser.h \
include/core/metatile.h \
include/core/metatileparser.h \
include/core/network.h \
include/core/paletteutil.h \
include/core/parseutil.h \
include/core/tile.h \
include/core/tileset.h \
include/core/validator.h \
include/core/regionmap.h \
include/core/wildmoninfo.h \
include/core/editcommands.h \
@ -161,6 +170,7 @@ HEADERS += include/core/block.h \
include/lib/fex/define_statement.h \
include/lib/fex/lexer.h \
include/lib/fex/parser.h \
include/lib/collapsiblesection.h \
include/lib/orderedmap.h \
include/lib/orderedjson.h \
include/ui/aboutporymap.h \
@ -174,6 +184,7 @@ HEADERS += include/core/block.h \
include/ui/connectionpixmapitem.h \
include/ui/currentselectedmetatilespixmapitem.h \
include/ui/gridsettings.h \
include/ui/mapheaderform.h \
include/ui/newmapconnectiondialog.h \
include/ui/prefabframe.h \
include/ui/projectsettingseditor.h \
@ -186,6 +197,7 @@ HEADERS += include/core/block.h \
include/ui/filterchildrenproxymodel.h \
include/ui/maplistmodels.h \
include/ui/maplisttoolbar.h \
include/ui/message.h \
include/ui/graphicsview.h \
include/ui/imageproviders.h \
include/ui/layoutpixmapitem.h \
@ -199,6 +211,10 @@ HEADERS += include/core/block.h \
include/ui/movablerect.h \
include/ui/movementpermissionsselector.h \
include/ui/neweventtoolbutton.h \
include/ui/newlayoutdialog.h \
include/ui/newlayoutform.h \
include/ui/newlocationdialog.h \
include/ui/newmapgroupdialog.h \
include/ui/noscrollcombobox.h \
include/ui/noscrollspinbox.h \
include/ui/montabwidget.h \
@ -212,7 +228,7 @@ HEADERS += include/core/block.h \
include/ui/tileseteditortileselector.h \
include/ui/tilemaptileselector.h \
include/ui/regionmapeditor.h \
include/ui/newmappopup.h \
include/ui/newmapdialog.h \
include/ui/mapimageexporter.h \
include/ui/newtilesetdialog.h \
include/ui/overlay.h \
@ -241,14 +257,19 @@ FORMS += forms/mainwindow.ui \
forms/colorinputwidget.ui \
forms/connectionslistitem.ui \
forms/gridsettingsdialog.ui \
forms/mapheaderform.ui \
forms/maplisttoolbar.ui \
forms/newlayoutdialog.ui \
forms/newlayoutform.ui \
forms/newlocationdialog.ui \
forms/newmapconnectiondialog.ui \
forms/newmapgroupdialog.ui \
forms/prefabcreationdialog.ui \
forms/prefabframe.ui \
forms/tileseteditor.ui \
forms/paletteeditor.ui \
forms/regionmapeditor.ui \
forms/newmappopup.ui \
forms/newmapdialog.ui \
forms/aboutporymap.ui \
forms/newtilesetdialog.ui \
forms/mapimageexporter.ui \

View File

@ -2,6 +2,7 @@
#include "log.h"
#include "shortcut.h"
#include "map.h"
#include "validator.h"
#include <QDir>
#include <QFile>
#include <QFormLayout>
@ -81,6 +82,7 @@ const QMap<ProjectIdentifier, QPair<QString, QString>> ProjectConfig::defaultIde
{ProjectIdentifier::symbol_spawn_npcs, {"symbol_spawn_npcs", "u8 sWhiteoutRespawnHealerNpcIds"}},
{ProjectIdentifier::symbol_attribute_table, {"symbol_attribute_table", "sMetatileAttrMasks"}},
{ProjectIdentifier::symbol_tilesets_prefix, {"symbol_tilesets_prefix", "gTileset_"}},
{ProjectIdentifier::symbol_dynamic_map_name, {"symbol_dynamic_map_name", "Dynamic"}},
// Defines
{ProjectIdentifier::define_obj_event_count, {"define_obj_event_count", "OBJECT_EVENT_TEMPLATES_COUNT"}},
{ProjectIdentifier::define_min_level, {"define_min_level", "MIN_LEVEL"}},
@ -152,7 +154,6 @@ const QMap<ProjectFilePath, QPair<QString, QString>> ProjectConfig::defaultPaths
{ProjectFilePath::data_pokemon_gfx, { "data_pokemon_gfx", "src/data/graphics/pokemon.h"}},
{ProjectFilePath::data_heal_locations, { "data_heal_locations", "src/data/heal_locations.h"}},
{ProjectFilePath::constants_global, { "constants_global", "include/constants/global.h"}},
{ProjectFilePath::constants_map_groups, { "constants_map_groups", "include/constants/map_groups.h"}},
{ProjectFilePath::constants_items, { "constants_items", "include/constants/items.h"}},
{ProjectFilePath::constants_flags, { "constants_flags", "include/constants/flags.h"}},
{ProjectFilePath::constants_vars, { "constants_vars", "include/constants/vars.h"}},
@ -348,6 +349,10 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) {
this->customScriptsEditorState = bytesFromString(value);
} else if (key == "wild_mon_chart_geometry") {
this->wildMonChartGeometry = bytesFromString(value);
} else if (key == "new_map_dialog_geometry") {
this->newMapDialogGeometry = bytesFromString(value);
} else if (key == "new_layout_dialog_geometry") {
this->newLayoutDialogGeometry = bytesFromString(value);
} else if (key == "metatiles_zoom") {
this->metatilesZoom = getConfigInteger(key, value, 10, 100, 30);
} else if (key == "collision_zoom") {
@ -374,6 +379,8 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) {
this->monitorFiles = getConfigBool(key, value);
} else if (key == "tileset_checkerboard_fill") {
this->tilesetCheckerboardFill = getConfigBool(key, value);
} else if (key == "new_map_header_section_expanded") {
this->newMapHeaderSectionExpanded = getConfigBool(key, value);
} else if (key == "theme") {
this->theme = value;
} else if (key == "wild_mon_chart_theme") {
@ -438,6 +445,8 @@ QMap<QString, QString> PorymapConfig::getKeyValueMap() {
map.insert("custom_scripts_editor_geometry", stringFromByteArray(this->customScriptsEditorGeometry));
map.insert("custom_scripts_editor_state", stringFromByteArray(this->customScriptsEditorState));
map.insert("wild_mon_chart_geometry", stringFromByteArray(this->wildMonChartGeometry));
map.insert("new_map_dialog_geometry", stringFromByteArray(this->newMapDialogGeometry));
map.insert("new_layout_dialog_geometry", stringFromByteArray(this->newLayoutDialogGeometry));
map.insert("mirror_connecting_maps", this->mirrorConnectingMaps ? "1" : "0");
map.insert("show_dive_emerge_maps", this->showDiveEmergeMaps ? "1" : "0");
map.insert("dive_emerge_map_opacity", QString::number(this->diveEmergeMapOpacity));
@ -457,6 +466,7 @@ QMap<QString, QString> PorymapConfig::getKeyValueMap() {
map.insert("show_tileset_editor_divider", this->showTilesetEditorDivider ? "1" : "0");
map.insert("monitor_files", this->monitorFiles ? "1" : "0");
map.insert("tileset_checkerboard_fill", this->tilesetCheckerboardFill ? "1" : "0");
map.insert("new_map_header_section_expanded", this->newMapHeaderSectionExpanded ? "1" : "0");
map.insert("theme", this->theme);
map.insert("wild_mon_chart_theme", this->wildMonChartTheme);
map.insert("text_editor_open_directory", this->textEditorOpenFolder);
@ -954,13 +964,23 @@ QString ProjectConfig::getFilePath(ProjectFilePath pathId) {
}
void ProjectConfig::setIdentifier(ProjectIdentifier id, const QString &text) {
if (!defaultIdentifiers.contains(id)) return;
QString copy(text);
if (copy.isEmpty()) {
void ProjectConfig::setIdentifier(ProjectIdentifier id, QString text) {
if (!defaultIdentifiers.contains(id))
return;
if (text.isEmpty()) {
this->identifiers.remove(id);
} else {
this->identifiers[id] = copy;
const QString idName = defaultIdentifiers.value(id).first;
if (idName.startsWith("define_") || idName.startsWith("symbol_")) {
// Validate the input for the identifier, depending on the type.
IdentifierValidator validator;
if (!validator.isValid(text)) {
logError(QString("The name '%1' for project identifier '%2' is invalid. It must only contain word characters, and cannot start with a digit.").arg(text).arg(idName));
return;
}
}
this->identifiers[id] = text;
}
}

View File

@ -0,0 +1,217 @@
#include "advancemapparser.h"
#include "log.h"
#include "project.h"
#include "maplayout.h"
Layout *AdvanceMapParser::parseLayout(const QString &filepath, bool *error, const Project *project)
{
QFile file(filepath);
if (!file.open(QIODevice::ReadOnly)) {
*error = true;
logError(QString("Could not open Advance Map 1.92 Map .map file '%1': ").arg(filepath) + file.errorString());
return nullptr;
}
QByteArray in = file.readAll();
file.close();
if (in.length() < 20 || in.length() % 2 != 0) {
*error = true;
logError(QString("Advance Map 1.92 Map .map file '%1' is an unexpected size.").arg(filepath));
return nullptr;
}
int borderWidth = static_cast<unsigned char>(in.at(16)); // 0 in RSE .map files
int borderHeight = static_cast<unsigned char>(in.at(17)); // 0 in RSE .map files
int numBorderTiles = borderWidth * borderHeight; // 0 if RSE
int mapDataOffset = 20 + (numBorderTiles * 2); // FRLG .map files store border metatile data after the header
int mapWidth = static_cast<unsigned char>(in.at(0)) |
(static_cast<unsigned char>(in.at(1)) << 8) |
(static_cast<unsigned char>(in.at(2)) << 16) |
(static_cast<unsigned char>(in.at(3)) << 24);
int mapHeight = static_cast<unsigned char>(in.at(4)) |
(static_cast<unsigned char>(in.at(5)) << 8) |
(static_cast<unsigned char>(in.at(6)) << 16) |
(static_cast<unsigned char>(in.at(7)) << 24);
int mapPrimaryTilesetNum = static_cast<unsigned char>(in.at(8)) |
(static_cast<unsigned char>(in.at(9)) << 8) |
(static_cast<unsigned char>(in.at(10)) << 16) |
(static_cast<unsigned char>(in.at(11)) << 24);
int mapSecondaryTilesetNum = static_cast<unsigned char>(in.at(12)) |
(static_cast<unsigned char>(in.at(13)) << 8) |
(static_cast<unsigned char>(in.at(14)) << 16) |
(static_cast<unsigned char>(in.at(15)) << 24);
int numMetatiles = mapWidth * mapHeight;
int expectedFileSize = 20 + (numBorderTiles * 2) + (numMetatiles * 2);
if (in.length() != expectedFileSize) {
*error = true;
logError(QString(".map file is an unexpected size. Expected %1 bytes, but it has %2 bytes.").arg(expectedFileSize).arg(in.length()));
return nullptr;
}
Blockdata blockdata;
for (int i = mapDataOffset; (i + 1) < in.length(); i += 2) {
uint16_t word = static_cast<uint16_t>((in[i] & 0xff) + ((in[i + 1] & 0xff) << 8));
blockdata.append(word);
}
Blockdata border;
if (numBorderTiles != 0) {
for (int i = 20; (i + 1) < mapDataOffset; i += 2) {
uint16_t word = static_cast<uint16_t>((in[i] & 0xff) + ((in[i + 1] & 0xff) << 8));
border.append(word);
}
}
Layout *mapLayout = new Layout();
mapLayout->width = mapWidth;
mapLayout->height = mapHeight;
mapLayout->border_width = (borderWidth == 0) ? DEFAULT_BORDER_WIDTH : borderWidth;
mapLayout->border_height = (borderHeight == 0) ? DEFAULT_BORDER_HEIGHT : borderHeight;
const QList<QString> tilesets = project->tilesetLabelsOrdered;
if (mapPrimaryTilesetNum > tilesets.size())
mapLayout->tileset_primary_label = project->getDefaultPrimaryTilesetLabel();
else
mapLayout->tileset_primary_label = tilesets.at(mapPrimaryTilesetNum);
if (mapSecondaryTilesetNum > tilesets.size())
mapLayout->tileset_secondary_label = project->getDefaultSecondaryTilesetLabel();
else
mapLayout->tileset_secondary_label = tilesets.at(mapSecondaryTilesetNum);
mapLayout->blockdata = blockdata;
if (!border.isEmpty()) {
mapLayout->border = border;
}
return mapLayout;
}
QList<Metatile*> AdvanceMapParser::parseMetatiles(const QString &filepath, bool *error, bool primaryTileset)
{
QFile file(filepath);
if (!file.open(QIODevice::ReadOnly)) {
*error = true;
logError(QString("Could not open Advance Map 1.92 Metatile .bvd file '%1': ").arg(filepath) + file.errorString());
return { };
}
QByteArray in = file.readAll();
file.close();
if (in.length() < 9 || in.length() % 2 != 0) {
*error = true;
logError(QString("Advance Map 1.92 Metatile .bvd file '%1' is an unexpected size.").arg(filepath));
return { };
}
int projIdOffset = in.length() - 4;
int metatileSize = 16;
BaseGameVersion version;
if (in.at(projIdOffset + 0) == 'R'
&& in.at(projIdOffset + 1) == 'S'
&& in.at(projIdOffset + 2) == 'E'
&& in.at(projIdOffset + 3) == ' ') {
// ruby and emerald are handled equally here.
version = BaseGameVersion::pokeemerald;
} else if (in.at(projIdOffset + 0) == 'F'
&& in.at(projIdOffset + 1) == 'R'
&& in.at(projIdOffset + 2) == 'L'
&& in.at(projIdOffset + 3) == 'G') {
version = BaseGameVersion::pokefirered;
} else {
*error = true;
logError(QString("Detected unsupported game type from .bvd file. Last 4 bytes of file must be 'RSE ' or 'FRLG'."));
return { };
}
int attrSize = Metatile::getDefaultAttributesSize(version);
int maxMetatiles = primaryTileset ? Project::getNumMetatilesPrimary() : Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary();
int numMetatiles = static_cast<unsigned char>(in.at(0)) |
(static_cast<unsigned char>(in.at(1)) << 8) |
(static_cast<unsigned char>(in.at(2)) << 16) |
(static_cast<unsigned char>(in.at(3)) << 24);
if (numMetatiles > maxMetatiles) {
*error = true;
logError(QString(".bvd file contains data for %1 metatiles, but the maximum number of metatiles is %2.").arg(numMetatiles).arg(maxMetatiles));
return { };
}
if (numMetatiles < 1) {
*error = true;
logError(QString(".bvd file contains no data for metatiles."));
return { };
}
int expectedFileSize = 4 + (metatileSize * numMetatiles) + (attrSize * numMetatiles) + 4;
if (in.length() != expectedFileSize) {
*error = true;
logError(QString(".bvd file is an unexpected size. Expected %1 bytes, but it has %2 bytes.").arg(expectedFileSize).arg(in.length()));
return { };
}
QList<Metatile*> metatiles;
for (int i = 0; i < numMetatiles; i++) {
Metatile *metatile = new Metatile();
QList<Tile> tiles;
for (int j = 0; j < 8; j++) {
int metatileOffset = 4 + i * metatileSize + j * 2;
Tile tile(static_cast<uint16_t>(
static_cast<unsigned char>(in.at(metatileOffset)) |
(static_cast<unsigned char>(in.at(metatileOffset + 1)) << 8)));
tiles.append(tile);
}
// AdvanceMap .bvd files only contain 8 tiles of data per metatile.
// If the user has triple-layer metatiles enabled we need to fill the remaining 4 tiles ourselves.
if (projectConfig.tripleLayerMetatilesEnabled) {
Tile tile = Tile();
for (int j = 0; j < 4; j++)
tiles.append(tile);
}
int attrOffset = 4 + (numMetatiles * metatileSize) + (i * attrSize);
uint32_t attributes = 0;
for (int j = 0; j < attrSize; j++)
attributes |= static_cast<unsigned char>(in.at(attrOffset + j)) << (8 * j);
metatile->setAttributes(attributes, version);
metatile->tiles = tiles;
metatiles.append(metatile);
}
return metatiles;
}
QList<QRgb> AdvanceMapParser::parsePalette(const QString &filepath, bool *error) {
QFile file(filepath);
if (!file.open(QIODevice::ReadOnly)) {
*error = true;
logError(QString("Could not open Advance Map 1.92 palette file '%1': ").arg(filepath) + file.errorString());
return QList<QRgb>();
}
QByteArray in = file.readAll();
file.close();
if (in.length() % 4 != 0) {
*error = true;
logError(QString("Advance Map 1.92 palette file '%1' had an unexpected format. File's length must be a multiple of 4, but the length is %2.").arg(filepath).arg(in.length()));
return QList<QRgb>();
}
QList<QRgb> palette;
int i = 0;
while (i < in.length()) {
unsigned char red = static_cast<unsigned char>(in.at(i + 0));
unsigned char green = static_cast<unsigned char>(in.at(i + 1));
unsigned char blue = static_cast<unsigned char>(in.at(i + 2));
palette.append(qRgb(red, green, blue));
i += 4;
}
return palette;
}

View File

@ -44,7 +44,7 @@ void Event::setPixmapItem(DraggablePixmapItem *item) {
}
int Event::getEventIndex() {
return this->map->events.value(this->getEventGroup()).indexOf(this);
return this->map->getIndexOfEvent(this);
}
void Event::setDefaultValues(Project *) {
@ -392,7 +392,8 @@ OrderedJson::object CloneObjectEvent::buildEventJson(Project *project) {
cloneJson["x"] = this->getX();
cloneJson["y"] = this->getY();
cloneJson["target_local_id"] = this->getTargetID();
cloneJson["target_map"] = project->mapNamesToMapConstants.value(this->getTargetMap());
const QString mapName = this->getTargetMap();
cloneJson["target_map"] = project->mapNamesToMapConstants.value(mapName, mapName);
this->addCustomValuesTo(&cloneJson);
return cloneJson;
@ -404,17 +405,11 @@ bool CloneObjectEvent::loadFromJson(QJsonObject json, Project *project) {
this->setGfx(ParseUtil::jsonToQString(json["graphics_id"]));
this->setTargetID(ParseUtil::jsonToInt(json["target_local_id"]));
// Ensure the target map constant is valid before adding it to the events.
const QString dynamicMapConstant = project->getDynamicMapDefineName();
QString mapConstant = ParseUtil::jsonToQString(json["target_map"]);
if (project->mapConstantsToMapNames.contains(mapConstant)) {
this->setTargetMap(project->mapConstantsToMapNames.value(mapConstant));
} else if (mapConstant == dynamicMapConstant) {
this->setTargetMap(DYNAMIC_MAP_NAME);
} else {
logWarn(QString("Target Map constant '%1' is invalid. Using default '%2'.").arg(mapConstant).arg(dynamicMapConstant));
this->setTargetMap(DYNAMIC_MAP_NAME);
}
// Log a warning if "target_map" isn't a known map ID, but don't overwrite user data.
const QString mapConstant = ParseUtil::jsonToQString(json["target_map"]);
if (!project->mapConstantsToMapNames.contains(mapConstant))
logWarn(QString("Unknown Target Map constant '%1'.").arg(mapConstant));
this->setTargetMap(project->mapConstantsToMapNames.value(mapConstant, mapConstant));
this->readCustomValues(json);
@ -424,7 +419,7 @@ bool CloneObjectEvent::loadFromJson(QJsonObject json, Project *project) {
void CloneObjectEvent::setDefaultValues(Project *project) {
this->setGfx(project->gfxDefines.keys().value(0, "0"));
this->setTargetID(1);
if (this->getMap()) this->setTargetMap(this->getMap()->name);
if (this->getMap()) this->setTargetMap(this->getMap()->name());
}
const QSet<QString> expectedCloneObjectFields = {
@ -445,7 +440,7 @@ void CloneObjectEvent::loadPixmap(Project *project) {
// Try to get the targeted object to clone
int eventIndex = this->targetID - 1;
Map *clonedMap = project->getMap(this->targetMap);
Event *clonedEvent = clonedMap ? clonedMap->events[Event::Group::Object].value(eventIndex, nullptr) : nullptr;
Event *clonedEvent = clonedMap ? clonedMap->getEvent(Event::Group::Object, eventIndex) : nullptr;
if (clonedEvent && clonedEvent->getEventType() == Event::Type::Object) {
// Get graphics data from cloned object
@ -502,7 +497,8 @@ OrderedJson::object WarpEvent::buildEventJson(Project *project) {
warpJson["x"] = this->getX();
warpJson["y"] = this->getY();
warpJson["elevation"] = this->getElevation();
warpJson["dest_map"] = project->mapNamesToMapConstants.value(this->getDestinationMap());
const QString mapName = this->getDestinationMap();
warpJson["dest_map"] = project->mapNamesToMapConstants.value(mapName, mapName);
warpJson["dest_warp_id"] = this->getDestinationWarpID();
this->addCustomValuesTo(&warpJson);
@ -516,17 +512,11 @@ bool WarpEvent::loadFromJson(QJsonObject json, Project *project) {
this->setElevation(ParseUtil::jsonToInt(json["elevation"]));
this->setDestinationWarpID(ParseUtil::jsonToQString(json["dest_warp_id"]));
// Ensure the warp destination map constant is valid before adding it to the warps.
const QString dynamicMapConstant = project->getDynamicMapDefineName();
QString mapConstant = ParseUtil::jsonToQString(json["dest_map"]);
if (project->mapConstantsToMapNames.contains(mapConstant)) {
this->setDestinationMap(project->mapConstantsToMapNames.value(mapConstant));
} else if (mapConstant == dynamicMapConstant) {
this->setDestinationMap(DYNAMIC_MAP_NAME);
} else {
logWarn(QString("Destination Map constant '%1' is invalid. Using default '%2'.").arg(mapConstant).arg(dynamicMapConstant));
this->setDestinationMap(DYNAMIC_MAP_NAME);
}
// Log a warning if "dest_map" isn't a known map ID, but don't overwrite user data.
const QString mapConstant = ParseUtil::jsonToQString(json["dest_map"]);
if (!project->mapConstantsToMapNames.contains(mapConstant))
logWarn(QString("Unknown Destination Map constant '%1'.").arg(mapConstant));
this->setDestinationMap(project->mapConstantsToMapNames.value(mapConstant, mapConstant));
this->readCustomValues(json);
@ -534,7 +524,7 @@ bool WarpEvent::loadFromJson(QJsonObject json, Project *project) {
}
void WarpEvent::setDefaultValues(Project *) {
if (this->getMap()) this->setDestinationMap(this->getMap()->name);
if (this->getMap()) this->setDestinationMap(this->getMap()->name());
this->setDestinationWarpID("0");
this->setElevation(0);
}
@ -949,16 +939,16 @@ OrderedJson::object HealLocationEvent::buildEventJson(Project *) {
void HealLocationEvent::setDefaultValues(Project *) {
this->setElevation(projectConfig.defaultElevation);
if (!this->getMap())
if (!this->map)
return;
bool respawnEnabled = projectConfig.healLocationRespawnDataEnabled;
const QString mapConstant = Map::mapConstantFromName(this->getMap()->name, false);
const QString prefix = projectConfig.getIdentifier(respawnEnabled ? ProjectIdentifier::define_spawn_prefix
: ProjectIdentifier::define_heal_locations_prefix);
this->setLocationName(mapConstant);
this->setIdName(prefix + mapConstant);
this->setLocationName(this->map->constantName());
this->setIdName(prefix + this->map->constantName());
if (respawnEnabled) {
this->setRespawnMap(this->getMap()->name);
this->setRespawnMap(this->map->name());
this->setRespawnNPC(1);
}
}

View File

@ -3,6 +3,8 @@
#include "events.h"
#include "map.h"
// TODO: Remove
HealLocation::HealLocation(QString id, QString map,
int i, int16_t x, int16_t y,
QString respawnMap, uint8_t respawnNPC) {

View File

@ -13,25 +13,51 @@
Map::Map(QObject *parent) : QObject(parent)
{
editHistory.setClean();
m_editHistory = new QUndoStack(this);
resetEvents();
m_header = new MapHeader(this);
connect(m_header, &MapHeader::modified, this, &Map::modified);
}
Map::Map(const Map &other, QObject *parent) : Map(parent) {
m_name = other.m_name;
m_constantName = other.m_constantName;
m_layoutId = other.m_layoutId;
m_sharedEventsMap = other.m_sharedEventsMap;
m_sharedScriptsMap = other.m_sharedScriptsMap;
m_customAttributes = other.m_customAttributes;
*m_header = *other.m_header;
m_layout = other.m_layout;
m_isPersistedToFile = false;
m_metatileLayerOrder = other.m_metatileLayerOrder;
m_metatileLayerOpacity = other.m_metatileLayerOpacity;
// Copy events
for (auto i = other.m_events.constBegin(); i != other.m_events.constEnd(); i++) {
QList<Event*> newEvents;
for (const auto &event : i.value()) {
auto newEvent = event->duplicate();
m_ownedEvents.insert(newEvent);
newEvents.append(newEvent);
}
m_events[i.key()] = newEvents;
}
// Duplicating the map connections is probably not desirable, so we skip them.
}
Map::~Map() {
qDeleteAll(ownedEvents);
ownedEvents.clear();
qDeleteAll(m_ownedEvents);
m_ownedEvents.clear();
deleteConnections();
}
void Map::setName(QString mapName) {
name = mapName;
constantName = mapConstantFromName(mapName);
scriptsLoaded = false;
}
// Note: Map does not take ownership of layout
void Map::setLayout(Layout *layout) {
this->layout = layout;
m_layout = layout;
if (layout) {
this->layoutId = layout->id;
m_layoutId = layout->id;
}
}
@ -42,29 +68,23 @@ QString Map::mapConstantFromName(QString mapName, bool includePrefix) {
const QString prefix = includePrefix ? projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix) : "";
QString withMapAndUppercase = prefix + nameWithUnderscores.toUpper();
static const QRegularExpression underscores("_+");
QString constantName = withMapAndUppercase.replace(underscores, "_");
// Handle special cases.
// SSTidal needs to be SS_TIDAL, rather than SSTIDAL
constantName = constantName.replace("SSTIDAL", "SS_TIDAL");
return constantName;
return withMapAndUppercase.replace(underscores, "_");
}
int Map::getWidth() {
return layout->getWidth();
int Map::getWidth() const {
return m_layout->getWidth();
}
int Map::getHeight() {
return layout->getHeight();
int Map::getHeight() const {
return m_layout->getHeight();
}
int Map::getBorderWidth() {
return layout->getBorderWidth();
int Map::getBorderWidth() const {
return m_layout->getBorderWidth();
}
int Map::getBorderHeight() {
return layout->getBorderHeight();
int Map::getBorderHeight() const {
return m_layout->getBorderHeight();
}
// Get the portion of the map that can be rendered when rendered as a map connection.
@ -106,7 +126,7 @@ QPixmap Map::renderConnection(const QString &direction, Layout * fromLayout) {
if (MapConnection::isDiving(direction))
fromLayout = nullptr;
QPixmap connectionPixmap = this->layout->render(true, fromLayout, bounds);
QPixmap connectionPixmap = m_layout->render(true, fromLayout, bounds);
return connectionPixmap.copy(bounds.x() * 16, bounds.y() * 16, bounds.width() * 16, bounds.height() * 16);
}
@ -114,18 +134,10 @@ void Map::openScript(QString label) {
emit openScriptRequested(label);
}
QList<Event *> Map::getAllEvents() const {
QList<Event *> all_events;
for (const auto &event_list : events) {
all_events << event_list;
}
return all_events;
}
QStringList Map::getScriptLabels(Event::Group group) {
if (!this->scriptsLoaded) {
this->scriptsFileLabels = ParseUtil::getGlobalScriptLabels(this->getScriptsFilePath());
this->scriptsLoaded = true;
if (!m_scriptsLoaded) {
m_scriptsFileLabels = ParseUtil::getGlobalScriptLabels(getScriptsFilePath());
m_scriptsLoaded = true;
}
QStringList scriptLabels;
@ -133,20 +145,20 @@ QStringList Map::getScriptLabels(Event::Group group) {
// Get script labels currently in-use by the map's events
if (group == Event::Group::None) {
ScriptTracker scriptTracker;
for (Event *event : this->getAllEvents()) {
for (const auto &event : getEvents()) {
event->accept(&scriptTracker);
}
scriptLabels = scriptTracker.getScripts();
} else {
ScriptTracker scriptTracker;
for (Event *event : events.value(group)) {
for (const auto &event : m_events.value(group)) {
event->accept(&scriptTracker);
}
scriptLabels = scriptTracker.getScripts();
}
// Add scripts from map's scripts file, and empty names.
scriptLabels.append(this->scriptsFileLabels);
scriptLabels.append(m_scriptsFileLabels);
scriptLabels.sort(Qt::CaseInsensitive);
scriptLabels.prepend("0x0");
scriptLabels.prepend("NULL");
@ -162,7 +174,7 @@ QString Map::getScriptsFilePath() const {
auto path = QDir::cleanPath(QString("%1/%2/%3/scripts")
.arg(projectConfig.projectDir)
.arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders))
.arg(this->name));
.arg(m_name));
auto extension = Project::getScriptFileExtension(usePoryscript);
if (usePoryscript && !QFile::exists(path + extension))
extension = Project::getScriptFileExtension(false);
@ -170,37 +182,77 @@ QString Map::getScriptsFilePath() const {
return path;
}
void Map::resetEvents() {
m_events[Event::Group::Object].clear();
m_events[Event::Group::Warp].clear();
m_events[Event::Group::Coord].clear();
m_events[Event::Group::Bg].clear();
m_events[Event::Group::Heal].clear();
}
QList<Event *> Map::getEvents(Event::Group group) const {
if (group == Event::Group::None) {
// Get all events
QList<Event *> all_events;
for (const auto &event_list : m_events) {
all_events << event_list;
}
return all_events;
}
return m_events[group];
}
Event* Map::getEvent(Event::Group group, int index) const {
return m_events[group].value(index, nullptr);
}
int Map::getNumEvents(Event::Group group) const {
if (group == Event::Group::None) {
// Total number of events
int numEvents = 0;
for (auto i = m_events.constBegin(); i != m_events.constEnd(); i++) {
numEvents += i.value().length();
}
return numEvents;
}
return m_events[group].length();
}
void Map::removeEvent(Event *event) {
for (Event::Group key : events.keys()) {
events[key].removeAll(event);
for (auto i = m_events.begin(); i != m_events.end(); i++) {
i.value().removeAll(event);
}
}
void Map::addEvent(Event *event) {
event->setMap(this);
events[event->getEventGroup()].append(event);
if (!ownedEvents.contains(event)) ownedEvents.append(event);
m_events[event->getEventGroup()].append(event);
if (!m_ownedEvents.contains(event)) m_ownedEvents.insert(event);
}
int Map::getIndexOfEvent(Event *event) const {
return m_events.value(event->getEventGroup()).indexOf(event);
}
void Map::deleteConnections() {
qDeleteAll(this->ownedConnections);
this->ownedConnections.clear();
this->connections.clear();
qDeleteAll(m_ownedConnections);
m_ownedConnections.clear();
m_connections.clear();
}
QList<MapConnection*> Map::getConnections() const {
return this->connections;
return m_connections;
}
void Map::addConnection(MapConnection *connection) {
if (!connection || this->connections.contains(connection))
if (!connection || m_connections.contains(connection))
return;
// Maps should only have one Dive/Emerge connection at a time.
// (Users can technically have more by editing their data manually, but we will only display one at a time)
// Any additional connections being added (this can happen via mirroring) are tracked for deleting but otherwise ignored.
if (MapConnection::isDiving(connection->direction())) {
for (auto i : this->connections) {
for (const auto &i : m_connections) {
if (i->direction() == connection->direction()) {
trackConnection(connection);
return;
@ -218,8 +270,8 @@ void Map::loadConnection(MapConnection *connection) {
if (!connection)
return;
if (!this->connections.contains(connection))
this->connections.append(connection);
if (!m_connections.contains(connection))
m_connections.append(connection);
trackConnection(connection);
}
@ -227,12 +279,12 @@ void Map::loadConnection(MapConnection *connection) {
void Map::trackConnection(MapConnection *connection) {
connection->setParentMap(this, false);
if (!this->ownedConnections.contains(connection)) {
this->ownedConnections.insert(connection);
if (!m_ownedConnections.contains(connection)) {
m_ownedConnections.insert(connection);
connect(connection, &MapConnection::parentMapChanged, [=](Map *, Map *after) {
if (after != this && after != nullptr) {
// MapConnection's parent has been reassigned, it's no longer our responsibility
this->ownedConnections.remove(connection);
m_ownedConnections.remove(connection);
QObject::disconnect(connection, &MapConnection::parentMapChanged, this, nullptr);
}
});
@ -241,23 +293,29 @@ void Map::trackConnection(MapConnection *connection) {
// We retain ownership of this MapConnection until it's assigned to a new parent map.
void Map::removeConnection(MapConnection *connection) {
if (!this->connections.removeOne(connection))
if (!m_connections.removeOne(connection))
return;
connection->setParentMap(nullptr, false);
modify();
emit connectionRemoved(connection);
}
void Map::commit(QUndoCommand *cmd) {
m_editHistory->push(cmd);
}
void Map::modify() {
emit modified();
}
void Map::clean() {
this->hasUnsavedDataChanges = false;
void Map::setClean() {
m_editHistory->setClean();
m_hasUnsavedDataChanges = false;
m_isPersistedToFile = true;
}
bool Map::hasUnsavedChanges() const {
return !editHistory.isClean() || this->layout->hasUnsavedChanges() || hasUnsavedDataChanges || !isPersistedToFile;
return !m_editHistory->isClean() || m_layout->hasUnsavedChanges() || m_hasUnsavedDataChanges || !m_isPersistedToFile;
}
void Map::pruneEditHistory() {
@ -271,11 +329,11 @@ void Map::pruneEditHistory() {
ID_MapConnectionAdd,
ID_MapConnectionRemove
};
for (int i = 0; i < this->editHistory.count(); i++) {
for (int i = 0; i < m_editHistory->count(); i++) {
// Qt really doesn't expect editing commands in the stack to be valid (fair).
// A better future design might be to have separate edit histories per map tab,
// and dumping the entire Connections tab history with QUndoStack::clear.
auto command = const_cast<QUndoCommand*>(this->editHistory.command(i));
auto command = const_cast<QUndoCommand*>(m_editHistory->command(i));
if (mapConnectionIds.contains(command->id()))
command->setObsolete(true);
}

View File

@ -65,7 +65,7 @@ QPixmap MapConnection::getPixmap() {
if (!map)
return QPixmap();
return map->renderConnection(m_direction, m_parentMap ? m_parentMap->layout : nullptr);
return map->renderConnection(m_direction, m_parentMap ? m_parentMap->layout() : nullptr);
}
void MapConnection::setParentMap(Map* map, bool mirror) {
@ -75,7 +75,7 @@ void MapConnection::setParentMap(Map* map, bool mirror) {
if (mirror) {
auto connection = findMirror();
if (connection)
connection->setTargetMapName(map ? map->name : QString(), false);
connection->setTargetMapName(map ? map->name() : QString(), false);
}
if (m_parentMap)
@ -91,7 +91,7 @@ void MapConnection::setParentMap(Map* map, bool mirror) {
}
QString MapConnection::parentMapName() const {
return m_parentMap ? m_parentMap->name : QString();
return m_parentMap ? m_parentMap->name() : QString();
}
void MapConnection::setTargetMapName(const QString &targetMapName, bool mirror) {

121
src/core/mapheader.cpp Normal file
View File

@ -0,0 +1,121 @@
#include "mapheader.h"
MapHeader::MapHeader(const MapHeader& other) : MapHeader() {
m_song = other.m_song;
m_location = other.m_location;
m_requiresFlash = other.m_requiresFlash;
m_weather = other.m_weather;
m_type = other.m_type;
m_showsLocationName = other.m_showsLocationName;
m_allowsRunning = other.m_allowsRunning;
m_allowsBiking = other.m_allowsBiking;
m_allowsEscaping = other.m_allowsEscaping;
m_floorNumber = other.m_floorNumber;
m_battleScene = other.m_battleScene;
}
MapHeader &MapHeader::operator=(const MapHeader &other) {
// We want to call each set function here to ensure any fieldChanged signals
// are sent as necessary. This does also mean the modified signal can be sent
// repeatedly (but for now at least that's not a big issue).
setSong(other.m_song);
setLocation(other.m_location);
setRequiresFlash(other.m_requiresFlash);
setWeather(other.m_weather);
setType(other.m_type);
setShowsLocationName(other.m_showsLocationName);
setAllowsRunning(other.m_allowsRunning);
setAllowsBiking(other.m_allowsBiking);
setAllowsEscaping(other.m_allowsEscaping);
setFloorNumber(other.m_floorNumber);
setBattleScene(other.m_battleScene);
return *this;
}
void MapHeader::setSong(const QString &song) {
if (m_song == song)
return;
m_song = song;
emit songChanged(m_song);
emit modified();
}
void MapHeader::setLocation(const QString &location) {
if (m_location == location)
return;
m_location = location;
emit locationChanged(m_location);
emit modified();
}
void MapHeader::setRequiresFlash(bool requiresFlash) {
if (m_requiresFlash == requiresFlash)
return;
m_requiresFlash = requiresFlash;
emit requiresFlashChanged(m_requiresFlash);
emit modified();
}
void MapHeader::setWeather(const QString &weather) {
if (m_weather == weather)
return;
m_weather = weather;
emit weatherChanged(m_weather);
emit modified();
}
void MapHeader::setType(const QString &type) {
if (m_type == type)
return;
m_type = type;
emit typeChanged(m_type);
emit modified();
}
void MapHeader::setShowsLocationName(bool showsLocationName) {
if (m_showsLocationName == showsLocationName)
return;
m_showsLocationName = showsLocationName;
emit showsLocationNameChanged(m_showsLocationName);
emit modified();
}
void MapHeader::setAllowsRunning(bool allowsRunning) {
if (m_allowsRunning == allowsRunning)
return;
m_allowsRunning = allowsRunning;
emit allowsRunningChanged(m_allowsRunning);
emit modified();
}
void MapHeader::setAllowsBiking(bool allowsBiking) {
if (m_allowsBiking == allowsBiking)
return;
m_allowsBiking = allowsBiking;
emit allowsBikingChanged(m_allowsBiking);
emit modified();
}
void MapHeader::setAllowsEscaping(bool allowsEscaping) {
if (m_allowsEscaping == allowsEscaping)
return;
m_allowsEscaping = allowsEscaping;
emit allowsEscapingChanged(m_allowsEscaping);
emit modified();
}
void MapHeader::setFloorNumber(int floorNumber) {
if (m_floorNumber == floorNumber)
return;
m_floorNumber = floorNumber;
emit floorNumberChanged(m_floorNumber);
emit modified();
}
void MapHeader::setBattleScene(const QString &battleScene) {
if (m_battleScene == battleScene)
return;
m_battleScene = battleScene;
emit battleSceneChanged(m_battleScene);
emit modified();
}

View File

@ -5,15 +5,17 @@
#include "scripting.h"
#include "imageproviders.h"
Layout::Layout(const Layout &other) : Layout() {
copyFrom(&other);
}
Layout *Layout::copy() {
Layout *Layout::copy() const {
Layout *layout = new Layout;
layout->copyFrom(this);
return layout;
}
void Layout::copyFrom(Layout *other) {
void Layout::copyFrom(const Layout *other) {
this->id = other->id;
this->name = other->name;
this->width = other->width;
@ -36,29 +38,20 @@ QString Layout::layoutConstantFromName(QString mapName) {
QString nameWithUnderscores = mapName.replace(caseChange, "\\1_\\2");
QString withMapAndUppercase = "LAYOUT_" + nameWithUnderscores.toUpper();
static const QRegularExpression underscores("_+");
QString constantName = withMapAndUppercase.replace(underscores, "_");
// Handle special cases.
// SSTidal should be SS_TIDAL, rather than SSTIDAL
constantName = constantName.replace("SSTIDAL", "SS_TIDAL");
return constantName;
return withMapAndUppercase.replace(underscores, "_");
}
int Layout::getWidth() {
return width;
}
int Layout::getHeight() {
return height;
}
int Layout::getBorderWidth() {
return border_width;
}
int Layout::getBorderHeight() {
return border_height;
Layout::Settings Layout::settings() const {
Layout::Settings settings;
settings.id = this->id;
settings.name = this->name;
settings.width = this->width;
settings.height = this->height;
settings.borderWidth = this->border_width;
settings.borderHeight = this->border_height;
settings.primaryTilesetLabel = this->tileset_primary_label;
settings.secondaryTilesetLabel = this->tileset_secondary_label;
return settings;
}
bool Layout::isWithinBounds(int x, int y) {

View File

@ -1,97 +0,0 @@
#include "mapparser.h"
#include "config.h"
#include "log.h"
#include "project.h"
MapParser::MapParser()
{
}
Layout *MapParser::parse(QString filepath, bool *error, Project *project)
{
QFile file(filepath);
if (!file.open(QIODevice::ReadOnly)) {
*error = true;
logError(QString("Could not open Advance Map 1.92 Map .map file '%1': ").arg(filepath) + file.errorString());
return nullptr;
}
QByteArray in = file.readAll();
file.close();
if (in.length() < 20 || in.length() % 2 != 0) {
*error = true;
logError(QString("Advance Map 1.92 Map .map file '%1' is an unexpected size.").arg(filepath));
return nullptr;
}
int borderWidth = static_cast<unsigned char>(in.at(16)); // 0 in RSE .map files
int borderHeight = static_cast<unsigned char>(in.at(17)); // 0 in RSE .map files
int numBorderTiles = borderWidth * borderHeight; // 0 if RSE
int mapDataOffset = 20 + (numBorderTiles * 2); // FRLG .map files store border metatile data after the header
int mapWidth = static_cast<unsigned char>(in.at(0)) |
(static_cast<unsigned char>(in.at(1)) << 8) |
(static_cast<unsigned char>(in.at(2)) << 16) |
(static_cast<unsigned char>(in.at(3)) << 24);
int mapHeight = static_cast<unsigned char>(in.at(4)) |
(static_cast<unsigned char>(in.at(5)) << 8) |
(static_cast<unsigned char>(in.at(6)) << 16) |
(static_cast<unsigned char>(in.at(7)) << 24);
int mapPrimaryTilesetNum = static_cast<unsigned char>(in.at(8)) |
(static_cast<unsigned char>(in.at(9)) << 8) |
(static_cast<unsigned char>(in.at(10)) << 16) |
(static_cast<unsigned char>(in.at(11)) << 24);
int mapSecondaryTilesetNum = static_cast<unsigned char>(in.at(12)) |
(static_cast<unsigned char>(in.at(13)) << 8) |
(static_cast<unsigned char>(in.at(14)) << 16) |
(static_cast<unsigned char>(in.at(15)) << 24);
int numMetatiles = mapWidth * mapHeight;
int expectedFileSize = 20 + (numBorderTiles * 2) + (numMetatiles * 2);
if (in.length() != expectedFileSize) {
*error = true;
logError(QString(".map file is an unexpected size. Expected %1 bytes, but it has %2 bytes.").arg(expectedFileSize).arg(in.length()));
return nullptr;
}
Blockdata blockdata;
for (int i = mapDataOffset; (i + 1) < in.length(); i += 2) {
uint16_t word = static_cast<uint16_t>((in[i] & 0xff) + ((in[i + 1] & 0xff) << 8));
blockdata.append(word);
}
Blockdata border;
if (numBorderTiles != 0) {
for (int i = 20; (i + 1) < mapDataOffset; i += 2) {
uint16_t word = static_cast<uint16_t>((in[i] & 0xff) + ((in[i + 1] & 0xff) << 8));
border.append(word);
}
}
Layout *mapLayout = new Layout();
mapLayout->width = mapWidth;
mapLayout->height = mapHeight;
mapLayout->border_width = (borderWidth == 0) ? DEFAULT_BORDER_WIDTH : borderWidth;
mapLayout->border_height = (borderHeight == 0) ? DEFAULT_BORDER_HEIGHT : borderHeight;
QList<QString> tilesets = project->tilesetLabelsOrdered;
if (mapPrimaryTilesetNum > tilesets.size())
mapLayout->tileset_primary_label = tilesets.at(0);
else
mapLayout->tileset_primary_label = tilesets.at(mapPrimaryTilesetNum);
if (mapSecondaryTilesetNum > tilesets.size())
mapLayout->tileset_secondary_label = tilesets.at(1);
else
mapLayout->tileset_secondary_label = tilesets.at(mapSecondaryTilesetNum);
mapLayout->blockdata = blockdata;
if (!border.isEmpty()) {
mapLayout->border = border;
}
return mapLayout;
}

View File

@ -1,99 +0,0 @@
#include "metatileparser.h"
#include "config.h"
#include "log.h"
#include "project.h"
#include <QString>
QList<Metatile*> MetatileParser::parse(QString filepath, bool *error, bool primaryTileset)
{
QFile file(filepath);
if (!file.open(QIODevice::ReadOnly)) {
*error = true;
logError(QString("Could not open Advance Map 1.92 Metatile .bvd file '%1': ").arg(filepath) + file.errorString());
return { };
}
QByteArray in = file.readAll();
file.close();
if (in.length() < 9 || in.length() % 2 != 0) {
*error = true;
logError(QString("Advance Map 1.92 Metatile .bvd file '%1' is an unexpected size.").arg(filepath));
return { };
}
int projIdOffset = in.length() - 4;
int metatileSize = 16;
BaseGameVersion version;
if (in.at(projIdOffset + 0) == 'R'
&& in.at(projIdOffset + 1) == 'S'
&& in.at(projIdOffset + 2) == 'E'
&& in.at(projIdOffset + 3) == ' ') {
// ruby and emerald are handled equally here.
version = BaseGameVersion::pokeemerald;
} else if (in.at(projIdOffset + 0) == 'F'
&& in.at(projIdOffset + 1) == 'R'
&& in.at(projIdOffset + 2) == 'L'
&& in.at(projIdOffset + 3) == 'G') {
version = BaseGameVersion::pokefirered;
} else {
*error = true;
logError(QString("Detected unsupported game type from .bvd file. Last 4 bytes of file must be 'RSE ' or 'FRLG'."));
return { };
}
int attrSize = Metatile::getDefaultAttributesSize(version);
int maxMetatiles = primaryTileset ? Project::getNumMetatilesPrimary() : Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary();
int numMetatiles = static_cast<unsigned char>(in.at(0)) |
(static_cast<unsigned char>(in.at(1)) << 8) |
(static_cast<unsigned char>(in.at(2)) << 16) |
(static_cast<unsigned char>(in.at(3)) << 24);
if (numMetatiles > maxMetatiles) {
*error = true;
logError(QString(".bvd file contains data for %1 metatiles, but the maximum number of metatiles is %2.").arg(numMetatiles).arg(maxMetatiles));
return { };
}
if (numMetatiles < 1) {
*error = true;
logError(QString(".bvd file contains no data for metatiles."));
return { };
}
int expectedFileSize = 4 + (metatileSize * numMetatiles) + (attrSize * numMetatiles) + 4;
if (in.length() != expectedFileSize) {
*error = true;
logError(QString(".bvd file is an unexpected size. Expected %1 bytes, but it has %2 bytes.").arg(expectedFileSize).arg(in.length()));
return { };
}
QList<Metatile*> metatiles;
for (int i = 0; i < numMetatiles; i++) {
Metatile *metatile = new Metatile();
QList<Tile> tiles;
for (int j = 0; j < 8; j++) {
int metatileOffset = 4 + i * metatileSize + j * 2;
Tile tile(static_cast<uint16_t>(
static_cast<unsigned char>(in.at(metatileOffset)) |
(static_cast<unsigned char>(in.at(metatileOffset + 1)) << 8)));
tiles.append(tile);
}
// AdvanceMap .bvd files only contain 8 tiles of data per metatile.
// If the user has triple-layer metatiles enabled we need to fill the remaining 4 tiles ourselves.
if (projectConfig.tripleLayerMetatilesEnabled) {
Tile tile = Tile();
for (int j = 0; j < 4; j++)
tiles.append(tile);
}
int attrOffset = 4 + (numMetatiles * metatileSize) + (i * attrSize);
uint32_t attributes = 0;
for (int j = 0; j < attrSize; j++)
attributes |= static_cast<unsigned char>(in.at(attrOffset + j)) << (8 * j);
metatile->setAttributes(attributes, version);
metatile->tiles = tiles;
metatiles.append(metatile);
}
return metatiles;
}

View File

@ -1,4 +1,5 @@
#include "paletteutil.h"
#include "advancemapparser.h"
#include "log.h"
#include <QFileInfo>
#include <QRegularExpression>
@ -6,7 +7,6 @@
QList<QRgb> parsePal(QString filepath, bool *error);
QList<QRgb> parseJASC(QString filepath, bool *error);
QList<QRgb> parseAdvanceMapPal(QString filepath, bool *error);
QList<QRgb> parseAdobeColorTable(QString filepath, bool *error);
QList<QRgb> parseTileLayerPro(QString filepath, bool *error);
QList<QRgb> parseAdvancePaletteEditor(QString filepath, bool *error);
@ -81,7 +81,7 @@ QList<QRgb> parsePal(QString filepath, bool *error) {
return parseJASC(filepath, error);
} else {
file.close();
return parseAdvanceMapPal(filepath, error);
return AdvanceMapParser::parsePalette(filepath, error);
}
}
@ -152,38 +152,6 @@ QList<QRgb> parseJASC(QString filepath, bool *error) {
return palette;
}
QList<QRgb> parseAdvanceMapPal(QString filepath, bool *error) {
QFile file(filepath);
if (!file.open(QIODevice::ReadOnly)) {
*error = true;
logError(QString("Could not open Advance Map 1.92 palette file '%1': ").arg(filepath) + file.errorString());
return QList<QRgb>();
}
QByteArray in = file.readAll();
file.close();
if (in.length() % 4 != 0) {
*error = true;
logError(QString("Advance Map 1.92 palette file '%1' had an unexpected format. File's length must be a multiple of 4, but the length is %2.").arg(filepath).arg(in.length()));
return QList<QRgb>();
}
QList<QRgb> palette;
int i = 0;
while (i < in.length()) {
unsigned char red = static_cast<unsigned char>(in.at(i));
unsigned char green = static_cast<unsigned char>(in.at(i + 1));
unsigned char blue = static_cast<unsigned char>(in.at(i + 2));
palette.append(qRgb(clampColorValue(red),
clampColorValue(green),
clampColorValue(blue)));
i += 4;
}
return palette;
}
QList<QRgb> parseAdobeColorTable(QString filepath, bool *error) {
QFile file(filepath);
if (!file.open(QIODevice::ReadOnly)) {

View File

@ -593,13 +593,13 @@ bool ParseUtil::gameStringToBool(QString gameString, bool * ok) {
return gameStringToInt(gameString, ok) != 0;
}
QMap<QString, QHash<QString, QString>> ParseUtil::readCStructs(const QString &filename, const QString &label, const QHash<int, QString> memberMap) {
tsl::ordered_map<QString, QHash<QString, QString>> ParseUtil::readCStructs(const QString &filename, const QString &label, const QHash<int, QString> &memberMap) {
QString filePath = this->root + "/" + filename;
auto cParser = fex::Parser();
auto tokens = fex::Lexer().LexFile(filePath);
auto structs = cParser.ParseTopLevelObjects(tokens);
QMap<QString, QHash<QString, QString>> structMaps;
for (auto it = structs.begin(); it != structs.end(); it++) {
auto topLevelObjects = cParser.ParseTopLevelObjects(tokens);
tsl::ordered_map<QString, QHash<QString, QString>> structs;
for (auto it = topLevelObjects.begin(); it != topLevelObjects.end(); it++) {
QString structLabel = QString::fromStdString(it->first);
if (structLabel.isEmpty()) continue;
if (!label.isEmpty() && label != structLabel) continue; // Speed up parsing if only looking for a particular symbol
@ -617,9 +617,9 @@ QMap<QString, QHash<QString, QString>> ParseUtil::readCStructs(const QString &fi
}
i++;
}
structMaps.insert(structLabel, values);
structs[structLabel] = values;
}
return structMaps;
return structs;
}
QList<QStringList> ParseUtil::getLabelMacros(const QList<QStringList> &list, const QString &label) {

View File

@ -397,10 +397,15 @@ void RegionMap::saveLayout() {
case LayoutFormat::Binary:
{
QByteArray data;
int defaultValue = this->project->mapSectionIdNames.indexOf(this->default_map_section);
for (int m = 0; m < this->layout_height; m++) {
for (int n = 0; n < this->layout_width; n++) {
int i = n + this->layout_width * m;
data.append(this->project->mapSectionIdNames.indexOf(this->layouts["main"][i].map_section));
int mapSectionValue = this->project->mapSectionIdNames.indexOf(this->layouts["main"][i].map_section);
if (mapSectionValue < 0){
mapSectionValue = defaultValue;
}
data.append(mapSectionValue);
}
}
QFile bfile(fullPath(this->layout_path));

View File

@ -3,6 +3,8 @@
#include "project.h"
#include "log.h"
#include "config.h"
#include "imageproviders.h"
#include "validator.h"
#include <QPainter>
#include <QImage>
@ -23,7 +25,7 @@ Tileset::Tileset(const Tileset &other)
metatileLabels(other.metatileLabels),
palettes(other.palettes),
palettePreviews(other.palettePreviews),
hasUnsavedTilesImage(false)
m_hasUnsavedTilesImage(other.m_hasUnsavedTilesImage)
{
for (auto tile : other.tiles) {
tiles.append(tile.copy());
@ -179,10 +181,8 @@ bool Tileset::setMetatileLabel(int metatileId, QString label, Tileset *primaryTi
if (!tileset)
return false;
static const QRegularExpression expression("[_A-Za-z0-9]*$");
QRegularExpressionValidator validator(expression);
int pos = 0;
if (validator.validate(label, pos) != QValidator::Acceptable)
IdentifierValidator validator;
if (!validator.isValid(label))
return false;
tileset->metatileLabels[metatileId] = label;
@ -397,3 +397,184 @@ QHash<int, QString> Tileset::getHeaderMemberMap(bool usingAsm)
map.insert(metatileAttrPosition, "metatileAttributes");
return map;
}
void Tileset::loadMetatiles() {
clearMetatiles();
QFile metatiles_file(this->metatiles_path);
if (!metatiles_file.open(QIODevice::ReadOnly)) {
logError(QString("Could not open '%1' for reading.").arg(this->metatiles_path));
return;
}
QByteArray data = metatiles_file.readAll();
int tilesPerMetatile = projectConfig.getNumTilesInMetatile();
int bytesPerMetatile = 2 * tilesPerMetatile;
int num_metatiles = data.length() / bytesPerMetatile;
for (int i = 0; i < num_metatiles; i++) {
auto metatile = new Metatile;
int index = i * bytesPerMetatile;
for (int j = 0; j < tilesPerMetatile; j++) {
uint16_t tileRaw = static_cast<unsigned char>(data[index++]);
tileRaw |= static_cast<unsigned char>(data[index++]) << 8;
metatile->tiles.append(Tile(tileRaw));
}
m_metatiles.append(metatile);
}
}
void Tileset::saveMetatiles() {
QFile metatiles_file(this->metatiles_path);
if (!metatiles_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
logError(QString("Could not open '%1' for writing.").arg(this->metatiles_path));
return;
}
QByteArray data;
int numTiles = projectConfig.getNumTilesInMetatile();
for (const auto &metatile : m_metatiles) {
for (int i = 0; i < numTiles; i++) {
uint16_t tile = metatile->tiles.at(i).rawValue();
data.append(static_cast<char>(tile));
data.append(static_cast<char>(tile >> 8));
}
}
metatiles_file.write(data);
}
void Tileset::loadMetatileAttributes() {
QFile attrs_file(this->metatile_attrs_path);
if (!attrs_file.open(QIODevice::ReadOnly)) {
logError(QString("Could not open '%1' for reading.").arg(this->metatile_attrs_path));
return;
}
QByteArray data = attrs_file.readAll();
int attrSize = projectConfig.metatileAttributesSize;
int numMetatiles = m_metatiles.length();
int numMetatileAttrs = data.length() / attrSize;
if (numMetatiles != numMetatileAttrs) {
logWarn(QString("Metatile count %1 does not match metatile attribute count %2 in %3").arg(numMetatiles).arg(numMetatileAttrs).arg(this->name));
}
for (int i = 0; i < qMin(numMetatiles, numMetatileAttrs); i++) {
uint32_t attributes = 0;
for (int j = 0; j < attrSize; j++)
attributes |= static_cast<unsigned char>(data.at(i * attrSize + j)) << (8 * j);
m_metatiles.at(i)->setAttributes(attributes);
}
}
void Tileset::saveMetatileAttributes() {
QFile attrs_file(this->metatile_attrs_path);
if (!attrs_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
logError(QString("Could not open '%1' for writing.").arg(this->metatile_attrs_path));
return;
}
QByteArray data;
for (const auto &metatile : m_metatiles) {
uint32_t attributes = metatile->getAttributes();
for (int i = 0; i < projectConfig.metatileAttributesSize; i++)
data.append(static_cast<char>(attributes >> (8 * i)));
}
attrs_file.write(data);
}
void Tileset::loadTilesImage(QImage *importedImage) {
QImage image;
if (importedImage) {
image = *importedImage;
m_hasUnsavedTilesImage = true;
} else if (QFile::exists(this->tilesImagePath)) {
// No image provided, load from file path.
image = QImage(this->tilesImagePath).convertToFormat(QImage::Format_Indexed8, Qt::ThresholdDither);
} else {
// Use default image
image = QImage(8, 8, QImage::Format_Indexed8);
}
// Validate image contains 16 colors.
int colorCount = image.colorCount();
if (colorCount > 16) {
flattenTo4bppImage(&image);
} else if (colorCount < 16) {
QVector<QRgb> colorTable = image.colorTable();
for (int i = colorTable.length(); i < 16; i++) {
colorTable.append(Qt::black);
}
image.setColorTable(colorTable);
}
QList<QImage> tiles;
int w = 8;
int h = 8;
for (int y = 0; y < image.height(); y += h)
for (int x = 0; x < image.width(); x += w) {
QImage tile = image.copy(x, y, w, h);
tiles.append(tile);
}
this->tilesImage = image;
this->tiles = tiles;
}
void Tileset::saveTilesImage() {
// Only write the tiles image if it was changed.
// Porymap will only ever change an existing tiles image by importing a new one.
if (!m_hasUnsavedTilesImage)
return;
if (!this->tilesImage.save(this->tilesImagePath, "PNG")) {
logError(QString("Failed to save tiles image '%1'").arg(this->tilesImagePath));
return;
}
m_hasUnsavedTilesImage = false;
}
void Tileset::loadPalettes() {
this->palettes.clear();
this->palettePreviews.clear();
for (int i = 0; i < Project::getNumPalettesTotal(); i++) {
QList<QRgb> palette;
QString path = this->palettePaths.value(i);
if (!path.isEmpty()) {
bool error = false;
palette = PaletteUtil::parse(path, &error);
if (error) palette.clear();
}
if (palette.isEmpty()) {
// Either the palette failed to load, or no palette exists.
// We expect tilesets to have a certain number of palettes,
// so fill this palette with dummy colors.
for (int j = 0; j < 16; j++) {
palette.append(qRgb(j * 16, j * 16, j * 16));
}
}
this->palettes.append(palette);
this->palettePreviews.append(palette);
}
}
void Tileset::savePalettes() {
int numPalettes = qMin(this->palettePaths.length(), this->palettes.length());
for (int i = 0; i < numPalettes; i++) {
PaletteUtil::writeJASC(this->palettePaths.at(i), this->palettes.at(i).toVector(), 0, 16);
}
}
void Tileset::load() {
loadMetatiles();
loadMetatileAttributes();
loadTilesImage();
loadPalettes();
}
// Because metatile labels are global (and handled by the project) we don't save them here.
void Tileset::save() {
saveMetatiles();
saveMetatileAttributes();
saveTilesImage();
savePalettes();
}

30
src/core/validator.cpp Normal file
View File

@ -0,0 +1,30 @@
#include "validator.h"
// Identifiers must only contain word characters, and cannot start with a digit.
const QRegularExpression IdentifierValidator::re_identifier = QRegularExpression("[A-Za-z_]+[\\w]*");
bool PrefixValidator::missingPrefix(const QString &input) const {
return !m_prefix.isEmpty() && !input.startsWith(m_prefix);
}
QValidator::State PrefixValidator::validate(QString &input, int &pos) const {
auto state = QRegularExpressionValidator::validate(input, pos);
if (state == QValidator::Acceptable) {
// This input could be valid. If there's a prefix we should require it now.
if (missingPrefix(input))
state = QValidator::Intermediate;
}
return state;
}
void PrefixValidator::fixup(QString &input) const {
QRegularExpressionValidator::fixup(input);
if (missingPrefix(input))
input.prepend(m_prefix);
}
bool PrefixValidator::isValid(QString &input) const {
int pos = 0;
return validate(input, pos) == QValidator::Acceptable;
}

View File

@ -11,6 +11,7 @@
#include "config.h"
#include "scripting.h"
#include "customattributestable.h"
#include "validator.h"
#include <QCheckBox>
#include <QPainter>
#include <QMouseEvent>
@ -152,7 +153,7 @@ void Editor::setEditorView() {
map_item->setEditsEnabled(false);
case EditMode::Events:
if (this->map) {
this->editGroup.setActiveStack(&this->map->editHistory);
this->editGroup.setActiveStack(this->map->editHistory());
}
break;
case EditMode::Header:
@ -240,23 +241,23 @@ void Editor::displayWildMonTables() {
clearWildMonTables();
// Don't try to read encounter data if it doesn't exist on disk for this map.
if (!project->wildMonData.contains(map->constantName)) {
if (!project->wildMonData.contains(map->constantName())) {
return;
}
QComboBox *labelCombo = ui->comboBox_EncounterGroupLabel;
for (auto groupPair : project->wildMonData[map->constantName])
for (auto groupPair : project->wildMonData[map->constantName()])
labelCombo->addItem(groupPair.first);
labelCombo->setCurrentText(labelCombo->itemText(0));
QStackedWidget *stack = ui->stackedWidget_WildMons;
int labelIndex = 0;
for (auto labelPair : project->wildMonData[map->constantName]) {
for (auto labelPair : project->wildMonData[map->constantName()]) {
QString label = labelPair.first;
WildPokemonHeader header = project->wildMonData[map->constantName][label];
WildPokemonHeader header = project->wildMonData[map->constantName()][label];
MonTabWidget *tabWidget = new MonTabWidget(this);
stack->insertWidget(labelIndex++, tabWidget);
@ -267,7 +268,7 @@ void Editor::displayWildMonTables() {
tabWidget->clearTableAt(tabIndex);
if (project->wildMonData.contains(map->constantName) && header.wildMons[fieldName].active) {
if (project->wildMonData.contains(map->constantName()) && header.wildMons[fieldName].active) {
tabWidget->populateTab(tabIndex, header.wildMons[fieldName]);
} else {
tabWidget->setTabActive(tabIndex, false);
@ -299,9 +300,7 @@ void Editor::addNewWildMonGroup(QWidget *window) {
QLineEdit *lineEdit = new QLineEdit();
lineEdit->setClearButtonEnabled(true);
form.addRow(new QLabel("Group Base Label:"), lineEdit);
static const QRegularExpression re_validChars("[_A-Za-z0-9]*");
QRegularExpressionValidator *validator = new QRegularExpressionValidator(re_validChars);
lineEdit->setValidator(validator);
lineEdit->setValidator(new IdentifierValidator(lineEdit));
connect(lineEdit, &QLineEdit::textChanged, [this, &lineEdit, &buttonBox](QString text){
if (this->project->encounterGroupLabels.contains(text)) {
lineEdit->setStyleSheet("QLineEdit { background-color: rgba(255, 0, 0, 25%) }");
@ -312,7 +311,7 @@ void Editor::addNewWildMonGroup(QWidget *window) {
}
});
// Give a default value to the label.
lineEdit->setText(QString("g%1%2").arg(map->name).arg(stack->count()));
lineEdit->setText(QString("g%1%2").arg(map->name()).arg(stack->count()));
// Fields [x] copy from existing
QLabel *fieldsLabel = new QLabel("Fields:");
@ -415,9 +414,9 @@ void Editor::deleteWildMonGroup() {
msgBox.exec();
if (msgBox.clickedButton() == deleteButton) {
auto it = project->wildMonData.find(map->constantName);
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));
logError(QString("Failed to find data for map %1. Unable to delete").arg(map->constantName()));
return;
}
@ -698,7 +697,7 @@ void Editor::saveEncounterTabData() {
if (!stack->count()) return;
tsl::ordered_map<QString, WildPokemonHeader> &encounterMap = project->wildMonData[map->constantName];
tsl::ordered_map<QString, WildPokemonHeader> &encounterMap = project->wildMonData[map->constantName()];
for (int groupIndex = 0; groupIndex < stack->count(); groupIndex++) {
MonTabWidget *tabWidget = static_cast<MonTabWidget *>(stack->widget(groupIndex));
@ -863,13 +862,13 @@ void Editor::addConnection(MapConnection *connection) {
// It's possible this is a Dive/Emerge connection, but that's ok (no selection will occur).
connection_to_select = connection;
this->map->editHistory.push(new MapConnectionAdd(this->map, connection));
this->map->commit(new MapConnectionAdd(this->map, connection));
}
void Editor::removeConnection(MapConnection *connection) {
if (!connection)
return;
this->map->editHistory.push(new MapConnectionRemove(this->map, connection));
this->map->commit(new MapConnectionRemove(this->map, connection));
}
void Editor::removeConnectionPixmap(MapConnection *connection) {
@ -986,7 +985,7 @@ void Editor::setDivingMapName(QString mapName, QString direction) {
if (mapName.isEmpty()) {
removeConnection(connection);
} else {
map->editHistory.push(new MapConnectionChangeMap(connection, mapName));
map->commit(new MapConnectionChangeMap(connection, mapName));
}
} else if (!mapName.isEmpty()) {
// Create new connection
@ -1167,7 +1166,7 @@ void Editor::setCursorRectVisible(bool visible) {
void Editor::onHoveredMapMetatileChanged(const QPoint &pos) {
int x = pos.x();
int y = pos.y();
if (!layout->isWithinBounds(x, y))
if (!layout || !layout->isWithinBounds(x, y))
return;
this->updateCursorRectPos(x, y);
@ -1199,7 +1198,7 @@ void Editor::onHoveredMapMetatileCleared() {
}
void Editor::onHoveredMapMovementPermissionChanged(int x, int y) {
if (!layout->isWithinBounds(x, y))
if (!layout || !layout->isWithinBounds(x, y))
return;
this->updateCursorRectPos(x, y);
@ -1243,10 +1242,10 @@ QString Editor::getMovementPermissionText(uint16_t collision, uint16_t elevation
void Editor::unsetMap() {
// disconnect previous map's signals so they are not firing
// multiple times if set again in the future
if (map) {
map->pruneEditHistory();
map->disconnect(this);
for (auto connection : map->getConnections())
if (this->map) {
this->map->pruneEditHistory();
this->map->disconnect(this);
for (const auto &connection : this->map->getConnections())
disconnectMapConnection(connection);
}
clearMapConnections();
@ -1259,19 +1258,18 @@ bool Editor::setMap(QString map_name) {
return false;
}
unsetMap();
Map *loadedMap = project->loadMap(map_name);
if (!loadedMap) {
return false;
}
unsetMap();
this->map = loadedMap;
setLayout(map->layout->id);
setLayout(map->layout()->id);
editGroup.addStack(&map->editHistory);
editGroup.setActiveStack(&map->editHistory);
editGroup.addStack(map->editHistory());
editGroup.setActiveStack(map->editHistory());
selected_events->clear();
if (!displayMap()) {
@ -1292,13 +1290,17 @@ bool Editor::setLayout(QString layoutId) {
return false;
}
this->layout = this->project->loadLayout(layoutId);
Layout *loadedLayout = this->project->loadLayout(layoutId);
if (!loadedLayout) {
return false;
}
this->layout = loadedLayout;
if (!displayLayout()) {
return false;
}
editGroup.addStack(&layout->editHistory);
editGroup.addStack(&this->layout->editHistory);
map_ruler->setMapDimensions(QSize(this->layout->getWidth(), this->layout->getHeight()));
connect(this->layout, &Layout::layoutDimensionsChanged, map_ruler, &MapRuler::setMapDimensions);
@ -1469,7 +1471,7 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i
}
selection_origin = QPoint(pos.x(), pos.y());
map->editHistory.push(new EventShift(selectedEvents, xDelta, yDelta, actionId));
map->commit(new EventShift(selectedEvents, xDelta, yDelta, actionId));
}
}
}
@ -1784,8 +1786,7 @@ void Editor::displayMapEvents() {
events_group = new QGraphicsItemGroup;
scene->addItem(events_group);
QList<Event *> events = map->getAllEvents();
for (Event *event : events) {
for (const auto &event : map->getEvents()) {
project->setEventPixmap(event);
addMapEvent(event);
}
@ -2032,14 +2033,14 @@ void Editor::updateBorderVisibility() {
// When connecting a map to itself we don't bother to re-render the map connections in real-time,
// i.e. if the user paints a new metatile on the map this isn't immediately reflected in the connection.
// We're rendering them now, so we take the opportunity to do a full re-render for self-connections.
bool fullRender = (this->map && item->connection && this->map->name == item->connection->targetMapName());
bool fullRender = (this->map && item->connection && this->map->name() == item->connection->targetMapName());
item->render(fullRender);
}
}
void Editor::updateCustomMapHeaderValues(QTableWidget *table)
{
map->customHeaders = CustomAttributesTable::getAttributes(table);
map->setCustomAttributes(CustomAttributesTable::getAttributes(table));
map->modify();
}
@ -2080,13 +2081,13 @@ void Editor::redrawObject(DraggablePixmapItem *item) {
void Editor::updateWarpEventWarning(Event *event) {
if (porymapConfig.warpBehaviorWarningDisabled)
return;
if (!project || !map || !map->layout || !event || event->getEventType() != Event::Type::Warp)
if (!project || !map || !map->layout() || !event || event->getEventType() != Event::Type::Warp)
return;
Block block;
Metatile * metatile = nullptr;
WarpEvent * warpEvent = static_cast<WarpEvent*>(event);
if (map->layout->getBlock(warpEvent->getX(), warpEvent->getY(), &block)) {
metatile = Tileset::getMetatile(block.metatileId(), map->layout->tileset_primary, map->layout->tileset_secondary);
if (map->layout()->getBlock(warpEvent->getX(), warpEvent->getY(), &block)) {
metatile = Tileset::getMetatile(block.metatileId(), map->layout()->tileset_primary, map->layout()->tileset_secondary);
}
// metatile may be null if the warp is in the map border. Display the warning in this case
bool validWarpBehavior = metatile && projectConfig.warpBehaviors.contains(metatile->behavior());
@ -2144,10 +2145,7 @@ void Editor::selectMapEvent(DraggablePixmapItem *object, bool toggle) {
void Editor::selectedEventIndexChanged(int index, Event::Group eventGroup) {
int event_offs = Event::getIndexOffset(eventGroup);
index = index - event_offs;
Event *event = nullptr;
if (index < this->map->events.value(eventGroup).length()) {
event = this->map->events.value(eventGroup).at(index);
}
Event *event = this->map->getEvent(eventGroup, index);
DraggablePixmapItem *selectedEvent = nullptr;
for (QGraphicsItem *child : this->events_group->childItems()) {
DraggablePixmapItem *item = static_cast<DraggablePixmapItem *>(child);
@ -2189,7 +2187,7 @@ void Editor::duplicateSelectedEvents() {
duplicate->setY(duplicate->getY() + 1);
selectedEvents.append(duplicate);
}
map->editHistory.push(new EventDuplicate(this, map, selectedEvents));
map->commit(new EventDuplicate(this, map, selectedEvents));
}
DraggablePixmapItem *Editor::addNewEvent(Event::Type type) {
@ -2209,7 +2207,7 @@ DraggablePixmapItem *Editor::addNewEvent(Event::Type type) {
((HealLocationEvent *)event)->setIndex(project->healLocations.length());
}
map->editHistory.push(new EventCreate(this, map, event));
map->commit(new EventCreate(this, map, event));
return event->getPixmapItem();
}
@ -2217,7 +2215,7 @@ DraggablePixmapItem *Editor::addNewEvent(Event::Type type) {
bool Editor::eventLimitReached(Event::Type event_type) {
if (project && map) {
if (Event::typeToGroup(event_type) == Event::Group::Object)
return map->events.value(Event::Group::Object).length() >= project->getMaxObjectEvents();
return map->getNumEvents(Event::Group::Object) >= project->getMaxObjectEvents();
}
return false;
}
@ -2246,14 +2244,12 @@ void Editor::deleteSelectedEvents() {
// If deleting multiple events, just let editor work out next selected.
if (numDeleted == 1) {
Event::Group event_group = selectedEvents[0]->getEventGroup();
int index = this->map->events.value(event_group).indexOf(selectedEvents[0]);
if (index != this->map->events.value(event_group).size() - 1)
int index = this->map->getIndexOfEvent(selectedEvents[0]);
if (index != this->map->getNumEvents(event_group) - 1)
index++;
else
index--;
Event *event = nullptr;
if (index >= 0)
event = this->map->events.value(event_group).at(index);
Event *event = this->map->getEvent(event_group, index);
for (QGraphicsItem *child : this->events_group->childItems()) {
DraggablePixmapItem *event_item = static_cast<DraggablePixmapItem *>(child);
if (event_item->event == event) {
@ -2262,7 +2258,7 @@ void Editor::deleteSelectedEvents() {
}
}
}
this->map->editHistory.push(new EventDelete(this, this->map, selectedEvents, nextSelectedEvent ? nextSelectedEvent->event : nullptr));
this->map->commit(new EventDelete(this, this->map, selectedEvents, nextSelectedEvent ? nextSelectedEvent->event : nullptr));
}
}

View File

@ -0,0 +1,143 @@
/*
Elypson/qt-collapsible-section
(c) 2016 Michael A. Voelkel - michael.alexander.voelkel@gmail.com
This file is part of Elypson/qt-collapsible section.
Elypson/qt-collapsible-section is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Elypson/qt-collapsible-section is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU General Public License
along with Elypson/qt-collapsible-section. If not, see <http://www.gnu.org/licenses/>.
PORYMAP NOTE: Modified to support having the section expanded by default, to stop the contents
squashing during the collapse animation, and to add some guard rails against crashes.
*/
#include "collapsiblesection.h"
CollapsibleSection::CollapsibleSection(const QString& title, const bool expanded, const int animationDuration, QWidget* parent)
: QWidget(parent), animationDuration(animationDuration), expanded(expanded)
{
toggleButton = new QToolButton(this);
headerLine = new QFrame(this);
toggleAnimation = new QParallelAnimationGroup(this);
contentArea = new QScrollArea(this);
mainLayout = new QGridLayout(this);
toggleButton->setStyleSheet("QToolButton {border: none;}");
toggleButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
toggleButton->setText(title);
toggleButton->setCheckable(true);
updateToggleButton();
headerLine->setFrameShape(QFrame::HLine);
headerLine->setFrameShadow(QFrame::Sunken);
headerLine->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
contentArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
contentArea->setMaximumHeight(0);
contentArea->setMinimumHeight(0);
sectionAnimations.insert(new QPropertyAnimation(this, "minimumHeight"));
sectionAnimations.insert(new QPropertyAnimation(this, "maximumHeight"));
for (const auto &anim : sectionAnimations)
toggleAnimation->addAnimation(anim);
contentAnimation = new QPropertyAnimation(contentArea, "maximumHeight");
toggleAnimation->addAnimation(contentAnimation);
mainLayout->setVerticalSpacing(0);
mainLayout->setContentsMargins(0, 0, 0, 0);
int row = 0;
mainLayout->addWidget(toggleButton, row, 0, 1, 1, Qt::AlignLeft);
mainLayout->addWidget(headerLine, row++, 2, 1, 1);
mainLayout->addWidget(contentArea, row, 0, 1, 3);
setLayout(mainLayout);
connect(toggleButton, &QToolButton::toggled, this, &CollapsibleSection::toggle);
}
void CollapsibleSection::updateToggleButton()
{
toggleButton->setChecked(this->expanded);
toggleButton->setArrowType(this->expanded ? Qt::ArrowType::DownArrow : Qt::ArrowType::RightArrow);
}
void CollapsibleSection::toggle(bool expand)
{
if (toggleAnimation->state() != QAbstractAnimation::Stopped)
return;
if (this->expanded == expand)
return;
this->expanded = expand;
updateToggleButton();
if (expand) {
// Opening animation. Set the contents to their maximum size immediately,
// and they will be revealed slowly by the section animation.
int contentHeight = getContentHeight();
contentArea->setMinimumHeight(contentHeight);
contentArea->setMaximumHeight(contentHeight);
toggleAnimation->setDirection(QAbstractAnimation::Forward);
} else {
// Closing animation. Keep the contents at their current size, allowing
// them to be hidden slowly by the section animation, then change their size
// once the animation is complete so they aren't visible just below the title.
auto ctx = new QObject();
connect(toggleAnimation, &QAbstractAnimation::finished, ctx, [this, ctx]() {
// This is a single-shot connection. Qt6 has built-in support for this kind of thing.
contentArea->setMinimumHeight(0);
contentArea->setMaximumHeight(0);
ctx->deleteLater();
});
toggleAnimation->setDirection(QAbstractAnimation::Backward);
}
toggleAnimation->start();
}
void CollapsibleSection::setContentLayout(QLayout* contentLayout)
{
if (contentArea->layout() == contentLayout)
return;
delete contentArea->layout();
contentArea->setLayout(contentLayout);
collapsedHeight = sizeHint().height() - contentArea->maximumHeight();
int contentHeight = this->expanded ? getContentHeight() : 0;
contentArea->setMinimumHeight(contentHeight);
contentArea->setMaximumHeight(contentHeight);
updateAnimationTargets();
}
void CollapsibleSection::setTitle(const QString &title)
{
toggleButton->setText(title);
}
int CollapsibleSection::getContentHeight() const
{
return contentArea->layout() ? contentArea->layout()->sizeHint().height() : 0;
}
void CollapsibleSection::updateAnimationTargets()
{
const int contentHeight = getContentHeight();
for (auto anim : sectionAnimations) {
anim->setDuration(animationDuration);
anim->setStartValue(collapsedHeight);
anim->setEndValue(collapsedHeight + contentHeight);
}
contentAnimation->setDuration(animationDuration);
contentAnimation->setStartValue(0);
contentAnimation->setEndValue(contentHeight);
}

View File

@ -337,9 +337,9 @@ namespace fex
return DefineStatement(identifer, value);
}
std::map<std::string, int> Parser::ReadDefines(const QString &filename, std::vector<std::string> matching)
tsl::ordered_map<std::string, int> Parser::ReadDefines(const QString &filename, std::vector<std::string> matching)
{
std::map<std::string, int> out;
tsl::ordered_map<std::string, int> out;
Lexer lexer;
auto tokens = lexer.LexFile(filename);
@ -488,12 +488,12 @@ namespace fex
return items;
}
std::map<std::string, ArrayValue> Parser::ParseTopLevelObjects(std::vector<Token> tokens)
tsl::ordered_map<std::string, ArrayValue> Parser::ParseTopLevelObjects(std::vector<Token> tokens)
{
index_ = 0;
tokens_ = std::move(tokens);
std::map<std::string, ArrayValue> items;
tsl::ordered_map<std::string, ArrayValue> items;
while (index_ < tokens_.size())
{

View File

@ -0,0 +1,50 @@
#include "lib/fex/parser_util.h"
#include <QMap>
#include "lib/fex/parser.h"
ParserUtil::ParserUtil(QString root): root_(root) {}
QStringList ParserUtil::ReadDefines(QString filename, QString prefix)
{
if (filename.isEmpty()) {
return QStringList();
}
QString filepath = root_ + "/" + filename;
fex::Parser parser;
std::vector<std::string> match_list = { prefix.toStdString() + ".*" };
tsl::ordered_map<std::string, int> defines = parser.ReadDefines(filepath.toStdString(), match_list);
QStringList out;
for(auto const& define : defines) {
out.append(QString::fromStdString(define.first));
}
return out;
}
QStringList ParserUtil::ReadDefinesValueSort(QString filename, QString prefix)
{
if (filename.isEmpty()) {
return QStringList();
}
QString filepath = root_ + "/" + filename;
fex::Parser parser;
std::vector<std::string> match_list = { prefix.toStdString() + ".*" };
tsl::ordered_map<std::string, int> defines = parser.ReadDefines(filepath.toStdString(), match_list);
QMultiMap<int, QString> defines_keyed_by_value;
for (const auto& pair : defines) {
defines_keyed_by_value.insert(pair.second, QString::fromStdString(pair.first));
}
return defines_keyed_by_value.values();
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -337,7 +337,7 @@ void MainWindow::refreshAfterPaletteChange(Tileset *tileset) {
this->editor->map_item->draw(true);
this->editor->updateMapBorder();
this->editor->updateMapConnections();
this->editor->project->saveTilesetPalettes(tileset);
tileset->savePalettes();
}
void MainWindow::setTilesetPalette(Tileset *tileset, int paletteIndex, QList<QList<int>> colors) {
@ -576,14 +576,14 @@ void MainWindow::setSecondaryTileset(QString tileset) {
void MainWindow::saveMetatilesByMetatileId(int metatileId) {
Tileset * tileset = Tileset::getMetatileTileset(metatileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary);
if (this->editor->project && tileset)
this->editor->project->saveTilesetMetatiles(tileset);
if (tileset)
tileset->saveMetatiles();
}
void MainWindow::saveMetatileAttributesByMetatileId(int metatileId) {
Tileset * tileset = Tileset::getMetatileTileset(metatileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary);
if (this->editor->project && tileset)
this->editor->project->saveTilesetMetatileAttributes(tileset);
if (tileset)
tileset->saveMetatileAttributes();
// If the tileset editor is open it needs to be refreshed with the new changes.
// Rather than do a full refresh (which is costly) we tell the editor it will need
@ -814,156 +814,132 @@ QJSValue MainWindow::getTilePixels(int tileId) {
QString MainWindow::getSong() {
if (!this->editor || !this->editor->map)
return QString();
return this->editor->map->song;
return this->editor->map->header()->song();
}
void MainWindow::setSong(QString song) {
if (!this->ui || !this->editor || !this->editor->project)
if (!this->editor || !this->editor->map || !this->editor->project)
return;
if (!this->editor->project->songNames.contains(song)) {
logError(QString("Unknown song '%1'").arg(song));
return;
}
this->ui->comboBox_Song->setCurrentText(song);
this->editor->map->header()->setSong(song);
}
QString MainWindow::getLocation() {
if (!this->editor || !this->editor->map)
return QString();
return this->editor->map->location;
return this->editor->map->header()->location();
}
void MainWindow::setLocation(QString location) {
if (!this->ui || !this->editor || !this->editor->project)
if (!this->editor || !this->editor->map || !this->editor->project)
return;
if (!this->editor->project->mapSectionIdNames.contains(location)) {
logError(QString("Unknown location '%1'").arg(location));
return;
}
this->ui->comboBox_Location->setCurrentText(location);
this->editor->map->header()->setLocation(location);
}
bool MainWindow::getRequiresFlash() {
if (!this->editor || !this->editor->map)
return false;
return this->editor->map->requiresFlash;
return this->editor->map->header()->requiresFlash();
}
void MainWindow::setRequiresFlash(bool require) {
if (!this->ui)
if (!this->editor || !this->editor->map)
return;
this->ui->checkBox_Visibility->setChecked(require);
this->editor->map->header()->setRequiresFlash(require);
}
QString MainWindow::getWeather() {
if (!this->editor || !this->editor->map)
return QString();
return this->editor->map->weather;
return this->editor->map->header()->weather();
}
void MainWindow::setWeather(QString weather) {
if (!this->ui || !this->editor || !this->editor->project)
if (!this->editor || !this->editor->map || !this->editor->project)
return;
if (!this->editor->project->weatherNames.contains(weather)) {
logError(QString("Unknown weather '%1'").arg(weather));
return;
}
this->ui->comboBox_Weather->setCurrentText(weather);
this->editor->map->header()->setWeather(weather);
}
QString MainWindow::getType() {
if (!this->editor || !this->editor->map)
return QString();
return this->editor->map->type;
return this->editor->map->header()->type();
}
void MainWindow::setType(QString type) {
if (!this->ui || !this->editor || !this->editor->project)
if (!this->editor || !this->editor->map || !this->editor->project)
return;
if (!this->editor->project->mapTypes.contains(type)) {
logError(QString("Unknown map type '%1'").arg(type));
return;
}
this->ui->comboBox_Type->setCurrentText(type);
this->editor->map->header()->setType(type);
}
QString MainWindow::getBattleScene() {
if (!this->editor || !this->editor->map)
return QString();
return this->editor->map->battle_scene;
return this->editor->map->header()->battleScene();
}
void MainWindow::setBattleScene(QString battleScene) {
if (!this->ui || !this->editor || !this->editor->project)
if (!this->editor || !this->editor->map || !this->editor->project)
return;
if (!this->editor->project->mapBattleScenes.contains(battleScene)) {
logError(QString("Unknown battle scene '%1'").arg(battleScene));
return;
}
this->ui->comboBox_BattleScene->setCurrentText(battleScene);
this->editor->map->header()->setBattleScene(battleScene);
}
bool MainWindow::getShowLocationName() {
if (!this->editor || !this->editor->map)
return false;
return this->editor->map->show_location;
return this->editor->map->header()->showsLocationName();
}
void MainWindow::setShowLocationName(bool show) {
if (!this->ui)
if (!this->editor || !this->editor->map)
return;
this->ui->checkBox_ShowLocation->setChecked(show);
this->editor->map->header()->setShowsLocationName(show);
}
bool MainWindow::getAllowRunning() {
if (!this->editor || !this->editor->map)
return false;
return this->editor->map->allowRunning;
return this->editor->map->header()->allowsRunning();
}
void MainWindow::setAllowRunning(bool allow) {
if (!this->ui)
if (!this->editor || !this->editor->map)
return;
this->ui->checkBox_AllowRunning->setChecked(allow);
this->editor->map->header()->setAllowsRunning(allow);
}
bool MainWindow::getAllowBiking() {
if (!this->editor || !this->editor->map)
return false;
return this->editor->map->allowBiking;
return this->editor->map->header()->allowsBiking();
}
void MainWindow::setAllowBiking(bool allow) {
if (!this->ui)
if (!this->editor || !this->editor->map)
return;
this->ui->checkBox_AllowBiking->setChecked(allow);
this->editor->map->header()->setAllowsBiking(allow);
}
bool MainWindow::getAllowEscaping() {
if (!this->editor || !this->editor->map)
return false;
return this->editor->map->allowEscaping;
return this->editor->map->header()->allowsEscaping();
}
void MainWindow::setAllowEscaping(bool allow) {
if (!this->ui)
if (!this->editor || !this->editor->map)
return;
this->ui->checkBox_AllowEscaping->setChecked(allow);
this->editor->map->header()->setAllowsEscaping(allow);
}
int MainWindow::getFloorNumber() {
if (!this->editor || !this->editor->map)
return 0;
return this->editor->map->floorNumber;
return this->editor->map->header()->floorNumber();
}
void MainWindow::setFloorNumber(int floorNumber) {
if (!this->ui)
if (!this->editor || !this->editor->map)
return;
if (floorNumber < -128 || floorNumber > 127) {
logError(QString("Invalid floor number '%1'").arg(floorNumber));
return;
}
this->ui->spinBox_FloorNumber->setValue(floorNumber);
this->editor->map->header()->setFloorNumber(floorNumber);
}

View File

@ -1,16 +1,10 @@
#include "colorinputwidget.h"
#include "ui_colorinputwidget.h"
#include "colorpicker.h"
#include "validator.h"
#include <cmath>
class HexCodeValidator : public QValidator {
virtual QValidator::State validate(QString &input, int &) const override {
input = input.toUpper();
return QValidator::Acceptable;
}
};
static inline int rgb5(int rgb) { return round(static_cast<double>(rgb * 31) / 255.0); }
static inline int rgb8(int rgb) { return round(rgb * 255. / 31.); }
static inline int gbaRed(int rgb) { return rgb & 0x1f; }
@ -43,8 +37,8 @@ void ColorInputWidget::init() {
connect(ui->spinBox_Green, QOverload<int>::of(&QSpinBox::valueChanged), this, &ColorInputWidget::setRgbFromSpinners);
connect(ui->spinBox_Blue, QOverload<int>::of(&QSpinBox::valueChanged), this, &ColorInputWidget::setRgbFromSpinners);
static const HexCodeValidator hexValidator;
ui->lineEdit_Hex->setValidator(&hexValidator);
static const UppercaseValidator uppercaseValidator;
ui->lineEdit_Hex->setValidator(&uppercaseValidator);
connect(ui->lineEdit_Hex, &QLineEdit::textEdited, this, &ColorInputWidget::setRgbFromHexString);
// We have separate signals for when color input editing finishes.

View File

@ -66,7 +66,7 @@ QVariant ConnectionPixmapItem::itemChange(GraphicsItemChange change, const QVari
// This is convoluted because of how our edit history works; this would otherwise just be 'this->connection->setOffset(newOffset);'
if (this->connection->parentMap() && newOffset != this->connection->offset())
this->connection->parentMap()->editHistory.push(new MapConnectionMove(this->connection, newOffset, this->actionId));
this->connection->parentMap()->commit(new MapConnectionMove(this->connection, newOffset, this->actionId));
return QPointF(x, y);
}

View File

@ -79,24 +79,24 @@ void ConnectionsListItem::mousePressEvent(QMouseEvent *) {
void ConnectionsListItem::on_comboBox_Direction_currentTextChanged(QString direction) {
this->setSelected(true);
if (this->map)
this->map->editHistory.push(new MapConnectionChangeDirection(this->connection, direction));
this->map->commit(new MapConnectionChangeDirection(this->connection, direction));
}
void ConnectionsListItem::on_comboBox_Map_currentTextChanged(QString mapName) {
this->setSelected(true);
if (this->map && ui->comboBox_Map->findText(mapName) >= 0)
this->map->editHistory.push(new MapConnectionChangeMap(this->connection, mapName));
this->map->commit(new MapConnectionChangeMap(this->connection, mapName));
}
void ConnectionsListItem::on_spinBox_Offset_valueChanged(int offset) {
this->setSelected(true);
if (this->map)
this->map->editHistory.push(new MapConnectionMove(this->connection, offset, this->actionId));
this->map->commit(new MapConnectionMove(this->connection, offset, this->actionId));
}
void ConnectionsListItem::on_button_Delete_clicked() {
if (this->map)
this->map->editHistory.push(new MapConnectionRemove(this->map, this->connection));
this->map->commit(new MapConnectionRemove(this->map, this->connection));
}
void ConnectionsListItem::on_button_OpenMap_clicked() {

View File

@ -185,7 +185,7 @@ void CustomScriptsEditor::displayNewScript(QString filepath) {
// Verify new script path is not already in list
for (int i = 0; i < ui->list->count(); i++) {
if (filepath == this->getScriptFilepath(ui->list->item(i), false)) {
QMessageBox::information(this, "", QString("The script '%1' is already loaded").arg(filepath));
QMessageBox::information(this, QApplication::applicationName(), QString("The script '%1' is already loaded").arg(filepath));
return;
}
}
@ -219,7 +219,7 @@ void CustomScriptsEditor::openScript(QListWidgetItem * item) {
const QString path = this->getScriptFilepath(item);
QFileInfo fileInfo(path);
if (!fileInfo.exists() || !fileInfo.isFile()){
QMessageBox::warning(this, "", QString("Failed to open script '%1'").arg(path));
QMessageBox::warning(this, QApplication::applicationName(), QString("Failed to open script '%1'").arg(path));
return;
}
Editor::openInTextEditor(path);

View File

@ -91,7 +91,7 @@ void DraggablePixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *mouse) {
} else {
selectedEvents.append(this->event);
}
editor->map->editHistory.push(new EventMove(selectedEvents, moveDistance.x(), moveDistance.y(), currentActionId));
editor->map->commit(new EventMove(selectedEvents, moveDistance.x(), moveDistance.y(), currentActionId));
this->releaseSelectionQueued = false;
}

View File

@ -108,7 +108,7 @@ void EventFrame::connectSignals(MainWindow *) {
connect(this->spinner_x, QOverload<int>::of(&QSpinBox::valueChanged), [this](int value) {
int delta = value - event->getX();
if (delta) {
this->event->getMap()->editHistory.push(new EventMove(QList<Event *>() << this->event, delta, 0, this->spinner_x->getActionId()));
this->event->getMap()->commit(new EventMove(QList<Event *>() << this->event, delta, 0, this->spinner_x->getActionId()));
}
});
@ -118,7 +118,7 @@ void EventFrame::connectSignals(MainWindow *) {
connect(this->spinner_y, QOverload<int>::of(&QSpinBox::valueChanged), [this](int value) {
int delta = value - event->getY();
if (delta) {
this->event->getMap()->editHistory.push(new EventMove(QList<Event *>() << this->event, 0, delta, this->spinner_y->getActionId()));
this->event->getMap()->commit(new EventMove(QList<Event *>() << this->event, 0, delta, this->spinner_y->getActionId()));
}
});
connect(this->event->getPixmapItem(), &DraggablePixmapItem::yChanged, this->spinner_y, &NoScrollSpinBox::setValue);

226
src/ui/mapheaderform.cpp Normal file
View File

@ -0,0 +1,226 @@
#include "mapheaderform.h"
#include "ui_mapheaderform.h"
#include "project.h"
MapHeaderForm::MapHeaderForm(QWidget *parent)
: QWidget(parent)
, ui(new Ui::MapHeaderForm)
{
ui->setupUi(this);
// This value is an s8 by default, but we don't need to unnecessarily limit users.
ui->spinBox_FloorNumber->setMinimum(INT_MIN);
ui->spinBox_FloorNumber->setMaximum(INT_MAX);
// The layout for this UI keeps fields at their size hint, which is a little short for the line edit.
ui->lineEdit_LocationName->setMinimumWidth(ui->comboBox_Location->sizeHint().width());
connect(ui->comboBox_Song, &QComboBox::currentTextChanged, this, &MapHeaderForm::onSongUpdated);
connect(ui->comboBox_Location, &QComboBox::currentTextChanged, this, &MapHeaderForm::onLocationChanged);
connect(ui->comboBox_Weather, &QComboBox::currentTextChanged, this, &MapHeaderForm::onWeatherChanged);
connect(ui->comboBox_Type, &QComboBox::currentTextChanged, this, &MapHeaderForm::onTypeChanged);
connect(ui->comboBox_BattleScene, &QComboBox::currentTextChanged, this, &MapHeaderForm::onBattleSceneChanged);
connect(ui->checkBox_RequiresFlash, &QCheckBox::stateChanged, this, &MapHeaderForm::onRequiresFlashChanged);
connect(ui->checkBox_ShowLocationName, &QCheckBox::stateChanged, this, &MapHeaderForm::onShowLocationNameChanged);
connect(ui->checkBox_AllowRunning, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowRunningChanged);
connect(ui->checkBox_AllowBiking, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowBikingChanged);
connect(ui->checkBox_AllowEscaping, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowEscapingChanged);
connect(ui->spinBox_FloorNumber, QOverload<int>::of(&QSpinBox::valueChanged), this, &MapHeaderForm::onFloorNumberChanged);
connect(ui->lineEdit_LocationName, &QLineEdit::textChanged, this, &MapHeaderForm::onLocationNameChanged);
}
MapHeaderForm::~MapHeaderForm()
{
delete ui;
}
void MapHeaderForm::setProject(Project * project, bool allowChanges) {
clear();
if (m_project) {
m_project->disconnect(this);
}
m_project = project;
m_allowProjectChanges = allowChanges;
if (!m_project)
return;
// Populate combo boxes
const QSignalBlocker b_Song(ui->comboBox_Song);
ui->comboBox_Song->clear();
ui->comboBox_Song->addItems(m_project->songNames);
const QSignalBlocker b_Weather(ui->comboBox_Weather);
ui->comboBox_Weather->clear();
ui->comboBox_Weather->addItems(m_project->weatherNames);
const QSignalBlocker b_Type(ui->comboBox_Type);
ui->comboBox_Type->clear();
ui->comboBox_Type->addItems(m_project->mapTypes);
const QSignalBlocker b_BattleScene(ui->comboBox_BattleScene);
ui->comboBox_BattleScene->clear();
ui->comboBox_BattleScene->addItems(m_project->mapBattleScenes);
const QSignalBlocker b_Locations(ui->comboBox_Location);
ui->comboBox_Location->clear();
ui->comboBox_Location->addItems(m_project->mapSectionIdNames);
// Hide config-specific settings
bool hasFlags = projectConfig.mapAllowFlagsEnabled;
ui->checkBox_AllowRunning->setVisible(hasFlags);
ui->checkBox_AllowBiking->setVisible(hasFlags);
ui->checkBox_AllowEscaping->setVisible(hasFlags);
ui->label_AllowRunning->setVisible(hasFlags);
ui->label_AllowBiking->setVisible(hasFlags);
ui->label_AllowEscaping->setVisible(hasFlags);
bool floorNumEnabled = projectConfig.floorNumberEnabled;
ui->spinBox_FloorNumber->setVisible(floorNumEnabled);
ui->label_FloorNumber->setVisible(floorNumEnabled);
// If the project changes any of the displayed data, update it accordingly.
connect(m_project, &Project::mapSectionIdNamesChanged, this, &MapHeaderForm::setLocations);
connect(m_project, &Project::mapSectionDisplayNameChanged, this, &MapHeaderForm::updateLocationName);
}
void MapHeaderForm::setLocations(const QStringList &locations) {
const QSignalBlocker b(ui->comboBox_Location);
const QString before = ui->comboBox_Location->currentText();
ui->comboBox_Location->clear();
ui->comboBox_Location->addItems(locations);
ui->comboBox_Location->setCurrentText(before);
}
// Assign a MapHeader that the form will keep in sync with the UI.
void MapHeaderForm::setHeader(MapHeader *header) {
if (m_header == header)
return;
if (m_header) {
m_header->disconnect(this);
}
m_header = header;
if (!m_header) {
clear();
return;
}
// If the MapHeader is changed externally (for example, with the scripting API) update the UI accordingly
connect(m_header, &MapHeader::songChanged, this, &MapHeaderForm::setSong);
connect(m_header, &MapHeader::locationChanged, this, &MapHeaderForm::setLocation);
connect(m_header, &MapHeader::requiresFlashChanged, this, &MapHeaderForm::setRequiresFlash);
connect(m_header, &MapHeader::weatherChanged, this, &MapHeaderForm::setWeather);
connect(m_header, &MapHeader::typeChanged, this, &MapHeaderForm::setType);
connect(m_header, &MapHeader::battleSceneChanged, this, &MapHeaderForm::setBattleScene);
connect(m_header, &MapHeader::showsLocationNameChanged, this, &MapHeaderForm::setShowsLocationName);
connect(m_header, &MapHeader::allowsRunningChanged, this, &MapHeaderForm::setAllowsRunning);
connect(m_header, &MapHeader::allowsBikingChanged, this, &MapHeaderForm::setAllowsBiking);
connect(m_header, &MapHeader::allowsEscapingChanged, this, &MapHeaderForm::setAllowsEscaping);
connect(m_header, &MapHeader::floorNumberChanged, this, &MapHeaderForm::setFloorNumber);
// Immediately update the UI to reflect the assigned MapHeader
setHeaderData(*m_header);
}
void MapHeaderForm::clear() {
m_header = nullptr;
setHeaderData(MapHeader());
}
void MapHeaderForm::setHeaderData(const MapHeader &header) {
setSong(header.song());
setLocation(header.location());
setRequiresFlash(header.requiresFlash());
setWeather(header.weather());
setType(header.type());
setBattleScene(header.battleScene());
setShowsLocationName(header.showsLocationName());
setAllowsRunning(header.allowsRunning());
setAllowsBiking(header.allowsBiking());
setAllowsEscaping(header.allowsEscaping());
setFloorNumber(header.floorNumber());
updateLocationName();
}
MapHeader MapHeaderForm::headerData() const {
if (m_header)
return *m_header;
// Build header from UI
MapHeader header;
header.setSong(song());
header.setLocation(location());
header.setRequiresFlash(requiresFlash());
header.setWeather(weather());
header.setType(type());
header.setBattleScene(battleScene());
header.setShowsLocationName(showsLocationName());
header.setAllowsRunning(allowsRunning());
header.setAllowsBiking(allowsBiking());
header.setAllowsEscaping(allowsEscaping());
header.setFloorNumber(floorNumber());
return header;
}
void MapHeaderForm::updateLocationName() {
setLocationName(m_project ? m_project->getMapsecDisplayName(location()) : QString());
}
// Set data in UI
void MapHeaderForm::setSong(const QString &song) { ui->comboBox_Song->setCurrentText(song); }
void MapHeaderForm::setLocation(const QString &location) { ui->comboBox_Location->setCurrentText(location); }
void MapHeaderForm::setLocationName(const QString &locationName) { ui->lineEdit_LocationName->setText(locationName); }
void MapHeaderForm::setRequiresFlash(bool requiresFlash) { ui->checkBox_RequiresFlash->setChecked(requiresFlash); }
void MapHeaderForm::setWeather(const QString &weather) { ui->comboBox_Weather->setCurrentText(weather); }
void MapHeaderForm::setType(const QString &type) { ui->comboBox_Type->setCurrentText(type); }
void MapHeaderForm::setBattleScene(const QString &battleScene) { ui->comboBox_BattleScene->setCurrentText(battleScene); }
void MapHeaderForm::setShowsLocationName(bool showsLocationName) { ui->checkBox_ShowLocationName->setChecked(showsLocationName); }
void MapHeaderForm::setAllowsRunning(bool allowsRunning) { ui->checkBox_AllowRunning->setChecked(allowsRunning); }
void MapHeaderForm::setAllowsBiking(bool allowsBiking) { ui->checkBox_AllowBiking->setChecked(allowsBiking); }
void MapHeaderForm::setAllowsEscaping(bool allowsEscaping) { ui->checkBox_AllowEscaping->setChecked(allowsEscaping); }
void MapHeaderForm::setFloorNumber(int floorNumber) { ui->spinBox_FloorNumber->setValue(floorNumber); }
// Read data from UI
QString MapHeaderForm::song() const { return ui->comboBox_Song->currentText(); }
QString MapHeaderForm::location() const { return ui->comboBox_Location->currentText(); }
QString MapHeaderForm::locationName() const { return ui->lineEdit_LocationName->text(); }
bool MapHeaderForm::requiresFlash() const { return ui->checkBox_RequiresFlash->isChecked(); }
QString MapHeaderForm::weather() const { return ui->comboBox_Weather->currentText(); }
QString MapHeaderForm::type() const { return ui->comboBox_Type->currentText(); }
QString MapHeaderForm::battleScene() const { return ui->comboBox_BattleScene->currentText(); }
bool MapHeaderForm::showsLocationName() const { return ui->checkBox_ShowLocationName->isChecked(); }
bool MapHeaderForm::allowsRunning() const { return ui->checkBox_AllowRunning->isChecked(); }
bool MapHeaderForm::allowsBiking() const { return ui->checkBox_AllowBiking->isChecked(); }
bool MapHeaderForm::allowsEscaping() const { return ui->checkBox_AllowEscaping->isChecked(); }
int MapHeaderForm::floorNumber() const { return ui->spinBox_FloorNumber->value(); }
// Send changes in UI to tracked MapHeader (if there is one)
void MapHeaderForm::onSongUpdated(const QString &song) { if (m_header) m_header->setSong(song); }
void MapHeaderForm::onWeatherChanged(const QString &weather) { if (m_header) m_header->setWeather(weather); }
void MapHeaderForm::onTypeChanged(const QString &type) { if (m_header) m_header->setType(type); }
void MapHeaderForm::onBattleSceneChanged(const QString &battleScene) { if (m_header) m_header->setBattleScene(battleScene); }
void MapHeaderForm::onRequiresFlashChanged(int selected) { if (m_header) m_header->setRequiresFlash(selected == Qt::Checked); }
void MapHeaderForm::onShowLocationNameChanged(int selected) { if (m_header) m_header->setShowsLocationName(selected == Qt::Checked); }
void MapHeaderForm::onAllowRunningChanged(int selected) { if (m_header) m_header->setAllowsRunning(selected == Qt::Checked); }
void MapHeaderForm::onAllowBikingChanged(int selected) { if (m_header) m_header->setAllowsBiking(selected == Qt::Checked); }
void MapHeaderForm::onAllowEscapingChanged(int selected) { if (m_header) m_header->setAllowsEscaping(selected == Qt::Checked); }
void MapHeaderForm::onFloorNumberChanged(int offset) { if (m_header) m_header->setFloorNumber(offset); }
void MapHeaderForm::onLocationChanged(const QString &location) {
if (m_header) m_header->setLocation(location);
updateLocationName();
}
void MapHeaderForm::onLocationNameChanged(const QString &locationName) {
if (m_project && m_allowProjectChanges) {
// The location name is actually part of the project, not the map header.
// If the field is changed in the UI we can push these changes to the project.
m_project->setMapsecDisplayName(location(), locationName);
}
}

View File

@ -36,25 +36,25 @@ QString getDescription(ImageExporterMode mode) {
return "";
}
MapImageExporter::MapImageExporter(QWidget *parent_, Editor *editor_, ImageExporterMode mode) :
QDialog(parent_),
MapImageExporter::MapImageExporter(QWidget *parent, Editor *editor, ImageExporterMode mode) :
QDialog(parent),
ui(new Ui::MapImageExporter)
{
this->setAttribute(Qt::WA_DeleteOnClose);
setAttribute(Qt::WA_DeleteOnClose);
ui->setupUi(this);
this->map = editor_->map;
this->layout = editor_->layout;
this->editor = editor_;
this->mode = mode;
this->setWindowTitle(getTitle(this->mode));
this->ui->label_Description->setText(getDescription(this->mode));
this->ui->groupBox_Connections->setVisible(this->mode != ImageExporterMode::Stitch);
this->ui->groupBox_Timelapse->setVisible(this->mode == ImageExporterMode::Timelapse);
m_map = editor->map;
m_layout = editor->layout;
m_editor = editor;
m_mode = mode;
setWindowTitle(getTitle(m_mode));
ui->label_Description->setText(getDescription(m_mode));
ui->groupBox_Connections->setVisible(m_mode != ImageExporterMode::Stitch);
ui->groupBox_Timelapse->setVisible(m_mode == ImageExporterMode::Timelapse);
if (this->map) {
this->ui->comboBox_MapSelection->addItems(editor->project->mapNames);
this->ui->comboBox_MapSelection->setCurrentText(map->name);
this->ui->comboBox_MapSelection->setEnabled(false);// TODO: allow selecting map from drop-down
if (m_map) {
ui->comboBox_MapSelection->addItems(editor->project->mapNames);
ui->comboBox_MapSelection->setCurrentText(m_map->name());
ui->comboBox_MapSelection->setEnabled(false);// TODO: allow selecting map from drop-down
}
connect(ui->pushButton_Save, &QPushButton::pressed, this, &MapImageExporter::saveImage);
@ -62,7 +62,7 @@ MapImageExporter::MapImageExporter(QWidget *parent_, Editor *editor_, ImageExpor
}
MapImageExporter::~MapImageExporter() {
delete scene;
delete m_scene;
delete ui;
}
@ -80,48 +80,49 @@ void MapImageExporter::resizeEvent(QResizeEvent *event) {
void MapImageExporter::saveImage() {
// Make sure preview is up-to-date before we save.
if (this->preview.isNull())
if (m_preview.isNull())
updatePreview();
if (this->preview.isNull())
if (m_preview.isNull())
return;
QString title = getTitle(this->mode);
const QString title = getTitle(m_mode);
const QString itemName = m_map ? m_map->name() : m_layout->name;
QString defaultFilename;
switch (this->mode)
switch (m_mode)
{
case ImageExporterMode::Normal:
defaultFilename = this->map? this->map->name : this->layout->name;
defaultFilename = itemName;
break;
case ImageExporterMode::Stitch:
defaultFilename = QString("Stitch_From_%1").arg(this->map? this->map->name : this->layout->name);
defaultFilename = QString("Stitch_From_%1").arg(itemName);
break;
case ImageExporterMode::Timelapse:
defaultFilename = QString("Timelapse_%1").arg(this->map? this->map->name : this->layout->name);
defaultFilename = QString("Timelapse_%1").arg(itemName);
break;
}
QString defaultFilepath = QString("%1/%2.%3")
.arg(FileDialog::getDirectory())
.arg(defaultFilename)
.arg(this->mode == ImageExporterMode::Timelapse ? "gif" : "png");
QString filter = this->mode == ImageExporterMode::Timelapse ? "Image Files (*.gif)" : "Image Files (*.png *.jpg *.bmp)";
.arg(m_mode == ImageExporterMode::Timelapse ? "gif" : "png");
QString filter = m_mode == ImageExporterMode::Timelapse ? "Image Files (*.gif)" : "Image Files (*.png *.jpg *.bmp)";
QString filepath = FileDialog::getSaveFileName(this, title, defaultFilepath, filter);
if (!filepath.isEmpty()) {
switch (this->mode) {
switch (m_mode) {
case ImageExporterMode::Normal:
case ImageExporterMode::Stitch:
// Normal and Stitch modes already have the image ready to go in the preview.
this->preview.save(filepath);
m_preview.save(filepath);
break;
case ImageExporterMode::Timelapse:
// Timelapse will play in order of layout changes then map changes (events)
// TODO: potentially update in the future?
QGifImage timelapseImg;
timelapseImg.setDefaultDelay(this->settings.timelapseDelayMs);
timelapseImg.setDefaultDelay(m_settings.timelapseDelayMs);
timelapseImg.setDefaultTransparentColor(QColor(0, 0, 0));
// lambda to avoid redundancy
auto generateTimelapseFromHistory = [this, &timelapseImg](QString progressText, QUndoStack &historyStack){
auto generateTimelapseFromHistory = [this, &timelapseImg](QString progressText, QUndoStack *historyStack){
QProgressDialog progress(progressText, "Cancel", 0, 1, this);
progress.setAutoClose(true);
progress.setWindowModality(Qt::WindowModal);
@ -129,20 +130,20 @@ void MapImageExporter::saveImage() {
progress.setMaximum(1);
progress.setValue(0);
int maxWidth = this->layout->getWidth() * 16;
int maxHeight = this->layout->getHeight() * 16;
if (this->settings.showBorder) {
int maxWidth = m_layout->getWidth() * 16;
int maxHeight = m_layout->getHeight() * 16;
if (m_settings.showBorder) {
maxWidth += 2 * STITCH_MODE_BORDER_DISTANCE * 16;
maxHeight += 2 * STITCH_MODE_BORDER_DISTANCE * 16;
}
// Rewind to the specified start of the map edit history.
int i = 0;
while (historyStack.canUndo()) {
while (historyStack->canUndo()) {
progress.setValue(i);
historyStack.undo();
int width = this->layout->getWidth() * 16;
int height = this->layout->getHeight() * 16;
if (this->settings.showBorder) {
historyStack->undo();
int width = m_layout->getWidth() * 16;
int height = m_layout->getHeight() * 16;
if (m_settings.showBorder) {
width += 2 * STITCH_MODE_BORDER_DISTANCE * 16;
height += 2 * STITCH_MODE_BORDER_DISTANCE * 16;
}
@ -161,19 +162,19 @@ void MapImageExporter::saveImage() {
while (i > 0) {
if (progress.wasCanceled()) {
progress.close();
while (i > 0 && historyStack.canRedo()) {
while (i > 0 && historyStack->canRedo()) {
i--;
historyStack.redo();
historyStack->redo();
}
return;
}
while (historyStack.canRedo() &&
!historyItemAppliesToFrame(historyStack.command(historyStack.index()))) {
while (historyStack->canRedo() &&
!historyItemAppliesToFrame(historyStack->command(historyStack->index()))) {
i--;
historyStack.redo();
historyStack->redo();
}
progress.setValue(progress.maximum() - i);
QPixmap pixmap = this->getFormattedMapPixmap(this->map);
QPixmap pixmap = getFormattedMapPixmap();
if (pixmap.width() < maxWidth || pixmap.height() < maxHeight) {
QPixmap pixmap2 = QPixmap(maxWidth, maxHeight);
QPainter painter(&pixmap2);
@ -183,34 +184,34 @@ void MapImageExporter::saveImage() {
pixmap = pixmap2;
}
timelapseImg.addFrame(pixmap.toImage());
for (int j = 0; j < this->settings.timelapseSkipAmount; j++) {
for (int j = 0; j < m_settings.timelapseSkipAmount; j++) {
if (i > 0) {
i--;
historyStack.redo();
while (historyStack.canRedo() &&
!historyItemAppliesToFrame(historyStack.command(historyStack.index()))) {
historyStack->redo();
while (historyStack->canRedo() &&
!historyItemAppliesToFrame(historyStack->command(historyStack->index()))) {
i--;
historyStack.redo();
historyStack->redo();
}
}
}
}
// The latest map state is the last animated frame.
QPixmap pixmap = this->getFormattedMapPixmap(this->map);
QPixmap pixmap = getFormattedMapPixmap();
timelapseImg.addFrame(pixmap.toImage());
progress.close();
};
if (this->layout)
generateTimelapseFromHistory("Building layout timelapse...", this->layout->editHistory);
if (m_layout)
generateTimelapseFromHistory("Building layout timelapse...", &m_layout->editHistory);
if (this->map)
generateTimelapseFromHistory("Building map timelapse...", this->map->editHistory);
if (m_map)
generateTimelapseFromHistory("Building map timelapse...", m_map->editHistory());
timelapseImg.save(filepath);
break;
}
this->close();
close();
}
}
@ -229,26 +230,26 @@ bool MapImageExporter::historyItemAppliesToFrame(const QUndoCommand *command) {
case CommandId::ID_PaintCollision:
case CommandId::ID_BucketFillCollision:
case CommandId::ID_MagicFillCollision:
return this->settings.showCollision;
return m_settings.showCollision;
case CommandId::ID_PaintBorder:
return this->settings.showBorder;
return m_settings.showBorder;
case CommandId::ID_MapConnectionMove:
case CommandId::ID_MapConnectionChangeDirection:
case CommandId::ID_MapConnectionChangeMap:
case CommandId::ID_MapConnectionAdd:
case CommandId::ID_MapConnectionRemove:
return this->settings.showUpConnections || this->settings.showDownConnections || this->settings.showLeftConnections || this->settings.showRightConnections;
return m_settings.showUpConnections || m_settings.showDownConnections || m_settings.showLeftConnections || m_settings.showRightConnections;
case CommandId::ID_EventMove:
case CommandId::ID_EventShift:
case CommandId::ID_EventCreate:
case CommandId::ID_EventDelete:
case CommandId::ID_EventDuplicate: {
bool eventTypeIsApplicable =
(this->settings.showObjects && (command->id() & IDMask_EventType_Object) != 0)
|| (this->settings.showWarps && (command->id() & IDMask_EventType_Warp) != 0)
|| (this->settings.showBGs && (command->id() & IDMask_EventType_BG) != 0)
|| (this->settings.showTriggers && (command->id() & IDMask_EventType_Trigger) != 0)
|| (this->settings.showHealLocations && (command->id() & IDMask_EventType_Heal) != 0);
(m_settings.showObjects && (command->id() & IDMask_EventType_Object) != 0)
|| (m_settings.showWarps && (command->id() & IDMask_EventType_Warp) != 0)
|| (m_settings.showBGs && (command->id() & IDMask_EventType_BG) != 0)
|| (m_settings.showTriggers && (command->id() & IDMask_EventType_Trigger) != 0)
|| (m_settings.showHealLocations && (command->id() & IDMask_EventType_Heal) != 0);
return eventTypeIsApplicable;
}
default:
@ -268,7 +269,7 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress, bool inclu
QSet<QString> visited;
QList<StitchedMap> stitchedMaps;
QList<StitchedMap> unvisited;
unvisited.append(StitchedMap{0, 0, this->editor->map});
unvisited.append(StitchedMap{0, 0, m_editor->map});
progress->setLabelText("Gathering stitched maps...");
while (!unvisited.isEmpty()) {
@ -279,9 +280,9 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress, bool inclu
progress->setValue(visited.size());
StitchedMap cur = unvisited.takeFirst();
if (visited.contains(cur.map->name))
if (visited.contains(cur.map->name()))
continue;
visited.insert(cur.map->name);
visited.insert(cur.map->name());
stitchedMaps.append(cur);
for (MapConnection *connection : cur.map->getConnections()) {
@ -361,7 +362,7 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress, bool inclu
pixelX -= STITCH_MODE_BORDER_DISTANCE * 16;
pixelY -= STITCH_MODE_BORDER_DISTANCE * 16;
}
QPixmap pixmap = this->getFormattedMapPixmap(map.map);
QPixmap pixmap = getFormattedMapPixmap(map.map);
painter.drawPixmap(pixelX, pixelY, pixmap);
}
@ -382,7 +383,7 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress, bool inclu
int pixelX = (map.x - minX) * 16;
int pixelY = (map.y - minY) * 16;
QPixmap pixmapWithoutBorders = this->getFormattedMapPixmap(map.map, true);
QPixmap pixmapWithoutBorders = getFormattedMapPixmap(map.map, true);
painter.drawPixmap(pixelX, pixelY, pixmapWithoutBorders);
}
}
@ -391,56 +392,50 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress, bool inclu
}
void MapImageExporter::updatePreview() {
if (this->scene) {
delete this->scene;
this->scene = nullptr;
if (m_scene) {
delete m_scene;
m_scene = nullptr;
}
this->scene = new QGraphicsScene;
m_scene = new QGraphicsScene;
if (this->mode == ImageExporterMode::Stitch) {
if (m_mode == ImageExporterMode::Stitch) {
QProgressDialog progress("Building map stitch...", "Cancel", 0, 1, this);
progress.setAutoClose(true);
progress.setWindowModality(Qt::WindowModal);
progress.setModal(true);
progress.setMinimumDuration(1000);
this->preview = getStitchedImage(&progress, this->settings.showBorder);
m_preview = getStitchedImage(&progress, m_settings.showBorder);
progress.close();
} else {
// Timelapse mode doesn't currently have a real preview. It just displays the current map as in Normal mode.
this->preview = getFormattedMapPixmap(this->map);
m_preview = getFormattedMapPixmap();
}
this->scene->addPixmap(this->preview);
ui->graphicsView_Preview->setScene(scene);
m_scene->addPixmap(m_preview);
ui->graphicsView_Preview->setScene(m_scene);
scalePreview();
}
void MapImageExporter::scalePreview() {
if (this->scene && !this->settings.previewActualSize){
ui->graphicsView_Preview->fitInView(this->scene->sceneRect(), Qt::KeepAspectRatioByExpanding);
if (m_scene && !m_settings.previewActualSize){
ui->graphicsView_Preview->fitInView(m_scene->sceneRect(), Qt::KeepAspectRatioByExpanding);
}
}
// THIS
QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) {
QPixmap pixmap;
QPixmap MapImageExporter::getFormattedMapPixmap() {
return m_map ? getFormattedMapPixmap(m_map) : getFormattedLayoutPixmap(m_layout);
}
Layout *layout;
QPixmap MapImageExporter::getFormattedLayoutPixmap(Layout *layout, bool ignoreBorder, bool ignoreGrid) {
if (!layout)
return QPixmap();
// draw background layer / base image
if (!this->map) {
layout = this->layout;
layout->render(true);
pixmap = layout->pixmap;
} else {
layout = map->layout;
map->layout->render(true);
pixmap = map->layout->pixmap;
}
layout->render(true);
QPixmap pixmap = layout->pixmap;
if (this->settings.showCollision) {
if (m_settings.showCollision) {
QPainter collisionPainter(&pixmap);
layout->renderCollision(true);
collisionPainter.setOpacity(editor->collisionOpacity);
collisionPainter.setOpacity(m_editor->collisionOpacity);
collisionPainter.drawPixmap(0, 0, layout->collision_pixmap);
collisionPainter.end();
}
@ -448,11 +443,11 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) {
// draw map border
// note: this will break when allowing map to be selected from drop down maybe
int borderHeight = 0, borderWidth = 0;
if (!ignoreBorder && this->settings.showBorder) {
int borderDistance = this->mode ? STITCH_MODE_BORDER_DISTANCE : BORDER_DISTANCE;
if (!ignoreBorder && m_settings.showBorder) {
int borderDistance = m_mode ? STITCH_MODE_BORDER_DISTANCE : BORDER_DISTANCE;
layout->renderBorder();
int borderHorzDist = editor->getBorderDrawDistance(layout->getBorderWidth());
int borderVertDist = editor->getBorderDrawDistance(layout->getBorderHeight());
int borderHorzDist = m_editor->getBorderDrawDistance(layout->getBorderWidth());
int borderVertDist = m_editor->getBorderDrawDistance(layout->getBorderHeight());
borderWidth = borderDistance * 16;
borderHeight = borderDistance * 16;
QPixmap newPixmap = QPixmap(layout->pixmap.width() + borderWidth * 2, layout->pixmap.height() + borderHeight * 2);
@ -467,20 +462,34 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) {
pixmap = newPixmap;
}
if (!this->map) {
return pixmap;
}
// The grid should be painted last, so if this layout pixmap is being painted
// as part of a map (which has more to paint after this) then don't paint the grid yet.
if (!ignoreGrid)
paintGrid(&pixmap, ignoreBorder);
if (!ignoreBorder && (this->settings.showUpConnections || this->settings.showDownConnections || this->settings.showLeftConnections || this->settings.showRightConnections)) {
return pixmap;
}
QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) {
if (!map)
return QPixmap();
QPixmap pixmap = getFormattedLayoutPixmap(map->layout(), ignoreBorder, true);
if (!ignoreBorder && (m_settings.showUpConnections || m_settings.showDownConnections || m_settings.showLeftConnections || m_settings.showRightConnections)) {
// if showing connections, draw on outside of image
QPainter connectionPainter(&pixmap);
int borderDistance = m_mode ? STITCH_MODE_BORDER_DISTANCE : BORDER_DISTANCE;
int borderWidth = borderDistance * 16;
int borderHeight = borderDistance * 16;
// TODO: Reading the connections from the editor and not 'map' is incorrect.
for (auto connectionItem : editor->connection_items) {
for (auto connectionItem : m_editor->connection_items) {
const QString direction = connectionItem->connection->direction();
if ((this->settings.showUpConnections && direction == "up")
|| (this->settings.showDownConnections && direction == "down")
|| (this->settings.showLeftConnections && direction == "left")
|| (this->settings.showRightConnections && direction == "right"))
if ((m_settings.showUpConnections && direction == "up")
|| (m_settings.showDownConnections && direction == "down")
|| (m_settings.showLeftConnections && direction == "left")
|| (m_settings.showRightConnections && direction == "right"))
connectionPainter.drawImage(connectionItem->x() + borderWidth, connectionItem->y() + borderHeight,
connectionItem->connection->getPixmap().toImage());
}
@ -488,37 +497,43 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) {
}
// draw events
if (this->settings.showObjects || this->settings.showWarps || this->settings.showBGs || this->settings.showTriggers || this->settings.showHealLocations) {
if (m_settings.showObjects || m_settings.showWarps || m_settings.showBGs || m_settings.showTriggers || m_settings.showHealLocations) {
QPainter eventPainter(&pixmap);
int pixelOffset = 0;
if (!ignoreBorder && this->settings.showBorder) {
pixelOffset = this->mode == ImageExporterMode::Normal ? BORDER_DISTANCE * 16 : STITCH_MODE_BORDER_DISTANCE * 16;
if (!ignoreBorder && m_settings.showBorder) {
pixelOffset = m_mode == ImageExporterMode::Normal ? BORDER_DISTANCE * 16 : STITCH_MODE_BORDER_DISTANCE * 16;
}
const QList<Event *> events = map->getAllEvents();
const QList<Event *> events = map->getEvents();
for (const auto &event : events) {
Event::Group group = event->getEventGroup();
if ((this->settings.showObjects && group == Event::Group::Object)
|| (this->settings.showWarps && group == Event::Group::Warp)
|| (this->settings.showBGs && group == Event::Group::Bg)
|| (this->settings.showTriggers && group == Event::Group::Coord)
|| (this->settings.showHealLocations && group == Event::Group::Heal)) {
editor->project->setEventPixmap(event);
if ((m_settings.showObjects && group == Event::Group::Object)
|| (m_settings.showWarps && group == Event::Group::Warp)
|| (m_settings.showBGs && group == Event::Group::Bg)
|| (m_settings.showTriggers && group == Event::Group::Coord)
|| (m_settings.showHealLocations && group == Event::Group::Heal)) {
m_editor->project->setEventPixmap(event);
eventPainter.drawImage(QPoint(event->getPixelX() + pixelOffset, event->getPixelY() + pixelOffset), event->getPixmap().toImage());
}
}
eventPainter.end();
}
paintGrid(&pixmap, ignoreBorder);
return pixmap;
}
void MapImageExporter::paintGrid(QPixmap *pixmap, bool ignoreBorder) {
// draw grid directly onto the pixmap
// since the last grid lines are outside of the pixmap, add a pixel to the bottom and right
if (this->settings.showGrid) {
if (m_settings.showGrid) {
bool hasBorder = !ignoreBorder && m_settings.showBorder;
int addX = 1, addY = 1;
if (borderHeight) addY = 0;
if (borderWidth) addX = 0;
if (hasBorder) addY = 0;
if (hasBorder) addX = 0;
QPixmap newPixmap= QPixmap(pixmap.width() + addX, pixmap.height() + addY);
QPixmap newPixmap= QPixmap(pixmap->width() + addX, pixmap->height() + addY);
QPainter gridPainter(&newPixmap);
gridPainter.drawImage(QPoint(0, 0), pixmap.toImage());
gridPainter.drawImage(QPoint(0, 0), pixmap->toImage());
for (int x = 0; x < newPixmap.width(); x += 16) {
gridPainter.drawLine(x, 0, x, newPixmap.height());
}
@ -526,58 +541,56 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) {
gridPainter.drawLine(0, y, newPixmap.width(), y);
}
gridPainter.end();
pixmap = newPixmap;
*pixmap = newPixmap;
}
return pixmap;
}
void MapImageExporter::updateShowBorderState() {
// If any of the Connections settings are enabled then this setting is locked (it's implicitly enabled)
bool on = (this->settings.showUpConnections || this->settings.showDownConnections || this->settings.showLeftConnections || this->settings.showRightConnections);
bool on = (m_settings.showUpConnections || m_settings.showDownConnections || m_settings.showLeftConnections || m_settings.showRightConnections);
const QSignalBlocker blocker(ui->checkBox_Border);
ui->checkBox_Border->setChecked(on);
ui->checkBox_Border->setDisabled(on);
this->settings.showBorder = on;
m_settings.showBorder = on;
}
void MapImageExporter::on_checkBox_Elevation_stateChanged(int state) {
this->settings.showCollision = (state == Qt::Checked);
m_settings.showCollision = (state == Qt::Checked);
updatePreview();
}
void MapImageExporter::on_checkBox_Grid_stateChanged(int state) {
this->settings.showGrid = (state == Qt::Checked);
m_settings.showGrid = (state == Qt::Checked);
updatePreview();
}
void MapImageExporter::on_checkBox_Border_stateChanged(int state) {
this->settings.showBorder = (state == Qt::Checked);
m_settings.showBorder = (state == Qt::Checked);
updatePreview();
}
void MapImageExporter::on_checkBox_Objects_stateChanged(int state) {
this->settings.showObjects = (state == Qt::Checked);
m_settings.showObjects = (state == Qt::Checked);
updatePreview();
}
void MapImageExporter::on_checkBox_Warps_stateChanged(int state) {
this->settings.showWarps = (state == Qt::Checked);
m_settings.showWarps = (state == Qt::Checked);
updatePreview();
}
void MapImageExporter::on_checkBox_BGs_stateChanged(int state) {
this->settings.showBGs = (state == Qt::Checked);
m_settings.showBGs = (state == Qt::Checked);
updatePreview();
}
void MapImageExporter::on_checkBox_Triggers_stateChanged(int state) {
this->settings.showTriggers = (state == Qt::Checked);
m_settings.showTriggers = (state == Qt::Checked);
updatePreview();
}
void MapImageExporter::on_checkBox_HealLocations_stateChanged(int state) {
this->settings.showHealLocations = (state == Qt::Checked);
m_settings.showHealLocations = (state == Qt::Checked);
updatePreview();
}
@ -588,51 +601,51 @@ void MapImageExporter::on_checkBox_AllEvents_stateChanged(int state) {
const QSignalBlocker b_Objects(ui->checkBox_Objects);
ui->checkBox_Objects->setChecked(on);
ui->checkBox_Objects->setDisabled(on);
this->settings.showObjects = on;
m_settings.showObjects = on;
const QSignalBlocker b_Warps(ui->checkBox_Warps);
ui->checkBox_Warps->setChecked(on);
ui->checkBox_Warps->setDisabled(on);
this->settings.showWarps = on;
m_settings.showWarps = on;
const QSignalBlocker b_BGs(ui->checkBox_BGs);
ui->checkBox_BGs->setChecked(on);
ui->checkBox_BGs->setDisabled(on);
this->settings.showBGs = on;
m_settings.showBGs = on;
const QSignalBlocker b_Triggers(ui->checkBox_Triggers);
ui->checkBox_Triggers->setChecked(on);
ui->checkBox_Triggers->setDisabled(on);
this->settings.showTriggers = on;
m_settings.showTriggers = on;
const QSignalBlocker b_HealLocations(ui->checkBox_HealLocations);
ui->checkBox_HealLocations->setChecked(on);
ui->checkBox_HealLocations->setDisabled(on);
this->settings.showHealLocations = on;
m_settings.showHealLocations = on;
updatePreview();
}
void MapImageExporter::on_checkBox_ConnectionUp_stateChanged(int state) {
this->settings.showUpConnections = (state == Qt::Checked);
m_settings.showUpConnections = (state == Qt::Checked);
updateShowBorderState();
updatePreview();
}
void MapImageExporter::on_checkBox_ConnectionDown_stateChanged(int state) {
this->settings.showDownConnections = (state == Qt::Checked);
m_settings.showDownConnections = (state == Qt::Checked);
updateShowBorderState();
updatePreview();
}
void MapImageExporter::on_checkBox_ConnectionLeft_stateChanged(int state) {
this->settings.showLeftConnections = (state == Qt::Checked);
m_settings.showLeftConnections = (state == Qt::Checked);
updateShowBorderState();
updatePreview();
}
void MapImageExporter::on_checkBox_ConnectionRight_stateChanged(int state) {
this->settings.showRightConnections = (state == Qt::Checked);
m_settings.showRightConnections = (state == Qt::Checked);
updateShowBorderState();
updatePreview();
}
@ -644,30 +657,30 @@ void MapImageExporter::on_checkBox_AllConnections_stateChanged(int state) {
const QSignalBlocker b_Up(ui->checkBox_ConnectionUp);
ui->checkBox_ConnectionUp->setChecked(on);
ui->checkBox_ConnectionUp->setDisabled(on);
this->settings.showUpConnections = on;
m_settings.showUpConnections = on;
const QSignalBlocker b_Down(ui->checkBox_ConnectionDown);
ui->checkBox_ConnectionDown->setChecked(on);
ui->checkBox_ConnectionDown->setDisabled(on);
this->settings.showDownConnections = on;
m_settings.showDownConnections = on;
const QSignalBlocker b_Left(ui->checkBox_ConnectionLeft);
ui->checkBox_ConnectionLeft->setChecked(on);
ui->checkBox_ConnectionLeft->setDisabled(on);
this->settings.showLeftConnections = on;
m_settings.showLeftConnections = on;
const QSignalBlocker b_Right(ui->checkBox_ConnectionRight);
ui->checkBox_ConnectionRight->setChecked(on);
ui->checkBox_ConnectionRight->setDisabled(on);
this->settings.showRightConnections = on;
m_settings.showRightConnections = on;
updateShowBorderState();
updatePreview();
}
void MapImageExporter::on_checkBox_ActualSize_stateChanged(int state) {
this->settings.previewActualSize = (state == Qt::Checked);
if (this->settings.previewActualSize) {
m_settings.previewActualSize = (state == Qt::Checked);
if (m_settings.previewActualSize) {
ui->graphicsView_Preview->resetTransform();
} else {
scalePreview();
@ -675,20 +688,20 @@ void MapImageExporter::on_checkBox_ActualSize_stateChanged(int state) {
}
void MapImageExporter::on_pushButton_Reset_pressed() {
this->settings = {};
m_settings = {};
for (auto widget : this->findChildren<QCheckBox *>()) {
const QSignalBlocker b(widget); // Prevent calls to updatePreview
widget->setChecked(false);
}
ui->spinBox_TimelapseDelay->setValue(this->settings.timelapseDelayMs);
ui->spinBox_FrameSkip->setValue(this->settings.timelapseSkipAmount);
ui->spinBox_TimelapseDelay->setValue(m_settings.timelapseDelayMs);
ui->spinBox_FrameSkip->setValue(m_settings.timelapseSkipAmount);
updatePreview();
}
void MapImageExporter::on_spinBox_TimelapseDelay_valueChanged(int delayMs) {
this->settings.timelapseDelayMs = delayMs;
m_settings.timelapseDelayMs = delayMs;
}
void MapImageExporter::on_spinBox_FrameSkip_valueChanged(int skip) {
this->settings.timelapseSkipAmount = skip;
m_settings.timelapseSkipAmount = skip;
}

View File

@ -1,11 +1,11 @@
#include "maplistmodels.h"
#include "validator.h"
#include "project.h"
#include "filterchildrenproxymodel.h"
#include <QMouseEvent>
#include <QLineEdit>
#include "project.h"
#include "filterchildrenproxymodel.h"
void MapTree::removeSelected() {
@ -38,8 +38,52 @@ void MapTree::keyPressEvent(QKeyEvent *event) {
}
}
MapListModel::MapListModel(Project *project, QObject *parent) : QStandardItemModel(parent) {
this->project = project;
this->root = invisibleRootItem();
this->mapGrayIcon = QIcon(QStringLiteral(":/icons/map_grayed.ico"));
this->mapIcon = QIcon(QStringLiteral(":/icons/map.ico"));
this->mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico"));
this->mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico"));
this->mapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off);
this->mapFolderIcon.addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On);
this->emptyMapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed.ico"), QSize(), QIcon::Normal, QIcon::Off);
this->emptyMapFolderIcon.addFile(QStringLiteral(":/icons/folder.ico"), QSize(), QIcon::Normal, QIcon::On);
}
QStandardItem *MapListModel::itemAt(const QModelIndex &index) const {
if (index.isValid()) {
QStandardItem *item = static_cast<QStandardItem*>(index.internalPointer());
if (item)
return item;
}
return this->root;
}
QStandardItem *MapListModel::itemAt(const QString &itemName) const {
QModelIndex index = this->indexOf(itemName);
if (!index.isValid())
return nullptr;
return this->itemAt(index)->child(index.row(), index.column());
}
QModelIndex MapListModel::indexOf(const QString &itemName) const {
if (this->mapItems.contains(itemName))
return this->mapItems.value(itemName)->index();
if (this->mapFolderItems.contains(itemName))
return this->mapFolderItems.value(itemName)->index();
return QModelIndex();
}
void MapListModel::removeItemAt(const QModelIndex &index) {
QStandardItem *item = this->getItem(index)->child(index.row(), index.column());
QStandardItem *item = this->itemAt(index)->child(index.row(), index.column());
if (!item)
return;
@ -49,24 +93,106 @@ void MapListModel::removeItemAt(const QModelIndex &index) {
} else {
// TODO: Because there's no support for deleting maps we can only delete empty folders
if (!item->hasChildren()) {
this->removeItem(item);
removeItem(item);
}
}
}
QStandardItem *MapListModel::createMapItem(const QString &mapName, QStandardItem *map) {
if (!map) map = new QStandardItem;
map->setText(mapName);
map->setData(mapName, MapListUserRoles::NameRole);
map->setData("map_name", MapListUserRoles::TypeRole);
map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemNeverHasChildren);
map->setToolTip(this->project->mapNamesToMapConstants.value(mapName));
this->mapItems.insert(mapName, map);
return map;
}
QStandardItem *MapListModel::createMapFolderItem(const QString &folderName, QStandardItem *folder) {
if (!folder) folder = new QStandardItem;
folder->setText(folderName);
folder->setData(folderName, MapListUserRoles::NameRole);
folder->setData(this->folderTypeName, MapListUserRoles::TypeRole);
folder->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled);
folder->setEditable(this->editable); // Will override flags if necessary
this->mapFolderItems.insert(folderName, folder);
return folder;
}
QStandardItem *MapListModel::insertMapItem(const QString &mapName, const QString &folderName) {
if (mapName.isEmpty() || mapName == this->project->getDynamicMapName()) // Disallow adding MAP_DYNAMIC to the map list.
return nullptr;
QStandardItem *map = createMapItem(mapName);
QStandardItem *folder = this->mapFolderItems[folderName];
if (!folder) {
// Folder doesn't exist yet, add it.
folder = insertMapFolderItem(folderName);
}
// If folder is still nullptr here it's because we failed to create it.
if (folder) {
folder->appendRow(map);
}
if (this->sortingEnabled)
this->sort(0, Qt::AscendingOrder);
return map;
}
QStandardItem *MapListModel::insertMapFolderItem(const QString &folderName) {
if (folderName.isEmpty())
return nullptr;
QStandardItem *item = createMapFolderItem(folderName);
this->root->appendRow(item);
if (this->sortingEnabled)
this->sort(0, Qt::AscendingOrder);
return item;
}
QVariant MapListModel::data(const QModelIndex &index, int role) const {
if (!index.isValid())
return QVariant();
int row = index.row();
int col = index.column();
const QStandardItem *item = this->itemAt(index)->child(row, col);
const QString type = item->data(MapListUserRoles::TypeRole).toString();
const QString name = item->data(MapListUserRoles::NameRole).toString();
if (role == Qt::DecorationRole) {
if (type == "map_name") {
// Decorating map in the map list
if (name == this->activeItemName)
return this->mapOpenedIcon;
const Map* map = this->project->mapCache.value(name);
if (!map)
return this->mapGrayIcon;
return map->hasUnsavedChanges() ? this->mapEditedIcon : this->mapIcon;
} else if (type == this->folderTypeName) {
// Decorating map folder in the map list
return item->hasChildren() ? this->mapFolderIcon : this->emptyMapFolderIcon;
}
}
return QStandardItemModel::data(index, role);
}
QWidget *GroupNameDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const {
QLineEdit *editor = new QLineEdit(parent);
static const QRegularExpression expression("[A-Za-z_]+[\\w]*");
editor->setPlaceholderText("gMapGroup_");
editor->setValidator(new QRegularExpressionValidator(expression, parent));
editor->setPlaceholderText(Project::getMapGroupPrefix());
editor->setValidator(new IdentifierValidator(parent));
editor->setFrame(false);
return editor;
}
void GroupNameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const {
QString groupName = index.data(Qt::UserRole).toString();
QString groupName = index.data(MapListUserRoles::NameRole).toString();
QLineEdit *le = static_cast<QLineEdit *>(editor);
le->setText(groupName);
}
@ -74,7 +200,7 @@ void GroupNameDelegate::setEditorData(QWidget *editor, const QModelIndex &index)
void GroupNameDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const {
QLineEdit *le = static_cast<QLineEdit *>(editor);
QString groupName = le->text();
model->setData(index, groupName, Qt::UserRole);
model->setData(index, groupName, MapListUserRoles::NameRole);
}
void GroupNameDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &) const {
@ -83,11 +209,16 @@ void GroupNameDelegate::updateEditorGeometry(QWidget *editor, const QStyleOption
MapGroupModel::MapGroupModel(Project *project, QObject *parent) : MapListModel(parent) {
this->project = project;
this->root = this->invisibleRootItem();
MapGroupModel::MapGroupModel(Project *project, QObject *parent) : MapListModel(project, parent) {
this->folderTypeName = "map_group";
this->editable = true;
initialize();
for (const auto &groupName : this->project->groupNames) {
insertMapFolderItem(groupName);
for (const auto &mapName : this->project->groupNameToMapNames.value(groupName)) {
insertMapItem(mapName, groupName);
}
}
}
Qt::DropActions MapGroupModel::supportedDropActions() const {
@ -112,7 +243,7 @@ QMimeData *MapGroupModel::mimeData(const QModelIndexList &indexes) const {
// if dropping a selection containing a group(s) and map(s), clear all selection but first group.
for (const QModelIndex &index : indexes) {
if (index.isValid() && data(index, MapListUserRoles::TypeRole).toString() == "map_group") {
QString groupName = data(index, Qt::UserRole).toString();
QString groupName = data(index, MapListUserRoles::NameRole).toString();
stream << groupName;
mimeData->setData("application/porymap.mapgroupmodel.group", encodedData);
mimeData->setData("application/porymap.mapgroupmodel.source.row", QByteArray::number(index.row()));
@ -122,7 +253,7 @@ QMimeData *MapGroupModel::mimeData(const QModelIndexList &indexes) const {
for (const QModelIndex &index : indexes) {
if (index.isValid()) {
QString mapName = data(index, Qt::UserRole).toString();
QString mapName = data(index, MapListUserRoles::NameRole).toString();
stream << mapName;
}
}
@ -168,12 +299,12 @@ bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i
QStringList mapsToMove;
for (int i = 0; i < this->rowCount(originIndex); ++i ) {
children << this->index( i, 0, originIndex);
mapsToMove << this->index( i, 0 , originIndex).data(Qt::UserRole).toString();
mapsToMove << this->index( i, 0 , originIndex).data(MapListUserRoles::NameRole).toString();
}
QModelIndex groupIndex = index(row, 0, parentIndex);
QStandardItem *groupItem = this->itemFromIndex(groupIndex);
createGroupItem(groupName, row, groupItem);
createMapFolderItem(groupName, groupItem);
for (QString mapName : mapsToMove) {
QStandardItem *mapItem = createMapItem(mapName);
@ -224,177 +355,63 @@ bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i
void MapGroupModel::updateProject() {
if (!this->project) return;
QStringList groupNames;
QMap<QString, int> mapGroups;
QList<QStringList> groupedMapNames;
// Temporary objects in case of failure, so we won't modify the project unless it succeeds.
QStringList mapNames;
QStringList groupNames;
QMap<QString, QStringList> groupNameToMapNames;
for (int g = 0; g < this->root->rowCount(); g++) {
QStandardItem *groupItem = this->item(g);
QString groupName = groupItem->data(Qt::UserRole).toString();
const QStandardItem *groupItem = this->item(g);
QString groupName = groupItem->data(MapListUserRoles::NameRole).toString();
groupNames.append(groupName);
mapGroups[groupName] = g;
QStringList mapsInGroup;
for (int m = 0; m < groupItem->rowCount(); m++) {
QStandardItem *mapItem = groupItem->child(m);
const QStandardItem *mapItem = groupItem->child(m);
if (!mapItem) {
logError("An error occured while trying to apply updates to map group structure.");
return;
}
QString mapName = mapItem->data(Qt::UserRole).toString();
mapsInGroup.append(mapName);
QString mapName = mapItem->data(MapListUserRoles::NameRole).toString();
groupNameToMapNames[groupName].append(mapName);
mapNames.append(mapName);
}
groupedMapNames.append(mapsInGroup);
}
this->project->groupNames = groupNames;
this->project->mapGroups = mapGroups;
this->project->groupedMapNames = groupedMapNames;
this->project->mapNames = mapNames;
this->project->groupNames = groupNames;
this->project->groupNameToMapNames = groupNameToMapNames;
this->project->hasUnsavedDataChanges = true;
}
QStandardItem *MapGroupModel::createGroupItem(QString groupName, int groupIndex, QStandardItem *group) {
if (!group) group = new QStandardItem;
group->setText(groupName);
group->setData(groupName, Qt::UserRole);
group->setData("map_group", MapListUserRoles::TypeRole);
group->setData(groupIndex, MapListUserRoles::GroupRole);
group->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsSelectable);
this->groupItems.insert(groupName, group);
return group;
}
QStandardItem *MapGroupModel::createMapItem(QString mapName, QStandardItem *map) {
if (!map) map = new QStandardItem;
map->setData(mapName, Qt::UserRole);
map->setData("map_name", MapListUserRoles::TypeRole);
map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
this->mapItems[mapName] = map;
return map;
}
QStandardItem *MapGroupModel::insertGroupItem(QString groupName) {
QStandardItem *group = createGroupItem(groupName, this->groupItems.size());
this->root->appendRow(group);
this->updateProject();
return group;
}
void MapGroupModel::removeItem(QStandardItem *item) {
this->removeRow(item->row());
this->updateProject();
}
QStandardItem *MapGroupModel::insertMapItem(QString mapName, QString groupName) {
QStandardItem *group = this->groupItems[groupName];
if (!group) {
group = insertGroupItem(groupName);
}
QStandardItem *map = createMapItem(mapName);
group->appendRow(map);
return map;
}
void MapGroupModel::initialize() {
this->groupItems.clear();
this->mapItems.clear();
for (int i = 0; i < this->project->groupNames.length(); i++) {
QString group_name = this->project->groupNames.value(i);
QStandardItem *group = createGroupItem(group_name, i);
root->appendRow(group);
QStringList names = this->project->groupedMapNames.value(i);
for (int j = 0; j < names.length(); j++) {
QString map_name = names.value(j);
QStandardItem *map = createMapItem(map_name);
group->appendRow(map);
}
}
}
QStandardItem *MapGroupModel::getItem(const QModelIndex &index) const {
if (index.isValid()) {
QStandardItem *item = static_cast<QStandardItem*>(index.internalPointer());
if (item)
return item;
}
return this->root;
}
QModelIndex MapGroupModel::indexOf(QString mapName) const {
if (this->mapItems.contains(mapName)) {
return this->mapItems[mapName]->index();
}
return QModelIndex();
}
QVariant MapGroupModel::data(const QModelIndex &index, int role) const {
if (!index.isValid()) return QVariant();
if (!index.isValid())
return QVariant();
int row = index.row();
int col = index.column();
if (role == Qt::DecorationRole) {
static QIcon mapGrayIcon = QIcon(QStringLiteral(":/icons/map_grayed.ico"));
static QIcon mapIcon = QIcon(QStringLiteral(":/icons/map.ico"));
static QIcon mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico"));
static QIcon mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico"));
static QIcon mapFolderIcon;
static QIcon folderIcon;
static bool loaded = false;
if (!loaded) {
mapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off);
mapFolderIcon.addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On);
folderIcon.addFile(QStringLiteral(":/icons/folder_closed.ico"), QSize(), QIcon::Normal, QIcon::Off);
folderIcon.addFile(QStringLiteral(":/icons/folder.ico"), QSize(), QIcon::Normal, QIcon::On);
loaded = true;
}
QStandardItem *item = this->getItem(index)->child(row, col);
QString type = item->data(MapListUserRoles::TypeRole).toString();
if (type == "map_group") {
if (!item->hasChildren()) {
return folderIcon;
}
return mapFolderIcon;
} else if (type == "map_name") {
QString mapName = item->data(Qt::UserRole).toString();
if (mapName == this->openMap) {
return mapOpenedIcon;
}
else if (this->project->mapCache.contains(mapName)) {
if (this->project->mapCache.value(mapName)->hasUnsavedChanges()) {
return mapEditedIcon;
}
else {
return mapIcon;
}
}
return mapGrayIcon;
}
}
else if (role == Qt::DisplayRole) {
QStandardItem *item = this->getItem(index)->child(row, col);
QString type = item->data(MapListUserRoles::TypeRole).toString();
const QStandardItem *item = this->itemAt(index)->child(row, col);
const QString type = item->data(MapListUserRoles::TypeRole).toString();
const QString name = item->data(MapListUserRoles::NameRole).toString();
if (role == Qt::DisplayRole) {
if (type == "map_name") {
return QString("[%1.%2] ").arg(this->getItem(index)->row()).arg(row, 2, 10, QLatin1Char('0')) + item->data(Qt::UserRole).toString();
return QString("[%1.%2] ").arg(this->itemAt(index)->row()).arg(row, 2, 10, QLatin1Char('0')) + name;
}
else if (type == "map_group") {
return item->data(Qt::UserRole).toString();
else if (type == this->folderTypeName) {
return name;
}
}
return QStandardItemModel::data(index, role);
return MapListModel::data(index, role);
}
bool MapGroupModel::setData(const QModelIndex &index, const QVariant &value, int role) {
if (role == Qt::UserRole && data(index, MapListUserRoles::TypeRole).toString() == "map_group") {
// verify uniqueness of new group name
if (this->project->groupNames.contains(value.toString())) {
if (role == MapListUserRoles::NameRole && data(index, MapListUserRoles::TypeRole).toString() == "map_group") {
if (!this->project->isIdentifierUnique(value.toString())) {
return false;
}
}
@ -406,287 +423,87 @@ bool MapGroupModel::setData(const QModelIndex &index, const QVariant &value, int
MapAreaModel::MapAreaModel(Project *project, QObject *parent) : MapListModel(parent) {
this->project = project;
this->root = this->invisibleRootItem();
MapLocationModel::MapLocationModel(Project *project, QObject *parent) : MapListModel(project, parent) {
this->folderTypeName = "map_section";
initialize();
}
QStandardItem *MapAreaModel::createAreaItem(QString mapsecName) {
QStandardItem *area = new QStandardItem;
area->setText(mapsecName);
area->setEditable(false);
area->setData(mapsecName, Qt::UserRole);
area->setData("map_section", MapListUserRoles::TypeRole);
// group->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
this->areaItems.insert(mapsecName, area);
return area;
}
QStandardItem *MapAreaModel::createMapItem(QString mapName, int, int) {
QStandardItem *map = new QStandardItem;
map->setText(mapName);
map->setEditable(false);
map->setData(mapName, Qt::UserRole);
map->setData("map_name", MapListUserRoles::TypeRole);
// map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
this->mapItems.insert(mapName, map);
return map;
}
QStandardItem *MapAreaModel::insertAreaItem(QString areaName) {
this->project->addNewMapsec(areaName);
QStandardItem *item = createAreaItem(areaName);
this->root->appendRow(item);
this->sort(0, Qt::AscendingOrder);
return item;
}
QStandardItem *MapAreaModel::insertMapItem(QString mapName, QString areaName, int groupIndex) {
QStandardItem *area = this->areaItems[areaName];
if (!area) {
return nullptr;
for (const auto &idName : this->project->mapSectionIdNames) {
insertMapFolderItem(idName);
}
int mapIndex = area->rowCount();
QStandardItem *map = createMapItem(mapName, groupIndex, mapIndex);
area->appendRow(map);
return map;
for (const auto &mapName : this->project->mapNames) {
insertMapItem(mapName, this->project->mapNameToMapSectionName.value(mapName));
}
this->sortingEnabled = true;
sort(0, Qt::AscendingOrder);
}
void MapAreaModel::removeItem(QStandardItem *item) {
this->project->removeMapsec(item->data(Qt::UserRole).toString());
void MapLocationModel::removeItem(QStandardItem *item) {
this->project->removeMapsec(item->data(MapListUserRoles::NameRole).toString());
this->removeRow(item->row());
}
void MapAreaModel::initialize() {
this->areaItems.clear();
this->mapItems.clear();
for (const auto &idName : this->project->mapSectionIdNames) {
this->root->appendRow(createAreaItem(idName));
}
for (int i = 0; i < this->project->groupNames.length(); i++) {
QStringList names = this->project->groupedMapNames.value(i);
for (int j = 0; j < names.length(); j++) {
QString mapName = names.value(j);
QStandardItem *map = createMapItem(mapName, i, j);
QString mapsecName = this->project->readMapLocation(mapName);
if (this->areaItems.contains(mapsecName)) {
this->areaItems[mapsecName]->appendRow(map);
}
}
}
this->sort(0, Qt::AscendingOrder);
}
QStandardItem *MapAreaModel::getItem(const QModelIndex &index) const {
if (index.isValid()) {
QStandardItem *item = static_cast<QStandardItem*>(index.internalPointer());
if (item)
return item;
}
return this->root;
}
QModelIndex MapAreaModel::indexOf(QString mapName) const {
if (this->mapItems.contains(mapName)) {
return this->mapItems[mapName]->index();
}
return QModelIndex();
}
QVariant MapAreaModel::data(const QModelIndex &index, int role) const {
if (!index.isValid()) return QVariant();
int row = index.row();
int col = index.column();
if (role == Qt::DecorationRole) {
static QIcon mapGrayIcon = QIcon(QStringLiteral(":/icons/map_grayed.ico"));
static QIcon mapIcon = QIcon(QStringLiteral(":/icons/map.ico"));
static QIcon mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico"));
static QIcon mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico"));
static QIcon mapFolderIcon;
static QIcon folderIcon;
static bool loaded = false;
if (!loaded) {
mapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off);
mapFolderIcon.addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On);
folderIcon.addFile(QStringLiteral(":/icons/folder_closed.ico"), QSize(), QIcon::Normal, QIcon::Off);
folderIcon.addFile(QStringLiteral(":/icons/folder.ico"), QSize(), QIcon::Normal, QIcon::On);
loaded = true;
}
QStandardItem *item = this->getItem(index)->child(row, col);
QString type = item->data(MapListUserRoles::TypeRole).toString();
if (type == "map_section") {
if (item->hasChildren()) {
return mapFolderIcon;
}
return folderIcon;
} else if (type == "map_name") {
QString mapName = item->data(Qt::UserRole).toString();
if (mapName == this->openMap) {
return mapOpenedIcon;
}
else if (this->project->mapCache.contains(mapName)) {
if (this->project->mapCache.value(mapName)->hasUnsavedChanges()) {
return mapEditedIcon;
}
else {
return mapIcon;
}
}
return mapGrayIcon;
}
}
else if (role == Qt::DisplayRole) {
QStandardItem *item = this->getItem(index)->child(row, col);
QString type = item->data(MapListUserRoles::TypeRole).toString();
if (type == "map_section") {
return item->data(Qt::UserRole).toString();
}
}
return QStandardItemModel::data(index, role);
QStandardItem *MapLocationModel::createMapFolderItem(const QString &folderName, QStandardItem *folder) {
folder = MapListModel::createMapFolderItem(folderName, folder);
folder->setToolTip(this->project->getMapsecDisplayName(folderName));
return folder;
}
LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : MapListModel(parent) {
this->project = project;
this->root = this->invisibleRootItem();
LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : MapListModel(project, parent) {
this->folderTypeName = "map_layout";
initialize();
}
QStandardItem *LayoutTreeModel::createLayoutItem(QString layoutId) {
QStandardItem *layout = new QStandardItem;
layout->setText(this->project->layoutIdsToNames[layoutId]);
layout->setEditable(false);
layout->setData(layoutId, Qt::UserRole);
layout->setData("map_layout", MapListUserRoles::TypeRole);
// // group->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
this->layoutItems.insert(layoutId, layout);
return layout;
}
QStandardItem *LayoutTreeModel::createMapItem(QString mapName) {
QStandardItem *map = new QStandardItem;
map->setText(mapName);
map->setEditable(false);
map->setData(mapName, Qt::UserRole);
map->setData("map_name", MapListUserRoles::TypeRole);
map->setFlags(Qt::NoItemFlags | Qt::ItemNeverHasChildren);
this->mapItems.insert(mapName, map);
return map;
}
QStandardItem *LayoutTreeModel::insertLayoutItem(QString layoutId) {
QStandardItem *layoutItem = this->createLayoutItem(layoutId);
this->root->appendRow(layoutItem);
this->sort(0, Qt::AscendingOrder);
return layoutItem;
}
QStandardItem *LayoutTreeModel::insertMapItem(QString mapName, QString layoutId) {
QStandardItem *layout = nullptr;
if (this->layoutItems.contains(layoutId)) {
layout = this->layoutItems[layoutId];
for (const auto &layoutId : this->project->layoutIds) {
insertMapFolderItem(layoutId);
}
else {
layout = createLayoutItem(layoutId);
this->root->appendRow(layout);
for (const auto &mapName : this->project->mapNames) {
insertMapItem(mapName, this->project->mapNameToLayoutId.value(mapName));
}
if (!layout) {
return nullptr;
}
QStandardItem *map = createMapItem(mapName);
layout->appendRow(map);
return map;
this->sortingEnabled = true;
sort(0, Qt::AscendingOrder);
}
void LayoutTreeModel::removeItem(QStandardItem *) {
// TODO: Deleting layouts not supported
}
QStandardItem *LayoutTreeModel::createMapFolderItem(const QString &folderName, QStandardItem *folder) {
folder = MapListModel::createMapFolderItem(folderName, folder);
void LayoutTreeModel::initialize() {
this->layoutItems.clear();
this->mapItems.clear();
for (int i = 0; i < this->project->mapLayoutsTable.length(); i++) {
QString layoutId = project->mapLayoutsTable.value(i);
QStandardItem *layoutItem = createLayoutItem(layoutId);
this->root->appendRow(layoutItem);
}
// Despite using layout IDs internally, the Layouts map list shows layouts using their file path name.
// We could handle this with Qt::DisplayRole in LayoutTreeModel::data, but then it would be sorted using the ID instead of the name.
const Layout* layout = this->project->mapLayouts.value(folderName);
if (layout) folder->setText(layout->name);
for (auto mapList : this->project->groupedMapNames) {
for (auto mapName : mapList) {
QString layoutId = project->readMapLayoutId(mapName);
QStandardItem *map = createMapItem(mapName);
this->layoutItems[layoutId]->appendRow(map);
}
}
this->sort(0, Qt::AscendingOrder);
}
// The layout ID will instead be shown as a tool tip.
folder->setToolTip(folderName);
QStandardItem *LayoutTreeModel::getItem(const QModelIndex &index) const {
if (index.isValid()) {
QStandardItem *item = static_cast<QStandardItem*>(index.internalPointer());
if (item)
return item;
}
return this->root;
}
QModelIndex LayoutTreeModel::indexOf(QString layoutName) const {
if (this->layoutItems.contains(layoutName)) {
return this->layoutItems[layoutName]->index();
}
return QModelIndex();
return folder;
}
QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const {
if (!index.isValid()) return QVariant();
if (!index.isValid())
return QVariant();
int row = index.row();
int col = index.column();
if (role == Qt::DecorationRole) {
static QIcon mapGrayIcon = QIcon(QStringLiteral(":/icons/map_grayed.ico"));
static QIcon mapIcon = QIcon(QStringLiteral(":/icons/map.ico"));
static QIcon mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico"));
static QIcon mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico"));
const QStandardItem *item = this->itemAt(index)->child(row, col);
const QString type = item->data(MapListUserRoles::TypeRole).toString();
const QString name = item->data(MapListUserRoles::NameRole).toString();
QStandardItem *item = this->getItem(index)->child(row, col);
QString type = item->data(MapListUserRoles::TypeRole).toString();
if (type == this->folderTypeName) {
if (role == Qt::DecorationRole) {
// Map layouts are used as folders, but we display them with the same icons as maps.
if (name == this->activeItemName)
return this->mapOpenedIcon;
if (type == "map_layout") {
QString layoutId = item->data(Qt::UserRole).toString();
if (layoutId == this->openLayout) {
return mapOpenedIcon;
}
else if (this->project->mapLayouts.contains(layoutId)) {
if (this->project->mapLayouts.value(layoutId)->hasUnsavedChanges()) {
return mapEditedIcon;
}
else if (!this->project->mapLayouts[layoutId]->loaded) {
return mapGrayIcon;
}
}
return mapIcon;
const Layout* layout = this->project->mapLayouts.value(name);
if (!layout || !layout->loaded)
return this->mapGrayIcon;
return layout->hasUnsavedChanges() ? this->mapEditedIcon : this->mapIcon;
}
else if (type == "map_name") {
return QVariant();
}
return QVariant();
}
return QStandardItemModel::data(index, role);
return MapListModel::data(index, role);
}

78
src/ui/message.cpp Normal file
View File

@ -0,0 +1,78 @@
#include "message.h"
#include "log.h"
#include <QApplication>
Message::Message(QMessageBox::Icon icon, const QString &text, QMessageBox::StandardButtons buttons, QWidget *parent) :
QMessageBox(icon, QApplication::applicationName(), text, buttons, parent)
{
setWindowModality(Qt::WindowModal);
}
ErrorMessage::ErrorMessage(const QString &message, QWidget *parent) :
Message(QMessageBox::Critical,
message,
QMessageBox::Ok,
parent)
{
setDefaultButton(QMessageBox::Ok);
}
WarningMessage::WarningMessage(const QString &message, QWidget *parent) :
Message(QMessageBox::Warning,
message,
QMessageBox::Ok,
parent)
{
setDefaultButton(QMessageBox::Ok);
}
InfoMessage::InfoMessage(const QString &message, QWidget *parent) :
Message(QMessageBox::Information,
message,
QMessageBox::Close,
parent)
{
setDefaultButton(QMessageBox::Close);
}
QuestionMessage::QuestionMessage(const QString &message, QWidget *parent) :
Message(QMessageBox::Question,
message,
QMessageBox::No | QMessageBox::Yes,
parent)
{
setDefaultButton(QMessageBox::No);
}
RecentErrorMessage::RecentErrorMessage(const QString &message, QWidget *parent) :
ErrorMessage(message, parent)
{
setInformativeText(QString("Please see %1 for full error details.").arg(getLogPath()));
setDetailedText(getMostRecentError());
}
int RecentErrorMessage::show(const QString &message, QWidget *parent) {
RecentErrorMessage msgBox(message, parent);
return msgBox.exec();
};
int ErrorMessage::show(const QString &message, QWidget *parent) {
ErrorMessage msgBox(message, parent);
return msgBox.exec();
};
int WarningMessage::show(const QString &message, QWidget *parent) {
WarningMessage msgBox(message, parent);
return msgBox.exec();
};
int QuestionMessage::show(const QString &message, QWidget *parent) {
QuestionMessage msgBox(message, parent);
return msgBox.exec();
};
int InfoMessage::show(const QString &message, QWidget *parent) {
InfoMessage msgBox(message, parent);
return msgBox.exec();
};

164
src/ui/newlayoutdialog.cpp Normal file
View File

@ -0,0 +1,164 @@
#include "newlayoutdialog.h"
#include "maplayout.h"
#include "ui_newlayoutdialog.h"
#include "config.h"
#include "validator.h"
#include <QMap>
#include <QSet>
#include <QStringList>
const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }";
NewLayoutDialog::NewLayoutDialog(Project *project, QWidget *parent) :
NewLayoutDialog(project, nullptr, parent)
{}
NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layoutToCopy, QWidget *parent) :
QDialog(parent),
ui(new Ui::NewLayoutDialog),
layoutToCopy(layoutToCopy)
{
setAttribute(Qt::WA_DeleteOnClose);
ui->setupUi(this);
this->project = project;
Layout::Settings *settings = &project->newLayoutSettings;
// Note: 'layoutToCopy' will have an empty name if it's an import from AdvanceMap
if (this->layoutToCopy && !this->layoutToCopy->name.isEmpty()) {
settings->name = project->toUniqueIdentifier(this->layoutToCopy->name);
} else {
settings->name = QString();
}
// Generate a unique Layout constant
settings->id = project->toUniqueIdentifier(Layout::layoutConstantFromName(settings->name));
ui->newLayoutForm->initUi(project);
auto validator = new IdentifierValidator(this);
ui->lineEdit_Name->setValidator(validator);
ui->lineEdit_LayoutID->setValidator(validator);
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewLayoutDialog::dialogButtonClicked);
refresh();
if (porymapConfig.newLayoutDialogGeometry.isEmpty()){
// On first display resize to fit contents a little better
adjustSize();
} else {
restoreGeometry(porymapConfig.newLayoutDialogGeometry);
}
}
NewLayoutDialog::~NewLayoutDialog()
{
porymapConfig.newLayoutDialogGeometry = saveGeometry();
saveSettings();
delete ui;
}
void NewLayoutDialog::refresh() {
const Layout::Settings *settings = &this->project->newLayoutSettings;
if (this->layoutToCopy) {
// If we're importing a layout then some settings will be enforced.
ui->newLayoutForm->setSettings(this->layoutToCopy->settings());
ui->newLayoutForm->setDisabled(true);
} else {
ui->newLayoutForm->setSettings(*settings);
ui->newLayoutForm->setDisabled(false);
}
ui->lineEdit_Name->setText(settings->name);
ui->lineEdit_LayoutID->setText(settings->id);
}
void NewLayoutDialog::saveSettings() {
Layout::Settings *settings = &this->project->newLayoutSettings;
*settings = ui->newLayoutForm->settings();
settings->id = ui->lineEdit_LayoutID->text();
settings->name = ui->lineEdit_Name->text();
}
bool NewLayoutDialog::validateLayoutID(bool allowEmpty) {
QString id = ui->lineEdit_LayoutID->text();
QString errorText;
if (id.isEmpty()) {
if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_LayoutID->text());
} else if (!this->project->isIdentifierUnique(id)) {
errorText = QString("%1 '%2' is not unique.").arg(ui->label_LayoutID->text()).arg(id);
}
bool isValid = errorText.isEmpty();
ui->label_LayoutIDError->setText(errorText);
ui->label_LayoutIDError->setVisible(!isValid);
ui->lineEdit_LayoutID->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : "");
return isValid;
}
void NewLayoutDialog::on_lineEdit_LayoutID_textChanged(const QString &) {
validateLayoutID(true);
}
bool NewLayoutDialog::validateName(bool allowEmpty) {
QString name = ui->lineEdit_Name->text();
QString errorText;
if (name.isEmpty()) {
if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Name->text());
} else if (!this->project->isIdentifierUnique(name)) {
errorText = QString("%1 '%2' is not unique.").arg(ui->label_Name->text()).arg(name);
}
bool isValid = errorText.isEmpty();
ui->label_NameError->setText(errorText);
ui->label_NameError->setVisible(!isValid);
ui->lineEdit_Name->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : "");
return isValid;
}
void NewLayoutDialog::on_lineEdit_Name_textChanged(const QString &text) {
validateName(true);
// Changing the layout name updates the layout ID field to match.
ui->lineEdit_LayoutID->setText(Layout::layoutConstantFromName(text));
}
void NewLayoutDialog::dialogButtonClicked(QAbstractButton *button) {
auto role = ui->buttonBox->buttonRole(button);
if (role == QDialogButtonBox::RejectRole){
reject();
} else if (role == QDialogButtonBox::ResetRole) {
this->project->initNewLayoutSettings();
refresh();
} else if (role == QDialogButtonBox::AcceptRole) {
accept();
}
}
void NewLayoutDialog::accept() {
// Make sure to call each validation function so that all errors are shown at once.
bool success = true;
if (!ui->newLayoutForm->validate()) success = false;
if (!validateLayoutID()) success = false;
if (!validateName()) success = false;
if (!success)
return;
// Update settings from UI
saveSettings();
Layout *layout = this->project->createNewLayout(this->project->newLayoutSettings, this->layoutToCopy);
if (!layout) {
ui->label_GenericError->setText(QString("Failed to create layout. See %1 for details.").arg(getLogPath()));
ui->label_GenericError->setVisible(true);
return;
}
ui->label_GenericError->setVisible(false);
emit applied(layout->id);
QDialog::accept();
}

144
src/ui/newlayoutform.cpp Normal file
View File

@ -0,0 +1,144 @@
#include "newlayoutform.h"
#include "ui_newlayoutform.h"
#include "project.h"
const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }";
NewLayoutForm::NewLayoutForm(QWidget *parent)
: QWidget(parent)
, ui(new Ui::NewLayoutForm)
{
ui->setupUi(this);
ui->groupBox_BorderDimensions->setVisible(projectConfig.useCustomBorderSize);
ui->spinBox_BorderWidth->setMaximum(MAX_BORDER_WIDTH);
ui->spinBox_BorderHeight->setMaximum(MAX_BORDER_HEIGHT);
connect(ui->spinBox_MapWidth, QOverload<int>::of(&QSpinBox::valueChanged), [=](int){ validateMapDimensions(); });
connect(ui->spinBox_MapHeight, QOverload<int>::of(&QSpinBox::valueChanged), [=](int){ validateMapDimensions(); });
connect(ui->comboBox_PrimaryTileset->lineEdit(), &QLineEdit::editingFinished, [this]{ validatePrimaryTileset(true); });
connect(ui->comboBox_SecondaryTileset->lineEdit(), &QLineEdit::editingFinished, [this]{ validateSecondaryTileset(true); });
}
NewLayoutForm::~NewLayoutForm()
{
delete ui;
}
void NewLayoutForm::initUi(Project *project) {
m_project = project;
ui->comboBox_PrimaryTileset->clear();
ui->comboBox_SecondaryTileset->clear();
if (m_project) {
ui->comboBox_PrimaryTileset->addItems(m_project->primaryTilesetLabels);
ui->comboBox_SecondaryTileset->addItems(m_project->secondaryTilesetLabels);
ui->spinBox_MapWidth->setMaximum(m_project->getMaxMapWidth());
ui->spinBox_MapHeight->setMaximum(m_project->getMaxMapHeight());
}
}
void NewLayoutForm::setDisabled(bool disabled) {
ui->groupBox_MapDimensions->setDisabled(disabled);
ui->groupBox_BorderDimensions->setDisabled(disabled);
ui->groupBox_Tilesets->setDisabled(disabled);
}
void NewLayoutForm::setSettings(const Layout::Settings &settings) {
ui->spinBox_MapWidth->setValue(settings.width);
ui->spinBox_MapHeight->setValue(settings.height);
ui->spinBox_BorderWidth->setValue(settings.borderWidth);
ui->spinBox_BorderHeight->setValue(settings.borderHeight);
ui->comboBox_PrimaryTileset->setTextItem(settings.primaryTilesetLabel);
ui->comboBox_SecondaryTileset->setTextItem(settings.secondaryTilesetLabel);
}
Layout::Settings NewLayoutForm::settings() const {
Layout::Settings settings;
settings.width = ui->spinBox_MapWidth->value();
settings.height = ui->spinBox_MapHeight->value();
if (ui->groupBox_BorderDimensions->isVisible()) {
settings.borderWidth = ui->spinBox_BorderWidth->value();
settings.borderHeight = ui->spinBox_BorderHeight->value();
} else {
settings.borderWidth = DEFAULT_BORDER_WIDTH;
settings.borderHeight = DEFAULT_BORDER_HEIGHT;
}
settings.primaryTilesetLabel = ui->comboBox_PrimaryTileset->currentText();
settings.secondaryTilesetLabel = ui->comboBox_SecondaryTileset->currentText();
return settings;
}
bool NewLayoutForm::validate() {
// Make sure to call each validation function so that all errors are shown at once.
bool valid = true;
if (!validateMapDimensions()) valid = false;
if (!validatePrimaryTileset()) valid = false;
if (!validateSecondaryTileset()) valid = false;
return valid;
}
bool NewLayoutForm::validateMapDimensions() {
int size = m_project->getMapDataSize(ui->spinBox_MapWidth->value(), ui->spinBox_MapHeight->value());
int maxSize = m_project->getMaxMapDataSize();
// TODO: Get from project
const int additionalWidth = 15;
const int additionalHeight = 14;
QString errorText;
if (size > maxSize) {
errorText = QString("The specified width and height are too large.\n"
"The maximum map width and height is the following: (width + %1) * (height + %2) <= %3\n"
"The specified map width and height was: (%4 + %1) * (%5 + %2) = %6")
.arg(additionalWidth)
.arg(additionalHeight)
.arg(maxSize)
.arg(ui->spinBox_MapWidth->value())
.arg(ui->spinBox_MapHeight->value())
.arg(size);
}
bool isValid = errorText.isEmpty();
ui->label_MapDimensionsError->setText(errorText);
ui->label_MapDimensionsError->setVisible(!isValid);
return isValid;
}
bool NewLayoutForm::validatePrimaryTileset(bool allowEmpty) {
const QString name = ui->comboBox_PrimaryTileset->currentText();
QString errorText;
if (name.isEmpty()) {
if (!allowEmpty) errorText = QString("The Primary Tileset cannot be empty.");
} else if (ui->comboBox_PrimaryTileset->findText(name) < 0) {
errorText = QString("The Primary Tileset '%1' does not exist.").arg(ui->label_PrimaryTileset->text()).arg(name);
}
bool isValid = errorText.isEmpty();
ui->label_PrimaryTilesetError->setText(errorText);
ui->label_PrimaryTilesetError->setVisible(!isValid);
ui->comboBox_PrimaryTileset->lineEdit()->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : "");
return isValid;
}
bool NewLayoutForm::validateSecondaryTileset(bool allowEmpty) {
const QString name = ui->comboBox_SecondaryTileset->currentText();
QString errorText;
if (name.isEmpty()) {
if (!allowEmpty) errorText = QString("The Secondary Tileset cannot be empty.");
} else if (ui->comboBox_SecondaryTileset->findText(name) < 0) {
errorText = QString("The Secondary Tileset '%1' does not exist.").arg(name);
}
bool isValid = errorText.isEmpty();
ui->label_SecondaryTilesetError->setText(errorText);
ui->label_SecondaryTilesetError->setVisible(!isValid);
ui->comboBox_SecondaryTileset->lineEdit()->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : "");
return isValid;
}

View File

@ -0,0 +1,79 @@
#include "newlocationdialog.h"
#include "ui_newlocationdialog.h"
#include "project.h"
#include "validator.h"
const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }";
NewLocationDialog::NewLocationDialog(Project* project, QWidget *parent) :
QDialog(parent),
ui(new Ui::NewLocationDialog),
namePrefix(projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix))
{
setAttribute(Qt::WA_DeleteOnClose);
ui->setupUi(this);
this->project = project;
ui->lineEdit_IdName->setValidator(new IdentifierValidator(namePrefix, this));
ui->lineEdit_IdName->setText(namePrefix);
connect(ui->lineEdit_IdName, &QLineEdit::textChanged, this, &NewLocationDialog::onIdNameChanged);
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewLocationDialog::dialogButtonClicked);
adjustSize();
}
NewLocationDialog::~NewLocationDialog()
{
delete ui;
}
void NewLocationDialog::onIdNameChanged(const QString &idName) {
validateIdName(true);
// Extract a presumed display name from the ID name
QString displayName = idName;
if (displayName.startsWith(namePrefix))
displayName.remove(0, namePrefix.length());
displayName.replace("_", " ");
ui->lineEdit_DisplayName->setText(displayName);
}
bool NewLocationDialog::validateIdName(bool allowEmpty) {
const QString name = ui->lineEdit_IdName->text();
QString errorText;
if (name.isEmpty() || name == namePrefix) {
if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_IdName->text());
} else if (!this->project->isIdentifierUnique(name)) {
errorText = QString("%1 '%2' is not unique.").arg(ui->label_IdName->text()).arg(name);
}
bool isValid = errorText.isEmpty();
ui->label_IdNameError->setText(errorText);
ui->label_IdNameError->setVisible(!isValid);
ui->lineEdit_IdName->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : "");
return isValid;
}
void NewLocationDialog::dialogButtonClicked(QAbstractButton *button) {
auto role = ui->buttonBox->buttonRole(button);
if (role == QDialogButtonBox::RejectRole){
reject();
} else if (role == QDialogButtonBox::AcceptRole) {
accept();
}
}
void NewLocationDialog::accept() {
if (!validateIdName())
return;
const QString idName = ui->lineEdit_IdName->text();
const QString displayName = ui->lineEdit_DisplayName->text();
this->project->addNewMapsec(idName);
this->project->setMapsecDisplayName(idName, displayName);
QDialog::accept();
}

View File

@ -33,7 +33,7 @@ NewMapConnectionDialog::NewMapConnectionDialog(QWidget *parent, Map* map, const
QString defaultMapName;
if (mapNames.isEmpty()) {
defaultMapName = QString();
} else if (mapNames.first() == map->name && mapNames.length() > 1) {
} else if (mapNames.first() == map->name() && mapNames.length() > 1) {
// Prefer not to connect the map to itself
defaultMapName = mapNames.at(1);
} else {

268
src/ui/newmapdialog.cpp Normal file
View File

@ -0,0 +1,268 @@
#include "newmapdialog.h"
#include "maplayout.h"
#include "mainwindow.h"
#include "ui_newmapdialog.h"
#include "config.h"
#include "validator.h"
#include <QMap>
#include <QSet>
#include <QStringList>
const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }";
NewMapDialog::NewMapDialog(Project *project, QWidget *parent) :
NewMapDialog(project, nullptr, parent)
{}
NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *parent) :
QDialog(parent),
ui(new Ui::NewMapDialog),
mapToCopy(mapToCopy)
{
setAttribute(Qt::WA_DeleteOnClose);
ui->setupUi(this);
this->project = project;
Project::NewMapSettings *settings = &project->newMapSettings;
if (this->mapToCopy) {
// Copy settings from the map we're duplicating
if (this->mapToCopy->layout()){
settings->layout = this->mapToCopy->layout()->settings();
}
settings->header = *this->mapToCopy->header();
settings->group = project->mapNameToMapGroup(this->mapToCopy->name());
settings->name = project->toUniqueIdentifier(this->mapToCopy->name());
} else {
// Clear the previously-used map name. The rest of the settings are preserved between sessions.
settings->name = QString();
}
// Generate a unique Layout constant
settings->layout.id = project->toUniqueIdentifier(Layout::layoutConstantFromName(settings->name));
ui->newLayoutForm->initUi(project);
ui->comboBox_Group->addItems(project->groupNames);
ui->comboBox_LayoutID->addItems(project->layoutIds);
auto validator = new IdentifierValidator(this);
ui->lineEdit_Name->setValidator(validator);
ui->comboBox_Group->setValidator(validator);
ui->comboBox_LayoutID->setValidator(validator);
// Create a collapsible section that has all the map header data.
this->headerForm = new MapHeaderForm();
this->headerForm->setProject(project, false);
auto sectionLayout = new QVBoxLayout();
sectionLayout->addWidget(this->headerForm);
this->headerSection = new CollapsibleSection("Header Data", porymapConfig.newMapHeaderSectionExpanded, 150, this);
this->headerSection->setContentLayout(sectionLayout);
ui->layout_HeaderData->addWidget(this->headerSection);
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapDialog::dialogButtonClicked);
refresh();
restoreGeometry(porymapConfig.newMapDialogGeometry);
}
// Adding new map to an existing map list folder. Initialize settings accordingly.
// Even if we initialize settings like this we'll allow users to change them afterwards,
// because nothing is expecting them to stay at these values (with exception to layouts).
NewMapDialog::NewMapDialog(Project *project, int mapListTab, const QString &mapListItem, QWidget *parent) :
NewMapDialog(project, parent)
{
switch (mapListTab)
{
case MapListTab::Groups:
ui->comboBox_Group->setTextItem(mapListItem);
break;
case MapListTab::Locations:
this->headerForm->setLocation(mapListItem);
break;
case MapListTab::Layouts:
// We specifically lock the layout ID because otherwise the setting would be overwritten when
// the user changes the map name (which will normally automatically update the layout ID to match).
// For the Group/Location settings above we don't care if the user changes them afterwards.
ui->comboBox_LayoutID->setTextItem(mapListItem);
ui->comboBox_LayoutID->setDisabled(true);
break;
}
}
NewMapDialog::~NewMapDialog()
{
porymapConfig.newMapDialogGeometry = saveGeometry();
saveSettings();
delete ui;
}
// Reload the UI from the last-saved settings.
void NewMapDialog::refresh() {
const Project::NewMapSettings *settings = &this->project->newMapSettings;
ui->lineEdit_Name->setText(settings->name);
ui->comboBox_Group->setTextItem(settings->group);
// If the layout combo box is disabled, it's because we're enforcing the setting. Leave it unchanged.
if (ui->comboBox_LayoutID->isEnabled())
ui->comboBox_LayoutID->setTextItem(settings->layout.id);
ui->newLayoutForm->setSettings(settings->layout);
ui->checkBox_CanFlyTo->setChecked(settings->canFlyTo);
this->headerForm->setHeaderData(settings->header);
}
void NewMapDialog::saveSettings() {
Project::NewMapSettings *settings = &this->project->newMapSettings;
settings->name = ui->lineEdit_Name->text();
settings->group = ui->comboBox_Group->currentText();
settings->layout = ui->newLayoutForm->settings();
settings->layout.id = ui->comboBox_LayoutID->currentText();
settings->canFlyTo = ui->checkBox_CanFlyTo->isChecked();
settings->header = this->headerForm->headerData();
// This dialog doesn't give users the option to give new layouts a name, we generate one using the map name.
// We instead add a "_Layout" suffix to differentiate the layout name from the map name.
// (an older iteration of this dialog gave users an option to name new layouts, but it's extra clutter for
// something the majority of users creating a map won't need. If they want to give a specific name to a layout
// they can create the layout first, then create a new map that uses that layout.)
const Layout *layout = this->project->mapLayouts.value(settings->layout.id);
if (!layout) {
const QString newLayoutName = settings->name + QStringLiteral("_Layout");
settings->layout.name = this->project->toUniqueIdentifier(newLayoutName);
} else {
// Pre-existing layout. The layout name won't be read, but we'll make sure it's correct anyway.
settings->layout.name = layout->name;
}
// Folders for new layouts created for new maps use the map name, rather than the layout name
// (i.e., if you create "MyMap", you'll get a 'data/layouts/MyMap/', rather than 'data/layouts/MyMap_Layout/').
// There's no real reason for this, aside from maintaining consistency with the default layout folder names that do this.
settings->layout.folderName = settings->name;
porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded();
}
bool NewMapDialog::validateName(bool allowEmpty) {
QString name = ui->lineEdit_Name->text();
QString errorText;
if (name.isEmpty()) {
if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Name->text());
} else if (!this->project->isIdentifierUnique(name)) {
errorText = QString("%1 '%2' is not unique.").arg(ui->label_Name->text()).arg(name);
}
bool isValid = errorText.isEmpty();
ui->label_NameError->setText(errorText);
ui->label_NameError->setVisible(!isValid);
ui->lineEdit_Name->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : "");
return isValid;
}
void NewMapDialog::on_lineEdit_Name_textChanged(const QString &text) {
validateName(true);
// Changing the map name updates the layout ID field to match.
if (ui->comboBox_LayoutID->isEnabled()) {
ui->comboBox_LayoutID->setCurrentText(Layout::layoutConstantFromName(text));
}
}
bool NewMapDialog::validateGroup(bool allowEmpty) {
QString groupName = ui->comboBox_Group->currentText();
QString errorText;
if (groupName.isEmpty()) {
if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Group->text());
} else if (!this->project->groupNames.contains(groupName) && !this->project->isIdentifierUnique(groupName)) {
errorText = QString("%1 must either be the name of an existing map group, or a unique identifier for a new map group.").arg(ui->label_Group->text());
}
bool isValid = errorText.isEmpty();
ui->label_GroupError->setText(errorText);
ui->label_GroupError->setVisible(!isValid);
ui->comboBox_Group->lineEdit()->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : "");
return isValid;
}
void NewMapDialog::on_comboBox_Group_currentTextChanged(const QString &) {
validateGroup(true);
}
bool NewMapDialog::validateLayoutID(bool allowEmpty) {
QString layoutId = ui->comboBox_LayoutID->currentText();
QString errorText;
if (layoutId.isEmpty()) {
if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_LayoutID->text());
} else if (!this->project->layoutIds.contains(layoutId) && !this->project->isIdentifierUnique(layoutId)) {
errorText = QString("%1 must either be the ID for an existing layout, or a unique identifier for a new layout.").arg(ui->label_LayoutID->text());
}
bool isValid = errorText.isEmpty();
ui->label_LayoutIDError->setText(errorText);
ui->label_LayoutIDError->setVisible(!isValid);
ui->comboBox_LayoutID->lineEdit()->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : "");
return isValid;
}
void NewMapDialog::on_comboBox_LayoutID_currentTextChanged(const QString &text) {
validateLayoutID(true);
// Changing the layout ID to an existing layout updates the layout settings to match.
const Layout *layout = this->project->mapLayouts.value(text);
if (!layout && this->mapToCopy) {
// When duplicating a map, if a new layout ID is specified the settings will be updated
// to match the layout of the map we're duplicating.
layout = this->mapToCopy->layout();
}
if (layout) {
ui->newLayoutForm->setSettings(layout->settings());
ui->newLayoutForm->setDisabled(true);
} else {
ui->newLayoutForm->setDisabled(false);
}
}
void NewMapDialog::dialogButtonClicked(QAbstractButton *button) {
auto role = ui->buttonBox->buttonRole(button);
if (role == QDialogButtonBox::RejectRole){
reject();
} else if (role == QDialogButtonBox::ResetRole) {
this->project->initNewMapSettings();
refresh();
} else if (role == QDialogButtonBox::AcceptRole) {
accept();
}
}
void NewMapDialog::accept() {
// Make sure to call each validation function so that all errors are shown at once.
bool success = true;
if (!ui->newLayoutForm->validate()) success = false;
if (!validateName()) success = false;
if (!validateGroup()) success = false;
if (!validateLayoutID()) success = false;
if (!success)
return;
// Update settings from UI
saveSettings();
Map *map = this->project->createNewMap(this->project->newMapSettings, this->mapToCopy);
if (!map) {
ui->label_GenericError->setText(QString("Failed to create map. See %1 for details.").arg(getLogPath()));
ui->label_GenericError->setVisible(true);
return;
}
ui->label_GenericError->setVisible(false);
// If the location name field was changed, update the project for that too.
this->project->setMapsecDisplayName(this->headerForm->location(), this->headerForm->locationName());
QDialog::accept();
}

View File

@ -0,0 +1,67 @@
#include "newmapgroupdialog.h"
#include "ui_newmapgroupdialog.h"
#include "project.h"
#include "validator.h"
const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }";
NewMapGroupDialog::NewMapGroupDialog(Project* project, QWidget *parent) :
QDialog(parent),
ui(new Ui::NewMapGroupDialog)
{
setAttribute(Qt::WA_DeleteOnClose);
ui->setupUi(this);
this->project = project;
ui->lineEdit_Name->setValidator(new IdentifierValidator(this));
ui->lineEdit_Name->setText(Project::getMapGroupPrefix());
connect(ui->lineEdit_Name, &QLineEdit::textChanged, this, &NewMapGroupDialog::onNameChanged);
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapGroupDialog::dialogButtonClicked);
adjustSize();
}
NewMapGroupDialog::~NewMapGroupDialog()
{
delete ui;
}
void NewMapGroupDialog::onNameChanged(const QString &) {
validateName(true);
}
bool NewMapGroupDialog::validateName(bool allowEmpty) {
const QString name = ui->lineEdit_Name->text();
QString errorText;
if (name.isEmpty()) {
if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Name->text());
} else if (!this->project->isIdentifierUnique(name)) {
errorText = QString("%1 '%2' is not unique.").arg(ui->label_Name->text()).arg(name);
}
bool isValid = errorText.isEmpty();
ui->label_NameError->setText(errorText);
ui->label_NameError->setVisible(!isValid);
ui->lineEdit_Name->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : "");
return isValid;
}
void NewMapGroupDialog::dialogButtonClicked(QAbstractButton *button) {
auto role = ui->buttonBox->buttonRole(button);
if (role == QDialogButtonBox::RejectRole){
reject();
} else if (role == QDialogButtonBox::AcceptRole) {
accept();
}
}
void NewMapGroupDialog::accept() {
if (!validateName())
return;
this->project->addNewMapGroup(ui->lineEdit_Name->text());
QDialog::accept();
}

View File

@ -1,361 +0,0 @@
#include "newmappopup.h"
#include "maplayout.h"
#include "mainwindow.h"
#include "ui_newmappopup.h"
#include "config.h"
#include <QMap>
#include <QSet>
#include <QStringList>
// TODO: Convert to modal dialog (among other things, this means we wouldn't need to worry about changes to the map list while this is open)
struct NewMapPopup::Settings NewMapPopup::settings = {};
NewMapPopup::NewMapPopup(QWidget *parent, Project *project) :
QMainWindow(parent),
ui(new Ui::NewMapPopup)
{
this->setAttribute(Qt::WA_DeleteOnClose);
ui->setupUi(this);
this->project = project;
this->existingLayout = false;
this->importedMap = false;
}
NewMapPopup::~NewMapPopup()
{
saveSettings();
delete ui;
}
void NewMapPopup::initUi() {
// Populate combo boxes
ui->comboBox_NewMap_Primary_Tileset->addItems(project->primaryTilesetLabels);
ui->comboBox_NewMap_Secondary_Tileset->addItems(project->secondaryTilesetLabels);
ui->comboBox_NewMap_Group->addItems(project->groupNames);
ui->comboBox_NewMap_Song->addItems(project->songNames);
ui->comboBox_NewMap_Type->addItems(project->mapTypes);
ui->comboBox_NewMap_Location->addItems(project->mapSectionIdNames);
const QSignalBlocker b(ui->comboBox_Layout);
ui->comboBox_Layout->addItems(project->mapLayoutsTable);
this->layoutId = project->mapLayoutsTable.first();
// Set spin box limits
ui->spinBox_NewMap_Width->setMinimum(1);
ui->spinBox_NewMap_Height->setMinimum(1);
ui->spinBox_NewMap_Width->setMaximum(project->getMaxMapWidth());
ui->spinBox_NewMap_Height->setMaximum(project->getMaxMapHeight());
ui->spinBox_NewMap_BorderWidth->setMinimum(1);
ui->spinBox_NewMap_BorderHeight->setMinimum(1);
ui->spinBox_NewMap_BorderWidth->setMaximum(MAX_BORDER_WIDTH);
ui->spinBox_NewMap_BorderHeight->setMaximum(MAX_BORDER_HEIGHT);
ui->spinBox_NewMap_Floor_Number->setMinimum(-128);
ui->spinBox_NewMap_Floor_Number->setMaximum(127);
// Hide config specific ui elements
bool hasFlags = projectConfig.mapAllowFlagsEnabled;
ui->checkBox_NewMap_Allow_Running->setVisible(hasFlags);
ui->checkBox_NewMap_Allow_Biking->setVisible(hasFlags);
ui->checkBox_NewMap_Allow_Escape_Rope->setVisible(hasFlags);
ui->label_NewMap_Allow_Running->setVisible(hasFlags);
ui->label_NewMap_Allow_Biking->setVisible(hasFlags);
ui->label_NewMap_Allow_Escape_Rope->setVisible(hasFlags);
bool hasCustomBorders = projectConfig.useCustomBorderSize;
ui->spinBox_NewMap_BorderWidth->setVisible(hasCustomBorders);
ui->spinBox_NewMap_BorderHeight->setVisible(hasCustomBorders);
ui->label_NewMap_BorderWidth->setVisible(hasCustomBorders);
ui->label_NewMap_BorderHeight->setVisible(hasCustomBorders);
bool hasFloorNumber = projectConfig.floorNumberEnabled;
ui->spinBox_NewMap_Floor_Number->setVisible(hasFloorNumber);
ui->label_NewMap_Floor_Number->setVisible(hasFloorNumber);
this->updateGeometry();
}
void NewMapPopup::init() {
// Restore previous settings
ui->lineEdit_NewMap_Name->setText(project->getNewMapName());
ui->comboBox_NewMap_Group->setTextItem(settings.group);
ui->spinBox_NewMap_Width->setValue(settings.width);
ui->spinBox_NewMap_Height->setValue(settings.height);
ui->spinBox_NewMap_BorderWidth->setValue(settings.borderWidth);
ui->spinBox_NewMap_BorderHeight->setValue(settings.borderHeight);
ui->comboBox_NewMap_Primary_Tileset->setTextItem(settings.primaryTilesetLabel);
ui->comboBox_NewMap_Secondary_Tileset->setTextItem(settings.secondaryTilesetLabel);
ui->comboBox_NewMap_Type->setTextItem(settings.type);
ui->comboBox_NewMap_Location->setTextItem(settings.location);
ui->comboBox_NewMap_Song->setTextItem(settings.song);
ui->checkBox_NewMap_Flyable->setChecked(settings.canFlyTo);
ui->checkBox_NewMap_Show_Location->setChecked(settings.showLocationName);
ui->checkBox_NewMap_Allow_Running->setChecked(settings.allowRunning);
ui->checkBox_NewMap_Allow_Biking->setChecked(settings.allowBiking);
ui->checkBox_NewMap_Allow_Escape_Rope->setChecked(settings.allowEscaping);
ui->spinBox_NewMap_Floor_Number->setValue(settings.floorNumber);
// Connect signals
connect(ui->spinBox_NewMap_Width, QOverload<int>::of(&QSpinBox::valueChanged), [=](int){checkNewMapDimensions();});
connect(ui->spinBox_NewMap_Height, QOverload<int>::of(&QSpinBox::valueChanged), [=](int){checkNewMapDimensions();});
ui->frame_NewMap_Options->setEnabled(true);
}
// Creating new map by right-clicking in the map list
void NewMapPopup::init(int tabIndex, QString fieldName) {
initUi();
switch (tabIndex)
{
case MapListTab::Groups:
settings.group = fieldName;
break;
case MapListTab::Areas:
settings.location = fieldName;
break;
case MapListTab::Layouts:
this->ui->checkBox_UseExistingLayout->setCheckState(Qt::Checked);
useLayout(fieldName);
break;
}
init();
}
// Creating new map from AdvanceMap import
void NewMapPopup::init(Layout *mapLayout) {
initUi();
this->importedMap = true;
useLayoutSettings(mapLayout);
this->map = new Map();
this->map->layout = new Layout();
this->map->layout->blockdata = mapLayout->blockdata;
if (!mapLayout->border.isEmpty()) {
this->map->layout->border = mapLayout->border;
}
init();
}
bool NewMapPopup::checkNewMapDimensions() {
int numMetatiles = project->getMapDataSize(ui->spinBox_NewMap_Width->value(), ui->spinBox_NewMap_Height->value());
int maxMetatiles = project->getMaxMapDataSize();
if (numMetatiles > maxMetatiles) {
ui->frame_NewMap_Warning->setVisible(true);
QString errorText = QString("Error: The specified width and height are too large.\n"
"The maximum map width and height is the following: (width + 15) * (height + 14) <= %1\n"
"The specified map width and height was: (%2 + 15) * (%3 + 14) = %4")
.arg(maxMetatiles)
.arg(ui->spinBox_NewMap_Width->value())
.arg(ui->spinBox_NewMap_Height->value())
.arg(numMetatiles);
ui->label_NewMap_WarningMessage->setText(errorText);
ui->label_NewMap_WarningMessage->setWordWrap(true);
return false;
}
else {
ui->frame_NewMap_Warning->setVisible(false);
ui->label_NewMap_WarningMessage->clear();
return true;
}
}
bool NewMapPopup::checkNewMapGroup() {
group = project->groupNames.indexOf(this->ui->comboBox_NewMap_Group->currentText());
if (group < 0) {
ui->frame_NewMap_Warning->setVisible(true);
QString errorText = QString("Error: The specified map group '%1' does not exist.")
.arg(ui->comboBox_NewMap_Group->currentText());
ui->label_NewMap_WarningMessage->setText(errorText);
ui->label_NewMap_WarningMessage->setWordWrap(true);
return false;
} else {
ui->frame_NewMap_Warning->setVisible(false);
ui->label_NewMap_WarningMessage->clear();
return true;
}
}
void NewMapPopup::setDefaultSettings(Project *project) {
settings.group = project->groupNames.at(0);
settings.width = project->getDefaultMapSize();
settings.height = project->getDefaultMapSize();
settings.borderWidth = DEFAULT_BORDER_WIDTH;
settings.borderHeight = DEFAULT_BORDER_HEIGHT;
settings.primaryTilesetLabel = project->getDefaultPrimaryTilesetLabel();
settings.secondaryTilesetLabel = project->getDefaultSecondaryTilesetLabel();
settings.type = project->mapTypes.value(0, "0");
settings.location = project->mapSectionIdNames.value(0, "0");
settings.song = project->defaultSong;
settings.canFlyTo = false;
settings.showLocationName = true;
settings.allowRunning = false;
settings.allowBiking = false;
settings.allowEscaping = false;
settings.floorNumber = 0;
}
void NewMapPopup::saveSettings() {
settings.group = ui->comboBox_NewMap_Group->currentText();
settings.width = ui->spinBox_NewMap_Width->value();
settings.height = ui->spinBox_NewMap_Height->value();
settings.borderWidth = ui->spinBox_NewMap_BorderWidth->value();
settings.borderHeight = ui->spinBox_NewMap_BorderHeight->value();
settings.primaryTilesetLabel = ui->comboBox_NewMap_Primary_Tileset->currentText();
settings.secondaryTilesetLabel = ui->comboBox_NewMap_Secondary_Tileset->currentText();
settings.type = ui->comboBox_NewMap_Type->currentText();
settings.location = ui->comboBox_NewMap_Location->currentText();
settings.song = ui->comboBox_NewMap_Song->currentText();
settings.canFlyTo = ui->checkBox_NewMap_Flyable->isChecked();
settings.showLocationName = ui->checkBox_NewMap_Show_Location->isChecked();
settings.allowRunning = ui->checkBox_NewMap_Allow_Running->isChecked();
settings.allowBiking = ui->checkBox_NewMap_Allow_Biking->isChecked();
settings.allowEscaping = ui->checkBox_NewMap_Allow_Escape_Rope->isChecked();
settings.floorNumber = ui->spinBox_NewMap_Floor_Number->value();
}
void NewMapPopup::useLayoutSettings(Layout *layout) {
if (!layout) return;
settings.width = layout->width;
ui->spinBox_NewMap_Width->setValue(layout->width);
settings.height = layout->height;
ui->spinBox_NewMap_Height->setValue(layout->height);
settings.borderWidth = layout->border_width;
ui->spinBox_NewMap_BorderWidth->setValue(layout->border_width);
settings.borderHeight = layout->border_height;
ui->spinBox_NewMap_BorderWidth->setValue(layout->border_height);
settings.primaryTilesetLabel = layout->tileset_primary_label;
ui->comboBox_NewMap_Primary_Tileset->setCurrentIndex(ui->comboBox_NewMap_Primary_Tileset->findText(layout->tileset_primary_label));
settings.secondaryTilesetLabel = layout->tileset_secondary_label;
ui->comboBox_NewMap_Secondary_Tileset->setCurrentIndex(ui->comboBox_NewMap_Secondary_Tileset->findText(layout->tileset_secondary_label));
}
void NewMapPopup::useLayout(QString layoutId) {
this->existingLayout = true;
this->layoutId = layoutId;
this->ui->comboBox_Layout->setCurrentIndex(this->ui->comboBox_Layout->findText(layoutId));
useLayoutSettings(project->mapLayouts.value(this->layoutId));
}
void NewMapPopup::on_checkBox_UseExistingLayout_stateChanged(int state) {
bool layoutEditsEnabled = (state == Qt::Unchecked);
this->ui->comboBox_Layout->setEnabled(!layoutEditsEnabled);
this->ui->spinBox_NewMap_Width->setEnabled(layoutEditsEnabled);
this->ui->spinBox_NewMap_Height->setEnabled(layoutEditsEnabled);
this->ui->spinBox_NewMap_BorderWidth->setEnabled(layoutEditsEnabled);
this->ui->spinBox_NewMap_BorderWidth->setEnabled(layoutEditsEnabled);
this->ui->comboBox_NewMap_Primary_Tileset->setEnabled(layoutEditsEnabled);
this->ui->comboBox_NewMap_Secondary_Tileset->setEnabled(layoutEditsEnabled);
if (!layoutEditsEnabled) {
useLayout(this->layoutId);//this->ui->comboBox_Layout->currentText());
} else {
this->existingLayout = false;
}
}
void NewMapPopup::on_comboBox_Layout_currentTextChanged(const QString &text) {
if (this->project->mapLayoutsTable.contains(text)) {
useLayout(text);
}
}
void NewMapPopup::on_lineEdit_NewMap_Name_textChanged(const QString &text) {
if (project->mapNames.contains(text)) {
this->ui->lineEdit_NewMap_Name->setStyleSheet("QLineEdit { background-color: rgba(255, 0, 0, 25%) }");
} else {
this->ui->lineEdit_NewMap_Name->setStyleSheet("");
}
}
void NewMapPopup::on_pushButton_NewMap_Accept_clicked() {
if (!checkNewMapDimensions() || !checkNewMapGroup()) {
// ignore when map dimensions or map group are invalid
return;
}
Map *newMap = new Map;
Layout *layout;
// If map name is not unique, use default value. Also use only valid characters.
// After stripping invalid characters, strip any leading digits.
static const QRegularExpression re_invalidChars("[^a-zA-Z0-9_]+");
QString newMapName = this->ui->lineEdit_NewMap_Name->text().remove(re_invalidChars);
static const QRegularExpression re_NaN("^[0-9]*");
newMapName.remove(re_NaN);
if (project->mapNames.contains(newMapName) || newMapName.isEmpty()) {
newMapName = project->getNewMapName();
}
newMap->name = newMapName;
newMap->type = this->ui->comboBox_NewMap_Type->currentText();
newMap->location = this->ui->comboBox_NewMap_Location->currentText();
newMap->song = this->ui->comboBox_NewMap_Song->currentText();
newMap->requiresFlash = false;
newMap->weather = this->project->weatherNames.value(0, "0");
newMap->show_location = this->ui->checkBox_NewMap_Show_Location->isChecked();
newMap->battle_scene = this->project->mapBattleScenes.value(0, "0");
if (this->existingLayout) {
layout = this->project->mapLayouts.value(this->layoutId);
newMap->needsLayoutDir = false;
} else {
layout = new Layout;
layout->id = Layout::layoutConstantFromName(newMapName);
layout->name = QString("%1_Layout").arg(newMap->name);
layout->width = this->ui->spinBox_NewMap_Width->value();
layout->height = this->ui->spinBox_NewMap_Height->value();
if (projectConfig.useCustomBorderSize) {
layout->border_width = this->ui->spinBox_NewMap_BorderWidth->value();
layout->border_height = this->ui->spinBox_NewMap_BorderHeight->value();
} else {
layout->border_width = DEFAULT_BORDER_WIDTH;
layout->border_height = DEFAULT_BORDER_HEIGHT;
}
layout->tileset_primary_label = this->ui->comboBox_NewMap_Primary_Tileset->currentText();
layout->tileset_secondary_label = this->ui->comboBox_NewMap_Secondary_Tileset->currentText();
QString basePath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders);
layout->border_path = QString("%1%2/border.bin").arg(basePath, newMapName);
layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, newMapName);
}
if (this->importedMap) {
layout->blockdata = map->layout->blockdata;
if (!map->layout->border.isEmpty())
layout->border = map->layout->border;
}
if (this->ui->checkBox_NewMap_Flyable->isChecked()) {
newMap->needsHealLocation = true;
}
if (projectConfig.mapAllowFlagsEnabled) {
newMap->allowRunning = this->ui->checkBox_NewMap_Allow_Running->isChecked();
newMap->allowBiking = this->ui->checkBox_NewMap_Allow_Biking->isChecked();
newMap->allowEscaping = this->ui->checkBox_NewMap_Allow_Escape_Rope->isChecked();
}
if (projectConfig.floorNumberEnabled) {
newMap->floorNumber = this->ui->spinBox_NewMap_Floor_Number->value();
}
newMap->layout = layout;
newMap->layoutId = layout->id;
if (this->existingLayout) {
project->loadMapLayout(newMap);
}
map = newMap;
emit applied();
this->close();
}

View File

@ -1,49 +1,80 @@
#include "newtilesetdialog.h"
#include "ui_newtilesetdialog.h"
#include "project.h"
#include "imageexport.h"
#include "validator.h"
const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }";
NewTilesetDialog::NewTilesetDialog(Project* project, QWidget *parent) :
QDialog(parent),
ui(new Ui::NewTilesetDialog)
ui(new Ui::NewTilesetDialog),
symbolPrefix(projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix))
{
setAttribute(Qt::WA_DeleteOnClose);
ui->setupUi(this);
this->setFixedSize(this->width(), this->height());
this->project = project;
//only allow characters valid for a symbol
static const QRegularExpression expression("[_A-Za-z0-9]+$");
QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression);
this->ui->nameLineEdit->setValidator(validator);
bool checkerboard = porymapConfig.tilesetCheckerboardFill;
this->ui->fillCheckBox->setChecked(checkerboard);
this->checkerboardFill = checkerboard;
ui->checkBox_CheckerboardFill->setChecked(porymapConfig.tilesetCheckerboardFill);
ui->comboBox_Type->setMinimumContentsLength(12 + this->symbolPrefix.length());
ui->lineEdit_Name->setValidator(new IdentifierValidator(this->symbolPrefix, this));
ui->lineEdit_Name->setText(this->symbolPrefix);
connect(this->ui->nameLineEdit, &QLineEdit::textChanged, this, &NewTilesetDialog::NameOrSecondaryChanged);
connect(this->ui->typeComboBox, &QComboBox::currentTextChanged, this, &NewTilesetDialog::SecondaryChanged);
connect(this->ui->fillCheckBox, &QCheckBox::stateChanged, this, &NewTilesetDialog::FillChanged);
//connect(this->ui->toolButton, &QToolButton::clicked, this, &NewTilesetDialog::ChangeFilePath);
this->SecondaryChanged();
connect(ui->lineEdit_Name, &QLineEdit::textChanged, this, &NewTilesetDialog::onNameChanged);
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewTilesetDialog::dialogButtonClicked);
adjustSize();
}
NewTilesetDialog::~NewTilesetDialog()
{
porymapConfig.tilesetCheckerboardFill = ui->checkBox_CheckerboardFill->isChecked();
delete ui;
}
void NewTilesetDialog::SecondaryChanged(){
this->isSecondary = (this->ui->typeComboBox->currentIndex() == 1);
NameOrSecondaryChanged();
void NewTilesetDialog::onNameChanged(const QString &) {
validateName(true);
}
void NewTilesetDialog::NameOrSecondaryChanged() {
this->friendlyName = this->ui->nameLineEdit->text();
this->fullSymbolName = projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix) + this->friendlyName;
this->ui->symbolNameLineEdit->setText(this->fullSymbolName);
this->path = Tileset::getExpectedDir(this->fullSymbolName, this->isSecondary);
this->ui->pathLineEdit->setText(this->path);
bool NewTilesetDialog::validateName(bool allowEmpty) {
const QString name = ui->lineEdit_Name->text();
QString errorText;
if (name.isEmpty() || name == symbolPrefix) {
if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Name->text());
} else if (!this->project->isIdentifierUnique(name)) {
errorText = QString("%1 '%2' is not unique.").arg(ui->label_Name->text()).arg(name);
}
bool isValid = errorText.isEmpty();
ui->label_NameError->setText(errorText);
ui->label_NameError->setVisible(!isValid);
ui->lineEdit_Name->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : "");
return isValid;
}
void NewTilesetDialog::FillChanged() {
this->checkerboardFill = this->ui->fillCheckBox->isChecked();
porymapConfig.tilesetCheckerboardFill = this->checkerboardFill;
void NewTilesetDialog::dialogButtonClicked(QAbstractButton *button) {
auto role = ui->buttonBox->buttonRole(button);
if (role == QDialogButtonBox::RejectRole){
reject();
} else if (role == QDialogButtonBox::AcceptRole) {
accept();
}
}
void NewTilesetDialog::accept() {
if (!validateName())
return;
bool secondary = ui->comboBox_Type->currentIndex() == 1;
Tileset *tileset = this->project->createNewTileset(ui->lineEdit_Name->text(), secondary, ui->checkBox_CheckerboardFill->isChecked());
if (!tileset) {
ui->label_GenericError->setText(QString("Failed to create tileset. See %1 for details.").arg(getLogPath()));
ui->label_GenericError->setVisible(true);
return;
}
ui->label_GenericError->setVisible(false);
QDialog::accept();
emit applied(tileset);
}

View File

@ -303,7 +303,7 @@ bool Prefab::tryImportDefaultPrefabs(QWidget * parent, BaseGameVersion version,
// into their project.
QMessageBox::StandardButton prompt =
QMessageBox::question(parent,
"Import Default Prefabs",
QApplication::applicationName(),
QString("Would you like to import the default prefabs for %1? %2.")
.arg(projectConfig.getBaseGameVersionString(version))
.arg(fileWarning),

View File

@ -116,11 +116,6 @@ void ProjectSettingsEditor::initUi() {
ui->lineEdit_BorderMetatiles->setValidator(validator_HexList);
this->setBorderMetatilesUi(projectConfig.useCustomBorderSize);
// Validate that the text added to the warp behavior list could be a valid define
// (we don't care whether it actually is a metatile behavior define)
static const QRegularExpression expression_Word("^[A-Za-z0-9_]*$");
QRegularExpressionValidator *validator_Word = new QRegularExpressionValidator(expression_Word);
ui->comboBox_WarpBehaviors->setValidator(validator_Word);
ui->textEdit_WarpBehaviors->setTextColor(Qt::gray);
// Set spin box limits
@ -298,7 +293,7 @@ QStringList ProjectSettingsEditor::getWarpBehaviorsList() {
void ProjectSettingsEditor::setWarpBehaviorsList(QStringList list) {
list.removeDuplicates();
list.sort();
Project::numericalModeSort(list);
ui->textEdit_WarpBehaviors->setText(list.join("\n"));
}
@ -400,7 +395,7 @@ QString ProjectSettingsEditor::chooseProjectFile(const QString &defaultFilepath)
if (!path.startsWith(this->baseDir)){
// Most of Porymap's file-parsing code for project files will assume that filepaths
// are relative to the root project folder, so we enforce that here.
QMessageBox::warning(this, "Failed to set custom filepath",
QMessageBox::warning(this, QApplication::applicationName(),
QString("Custom filepaths must be inside the root project folder '%1'").arg(this->baseDir));
return QString();
}

View File

@ -28,6 +28,7 @@ RegionMapEditor::RegionMapEditor(QWidget *parent, Project *project) :
this->setAttribute(Qt::WA_DeleteOnClose);
this->ui->setupUi(this);
this->project = project;
connect(this->project, &Project::mapSectionIdNamesChanged, this, &RegionMapEditor::setLocations);
this->configFilepath = QString("%1/%2").arg(this->project->root).arg(projectConfig.getFilePath(ProjectFilePath::json_region_porymap_cfg));
this->initShortcuts();
this->restoreWindowState();
@ -651,10 +652,9 @@ void RegionMapEditor::displayRegionMapLayout() {
void RegionMapEditor::displayRegionMapLayoutOptions() {
if (!this->region_map->layoutEnabled()) return;
this->ui->comboBox_RM_ConnectedMap->blockSignals(true);
const QSignalBlocker b(ui->comboBox_RM_ConnectedMap);
this->ui->comboBox_RM_ConnectedMap->clear();
this->ui->comboBox_RM_ConnectedMap->addItems(this->project->mapSectionIdNames);
this->ui->comboBox_RM_ConnectedMap->blockSignals(false);
this->ui->frame_RM_Options->setEnabled(true);
@ -662,22 +662,19 @@ void RegionMapEditor::displayRegionMapLayoutOptions() {
}
void RegionMapEditor::updateRegionMapLayoutOptions(int index) {
this->ui->comboBox_RM_ConnectedMap->blockSignals(true);
const QSignalBlocker b_ConnectedMap(ui->comboBox_RM_ConnectedMap);
this->ui->comboBox_RM_ConnectedMap->setCurrentText(this->region_map->squareMapSection(index));
this->ui->comboBox_RM_ConnectedMap->blockSignals(false);
this->ui->pushButton_RM_Options_delete->setEnabled(this->region_map->squareHasMap(index));
this->ui->spinBox_RM_LayoutWidth->blockSignals(true);
this->ui->spinBox_RM_LayoutHeight->blockSignals(true);
const QSignalBlocker b_LayoutWidth(ui->spinBox_RM_LayoutWidth);
const QSignalBlocker b_LayoutHeight(ui->spinBox_RM_LayoutHeight);
this->ui->spinBox_RM_LayoutWidth->setMinimum(1);
this->ui->spinBox_RM_LayoutWidth->setMaximum(this->region_map->tilemapWidth() - this->region_map->padLeft());
this->ui->spinBox_RM_LayoutHeight->setMinimum(1);
this->ui->spinBox_RM_LayoutHeight->setMaximum(this->region_map->tilemapHeight() - this->region_map->padTop());
this->ui->spinBox_RM_LayoutWidth->setValue(this->region_map->layoutWidth());
this->ui->spinBox_RM_LayoutHeight->setValue(this->region_map->layoutHeight());
this->ui->spinBox_RM_LayoutWidth->blockSignals(false);
this->ui->spinBox_RM_LayoutHeight->blockSignals(false);
}
void RegionMapEditor::displayRegionMapEntriesImage() {
@ -732,8 +729,12 @@ void RegionMapEditor::displayRegionMapEntryOptions() {
void RegionMapEditor::updateRegionMapEntryOptions(QString section) {
if (!this->region_map->layoutEnabled()) return;
const QSignalBlocker b_X(this->ui->spinBox_RM_Entry_x);
const QSignalBlocker b_Y(this->ui->spinBox_RM_Entry_y);
const QSignalBlocker b_W(this->ui->spinBox_RM_Entry_width);
const QSignalBlocker b_H(this->ui->spinBox_RM_Entry_height);
bool enabled = (section != this->region_map->default_map_section) && this->region_map_entries.contains(section);
this->ui->lineEdit_RM_MapName->setEnabled(enabled);
this->ui->spinBox_RM_Entry_x->setEnabled(enabled);
this->ui->spinBox_RM_Entry_y->setEnabled(enabled);
this->ui->spinBox_RM_Entry_width->setEnabled(enabled);
@ -741,56 +742,31 @@ void RegionMapEditor::updateRegionMapEntryOptions(QString section) {
this->ui->pushButton_entryActivate->setEnabled(section != this->region_map->default_map_section);
this->ui->pushButton_entryActivate->setText(enabled ? "Remove" : "Add");
this->ui->lineEdit_RM_MapName->blockSignals(true);
this->ui->spinBox_RM_Entry_x->blockSignals(true);
this->ui->spinBox_RM_Entry_y->blockSignals(true);
this->ui->spinBox_RM_Entry_width->blockSignals(true);
this->ui->spinBox_RM_Entry_height->blockSignals(true);
this->ui->comboBox_RM_Entry_MapSection->setCurrentText(section);
this->activeEntry = section;
this->region_map_entries_item->currentSection = section;
MapSectionEntry entry = enabled ? this->region_map_entries[section] : MapSectionEntry();
this->ui->lineEdit_RM_MapName->setText(entry.name);
this->ui->spinBox_RM_Entry_x->setValue(entry.x);
this->ui->spinBox_RM_Entry_y->setValue(entry.y);
this->ui->spinBox_RM_Entry_width->setValue(entry.width);
this->ui->spinBox_RM_Entry_height->setValue(entry.height);
this->ui->lineEdit_RM_MapName->blockSignals(false);
this->ui->spinBox_RM_Entry_x->blockSignals(false);
this->ui->spinBox_RM_Entry_y->blockSignals(false);
this->ui->spinBox_RM_Entry_width->blockSignals(false);
this->ui->spinBox_RM_Entry_height->blockSignals(false);
}
void RegionMapEditor::on_pushButton_entryActivate_clicked() {
QString section = this->ui->comboBox_RM_Entry_MapSection->currentText();
if (section == this->region_map->default_map_section) return;
MapSectionEntry oldEntry = this->region_map->getEntry(section);
if (this->region_map_entries.contains(section)) {
// disable
MapSectionEntry oldEntry = this->region_map->getEntry(section);
this->region_map->removeEntry(section);
MapSectionEntry newEntry = this->region_map->getEntry(section);
RemoveEntry *commit = new RemoveEntry(this->region_map, section, oldEntry, newEntry);
this->region_map->editHistory.push(commit);
updateRegionMapEntryOptions(section);
this->ui->pushButton_entryActivate->setText("Add");
this->region_map->editHistory.push(new RemoveEntry(this->region_map, section, oldEntry, MapSectionEntry()));
} else {
// enable
MapSectionEntry oldEntry = this->region_map->getEntry(section);
MapSectionEntry entry = MapSectionEntry();
entry.valid = true;
this->region_map->setEntry(section, entry);
MapSectionEntry newEntry = this->region_map->getEntry(section);
AddEntry *commit = new AddEntry(this->region_map, section, oldEntry, newEntry);
this->region_map->editHistory.push(commit);
updateRegionMapEntryOptions(section);
this->ui->pushButton_entryActivate->setText("Remove");
MapSectionEntry newEntry = MapSectionEntry();
newEntry.valid = true;
this->region_map->editHistory.push(new AddEntry(this->region_map, section, oldEntry, newEntry));
}
updateRegionMapEntryOptions(section);
}
void RegionMapEditor::displayRegionMapTileSelector() {
@ -936,7 +912,7 @@ void RegionMapEditor::on_tabWidget_Region_Map_currentChanged(int index) {
break;
case 2:
this->ui->verticalSlider_Zoom_Image_Tiles->setVisible(false);
on_comboBox_RM_Entry_MapSection_textActivated(ui->comboBox_RM_Entry_MapSection->currentText());
on_comboBox_RM_Entry_MapSection_currentTextChanged(ui->comboBox_RM_Entry_MapSection->currentText());
break;
}
}
@ -947,7 +923,7 @@ void RegionMapEditor::on_comboBox_RM_ConnectedMap_textActivated(const QString &m
onRegionMapLayoutSelectedTileChanged(this->currIndex);// re-draw layout image
}
void RegionMapEditor::on_comboBox_RM_Entry_MapSection_textActivated(const QString &text) {
void RegionMapEditor::on_comboBox_RM_Entry_MapSection_currentTextChanged(const QString &text) {
this->activeEntry = text;
this->region_map_entries_item->currentSection = text;
updateRegionMapEntryOptions(text);
@ -1050,15 +1026,6 @@ void RegionMapEditor::on_spinBox_RM_LayoutHeight_valueChanged(int value) {
}
}
void RegionMapEditor::on_lineEdit_RM_MapName_textEdited(const QString &text) {
if (!this->region_map_entries.contains(activeEntry)) return;
MapSectionEntry oldEntry = this->region_map_entries[activeEntry];
this->region_map_entries[activeEntry].name = text;
MapSectionEntry newEntry = this->region_map_entries[activeEntry];
EditEntry *commit = new EditEntry(this->region_map, activeEntry, oldEntry, newEntry);
this->region_map->editHistory.push(commit);
}
void RegionMapEditor::on_pushButton_RM_Options_delete_clicked() {
int index = this->region_map->tilemapToLayoutIndex(this->currIndex);
QList<LayoutSquare> oldLayout = this->region_map->getLayout(this->region_map->getLayer());
@ -1151,7 +1118,7 @@ void RegionMapEditor::on_action_Swap_triggered() {
connect(&buttonBox, &QDialogButtonBox::accepted, [&popup, &oldSecBox, &newSecBox, &beforeSection, &afterSection](){
beforeSection = oldSecBox->currentText();
afterSection = newSecBox->currentText();
if (!beforeSection.isEmpty() && !afterSection.isEmpty()) {
if (!beforeSection.isEmpty() && !afterSection.isEmpty() && beforeSection != afterSection) {
popup.accept();
}
});
@ -1190,7 +1157,7 @@ void RegionMapEditor::on_action_Replace_triggered() {
connect(&buttonBox, &QDialogButtonBox::accepted, [&popup, &oldSecBox, &newSecBox, &beforeSection, &afterSection](){
beforeSection = oldSecBox->currentText();
afterSection = newSecBox->currentText();
if (!beforeSection.isEmpty() && !afterSection.isEmpty()) {
if (!beforeSection.isEmpty() && !afterSection.isEmpty() && beforeSection != afterSection) {
popup.accept();
}
});
@ -1269,7 +1236,7 @@ void RegionMapEditor::closeEvent(QCloseEvent *event)
if (this->modified()) {
QMessageBox::StandardButton result = QMessageBox::question(
this,
"porymap",
QApplication::applicationName(),
"The region map has been modified, save changes?",
QMessageBox::No | QMessageBox::Yes | QMessageBox::Cancel,
QMessageBox::Yes);
@ -1323,3 +1290,18 @@ void RegionMapEditor::on_verticalSlider_Zoom_Image_Tiles_valueChanged(int val) {
ui->graphicsView_RegionMap_Tiles->setTransform(transform);
ui->graphicsView_RegionMap_Tiles->setFixedSize(width + 2, height + 2);
}
// Repopulate the combo boxes that display MAPSEC names.
void RegionMapEditor::setLocations(const QStringList &locations) {
const QSignalBlocker b_ConnectedMap(ui->comboBox_RM_ConnectedMap);
auto before = ui->comboBox_RM_ConnectedMap->currentText();
ui->comboBox_RM_ConnectedMap->clear();
ui->comboBox_RM_ConnectedMap->addItems(locations);
ui->comboBox_RM_ConnectedMap->setCurrentText(before);
const QSignalBlocker b_MapSection(ui->comboBox_RM_Entry_MapSection);
before = ui->comboBox_RM_Entry_MapSection->currentText();
ui->comboBox_RM_Entry_MapSection->clear();
ui->comboBox_RM_Entry_MapSection->addItems(locations);
ui->comboBox_RM_Entry_MapSection->setCurrentText(before);
}

View File

@ -8,7 +8,7 @@ void RegionMapEntriesPixmapItem::draw() {
int entry_x, entry_y, entry_w, entry_h;
if (!entry.valid || entry.name == region_map->default_map_section) {
if (!entry.valid || currentSection == region_map->default_map_section) {
entry_x = entry_y = 0;
entry_w = entry_h = 1;
} else {

View File

@ -158,7 +158,7 @@ void ShortcutsEditor::promptUserOnDuplicateFound(MultiKeyEdit *sender, MultiKeyE
.arg(duplicateKeySequence.toString()).arg(siblingLabel);
const auto result = QMessageBox::question(
this, "porymap", message, QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
this, QApplication::applicationName(), message, QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (result == QMessageBox::Yes)
removeKeySequence(duplicateKeySequence, sibling);

View File

@ -2,12 +2,13 @@
#include "ui_tileseteditor.h"
#include "log.h"
#include "imageproviders.h"
#include "metatileparser.h"
#include "advancemapparser.h"
#include "paletteutil.h"
#include "imageexport.h"
#include "config.h"
#include "shortcut.h"
#include "filedialog.h"
#include "validator.h"
#include <QMessageBox>
#include <QDialogButtonBox>
#include <QCloseEvent>
@ -20,9 +21,33 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent)
layout(layout),
hasUnsavedChanges(false)
{
this->setAttribute(Qt::WA_DeleteOnClose);
this->setTilesets(this->layout->tileset_primary_label, this->layout->tileset_secondary_label);
this->initUi();
setAttribute(Qt::WA_DeleteOnClose);
setTilesets(this->layout->tileset_primary_label, this->layout->tileset_secondary_label);
ui->setupUi(this);
this->tileXFlip = ui->checkBox_xFlip->isChecked();
this->tileYFlip = ui->checkBox_yFlip->isChecked();
this->paletteId = ui->spinBox_paletteSelector->value();
// TODO: The dividing line at the moment is only accurate if the number of primary metatiles is divisible by 8.
// If it's not, the secondary metatiles will wrap above the line. This has other problems (like skewing
// metatile groups the user may have designed) so this should be fixed by filling the primary metatiles
// image with invalid magenta metatiles until it's divisible by 8. Then the line can be re-enabled as-is.
ui->actionShow_Tileset_Divider->setChecked(/*porymapConfig.showTilesetEditorDivider*/false);
ui->actionShow_Tileset_Divider->setVisible(false);
ui->spinBox_paletteSelector->setMinimum(0);
ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1);
ui->lineEdit_metatileLabel->setValidator(new IdentifierValidator(this));
setAttributesUi();
initMetatileSelector();
initMetatileLayersItem();
initTileSelector();
initSelectedTileItem();
initShortcuts();
this->metatileSelector->select(0);
restoreWindowState();
}
TilesetEditor::~TilesetEditor()
@ -57,7 +82,7 @@ void TilesetEditor::updateTilesets(QString primaryTilesetLabel, QString secondar
if (this->hasUnsavedChanges) {
QMessageBox::StandardButton result = QMessageBox::question(
this,
"porymap",
QApplication::applicationName(),
"Tileset has been modified, save changes?",
QMessageBox::No | QMessageBox::Yes,
QMessageBox::Yes);
@ -92,33 +117,6 @@ void TilesetEditor::setTilesets(QString primaryTilesetLabel, QString secondaryTi
this->initMetatileHistory();
}
void TilesetEditor::initUi() {
ui->setupUi(this);
this->tileXFlip = ui->checkBox_xFlip->isChecked();
this->tileYFlip = ui->checkBox_yFlip->isChecked();
this->paletteId = ui->spinBox_paletteSelector->value();
this->ui->spinBox_paletteSelector->setMinimum(0);
this->ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1);
// TODO: The dividing line at the moment is only accurate if the number of primary metatiles is divisible by 8.
// If it's not, the secondary metatiles will wrap above the line. This has other problems (like skewing
// metatile groups the user may have designed) so this should be fixed by filling the primary metatiles
// image with invalid magenta metatiles until it's divisible by 8. Then the line can be re-enabled as-is.
this->ui->actionShow_Tileset_Divider->setChecked(/*porymapConfig.showTilesetEditorDivider*/false);
this->ui->actionShow_Tileset_Divider->setVisible(false);
this->setAttributesUi();
this->setMetatileLabelValidator();
this->initMetatileSelector();
this->initMetatileLayersItem();
this->initTileSelector();
this->initSelectedTileItem();
this->initShortcuts();
this->metatileSelector->select(0);
this->restoreWindowState();
}
void TilesetEditor::setAttributesUi() {
// Behavior
if (projectConfig.metatileBehaviorMask) {
@ -178,13 +176,6 @@ void TilesetEditor::setAttributesUi() {
this->ui->frame_Properties->adjustSize();
}
void TilesetEditor::setMetatileLabelValidator() {
//only allow characters valid for a symbol
static const QRegularExpression expression("[_A-Za-z0-9]*$");
QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression);
this->ui->lineEdit_metatileLabel->setValidator(validator);
}
void TilesetEditor::initMetatileSelector()
{
this->metatileSelector = new TilesetEditorMetatileSelector(this->primaryTileset, this->secondaryTileset, this->layout);
@ -726,22 +717,9 @@ void TilesetEditor::importTilesetTiles(Tileset *tileset, bool primary) {
image = image.convertToFormat(QImage::Format::Format_Indexed8, colorTable);
}
// Validate image is properly indexed to 16 colors.
int colorCount = image.colorCount();
if (colorCount > 16) {
flattenTo4bppImage(&image);
} else if (colorCount < 16) {
QVector<QRgb> colorTable = image.colorTable();
for (int i = colorTable.length(); i < 16; i++) {
colorTable.append(Qt::black);
}
image.setColorTable(colorTable);
}
this->project->loadTilesetTiles(tileset, image);
tileset->loadTilesImage(&image);
this->refresh();
this->hasUnsavedChanges = true;
tileset->hasUnsavedTilesImage = true;
}
void TilesetEditor::closeEvent(QCloseEvent *event)
@ -749,7 +727,7 @@ void TilesetEditor::closeEvent(QCloseEvent *event)
if (this->hasUnsavedChanges) {
QMessageBox::StandardButton result = QMessageBox::question(
this,
"porymap",
QApplication::applicationName(),
"Tileset has been modified, save changes?",
QMessageBox::No | QMessageBox::Yes | QMessageBox::Cancel,
QMessageBox::Yes);
@ -988,7 +966,7 @@ void TilesetEditor::importTilesetMetatiles(Tileset *tileset, bool primary)
}
bool error = false;
QList<Metatile*> metatiles = MetatileParser::parse(filepath, &error, primary);
QList<Metatile*> metatiles = AdvanceMapParser::parseMetatiles(filepath, &error, primary);
if (error) {
QMessageBox msgBox(this);
msgBox.setText("Failed to import metatiles from Advance Map 1.92 .bvd file.");
@ -997,6 +975,7 @@ void TilesetEditor::importTilesetMetatiles(Tileset *tileset, bool primary)
msgBox.setDefaultButton(QMessageBox::Ok);
msgBox.setIcon(QMessageBox::Icon::Critical);
msgBox.exec();
qDeleteAll(metatiles);
return;
}

View File

@ -465,7 +465,7 @@ void WildMonChart::showHelpDialog() {
informativeText = levelTabInfo;
}
QMessageBox msgBox(QMessageBox::Information, "porymap", text, QMessageBox::Close, this);
QMessageBox msgBox(QMessageBox::Information, QApplication::applicationName(), text, QMessageBox::Close, this);
msgBox.setTextFormat(Qt::RichText);
msgBox.setInformativeText(informativeText);
msgBox.exec();