Merge branch 'master' of https://github.com/huderlem/porymap into encounter-graph

This commit is contained in:
GriffinR 2024-08-26 23:52:34 -04:00
commit dba3e524b3
38 changed files with 2560 additions and 1179 deletions

View file

@ -8,10 +8,14 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
## [Unreleased]
### Added
- Redesigned the Connections tab, adding a number of new features including the option to open or display diving maps and a list UI for easier edit access.
- Add a `Close Project` option
- An alert will be displayed when attempting to open a seemingly invalid project.
### Changed
- Edits to map connections now have Undo/Redo and can be viewed in exported timelapses.
- Changes to the "Mirror to Connecting Maps" setting will now be saved between sessions.
- A notice will be displayed when attempting to open the "Dynamic" map, rather than nothing happening.
- The base game version is now auto-detected if the project name contains only one of "emerald", "firered/leafgreen", or "ruby/sapphire".
- It's now possible to cancel quitting if there are unsaved changes in sub-windows.
@ -25,6 +29,13 @@ The **"Breaking Changes"** listed below are changes that have been made in the d
- Fix selections with multiple Events not always clearing when making a new selection.
- Fix `About porymap` opening a new window each time it's activated.
- Fix the `Edit History` window not raising to the front when reactivated.
- New maps are now always inserted in map dropdowns at the correct position, rather than at the bottom of the list until the project is reloaded.
- Fix changes to map connections not marking connected maps as unsaved.
- Fix numerous issues related to connecting a map to itself.
- Fix incorrect map connections getting selected when opening a map by double-clicking a map connection.
- Fix a visual issue when quickly dragging map connections around.
- Fix map connections rendering incorrectly if their direction name was unknown.
- Fix map connections rendering incorrectly if their dimensions were smaller than the border draw distance.
## [5.4.1] - 2024-03-21
### Fixed

View file

@ -2,35 +2,40 @@
Editing Map Connections
***********************
Maps can be connected together so that the player can seamlessly walk between them. These connections can be edited in the Connections tab.
Maps can be connected together so that the player can seamlessly walk between them. These connections can be edited in the Connections tab.
.. figure:: images/editing-map-connections/map-connections.png
:alt: Map Connections View
Map Connections View
A connection has a direction, offset, and destination map. To add new connection, press the plus button |add-connection-button|. To delete a connection, select it and press the delete button |remove-connection-button|.
A connection has a direction, offset, and destination map. To add a new connection, press the |add-connection-button| button. To delete a connection you can either press the |remove-connection-button| button on its entry in the list, or select the connection and press the delete key.
The |open-connection-button| button will open the destination map. You may also open the destination map by double-clicking the connection itself (this can be done from the ``Map`` and ``Events`` tabs as well).
.. |add-connection-button|
image:: images/editing-map-connections/add-connection-button.png
:height: 24
.. |remove-connection-button|
image:: images/editing-map-connections/remove-connection-button.png
:height: 24
.. |open-connection-button|
image:: images/editing-map-connections/open-connection-button.png
:height: 24
To change the connection's vertical or horizontal offset, it's easiest to click and drag the connection to the desired offset.
Dive & Emerge Warps
-------------------
Dive & emerge warps are used for the HM move Dive. They don't have offsets or directions--only a destination map. To add a dive or emerge warp, simply add a value in the Dive Map and/or Emerge Map dropdown menus.
Dive & emerge warps are used for the HM move Dive. They don't have offsets or directions--only a destination map. To add a dive or emerge warp, simply add a value in the Dive Map and/or Emerge Map dropdown menus.
You can select the ``Show Emerge/Dive Maps`` checkbox to view your connected dive/emerge maps overlaid on the current map. The slider will change the opacity of this overlay. If you have both an emerge and a dive map connected you will see two sliders; the top slider is for the emerge map's opacity, and the bottom slider is for the dive map's opacity.
Mirror Connections
------------------
An extremely useful feature is the *Mirror to Connecting Maps* checkbox in the top-right corner. Connections are one-way, which means that you must keep the two connections in sync between the two maps. For example, Petalburg City has a west connection to Route 104, and Route 104 has an east connection to Petalburg City. If *Mirror to Connecting Maps* is enabled, then Porymap will automatically update both sides of the connection. Be sure to *File -> Save All* (``Ctrl+Shift+S``) when saving, since you will need to save both maps' connections.
Follow Connections
------------------
Double-clicking on a connection will open the destination map. This is very useful for navigating through your maps, similar to double-clicking on :ref:`Warp Events <event-warps>`.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 890 B

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 431 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View file

@ -0,0 +1,132 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConnectionsListItem</class>
<widget class="QFrame" name="ConnectionsListItem">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>178</width>
<height>157</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">.ConnectionsListItem { border-width: 1px; }</string>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_Map">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Map</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_Offset">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Offset</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_Direction">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Direction</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QToolButton" name="button_Delete">
<property name="toolTip">
<string>Remove this connection.</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../resources/images.qrc">
<normaloff>:/icons/delete.ico</normaloff>:/icons/delete.ico</iconset>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="NoScrollComboBox" name="comboBox_Direction">
<property name="toolTip">
<string>Where the connected map should be positioned relative to the current map.</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="NoScrollComboBox" name="comboBox_Map">
<property name="toolTip">
<string>The name of the map to connect to the current map.</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="NoScrollSpinBox" name="spinBox_Offset">
<property name="toolTip">
<string>The number of spaces to move the connected map perpendicular to its connected direction.</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QToolButton" name="button_OpenMap">
<property name="toolTip">
<string>Open the connected map.</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../resources/images.qrc">
<normaloff>:/icons/map_go.ico</normaloff>:/icons/map_go.ico</iconset>
</property>
</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>
<include location="../resources/images.qrc"/>
</resources>
<connections/>
</ui>

View file

@ -1715,7 +1715,7 @@
<x>0</x>
<y>0</y>
<width>100</width>
<height>16</height>
<height>30</height>
</rect>
</property>
<property name="sizePolicy">
@ -1809,7 +1809,7 @@
<x>0</x>
<y>0</y>
<width>100</width>
<height>16</height>
<height>30</height>
</rect>
</property>
<property name="sizePolicy">
@ -1903,7 +1903,7 @@
<x>0</x>
<y>0</y>
<width>100</width>
<height>16</height>
<height>30</height>
</rect>
</property>
<property name="sizePolicy">
@ -2003,7 +2003,7 @@
<x>0</x>
<y>0</y>
<width>100</width>
<height>16</height>
<height>30</height>
</rect>
</property>
<property name="sizePolicy">
@ -2097,7 +2097,7 @@
<x>0</x>
<y>0</y>
<width>100</width>
<height>16</height>
<height>30</height>
</rect>
</property>
<property name="sizePolicy">
@ -2541,7 +2541,7 @@
<number>0</number>
</property>
<item row="3" column="0">
<widget class="QFrame" name="gridFrame">
<widget class="QFrame" name="frame_Connections">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
@ -2554,24 +2554,9 @@
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QGridLayout" name="gridLayout_11">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
<number>0</number>
</property>
<item row="2" column="0">
<widget class="QFrame" name="horizontalFrame">
<layout class="QVBoxLayout" name="verticalLayout_18">
<item>
<widget class="QFrame" name="frame_ConnectionsTopBar">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
@ -2590,10 +2575,7 @@
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="spacing">
<number>4</number>
</property>
<layout class="QGridLayout" name="gridLayout_10">
<property name="leftMargin">
<number>4</number>
</property>
@ -2606,82 +2588,20 @@
<property name="bottomMargin">
<number>4</number>
</property>
<item>
<widget class="QPushButton" name="pushButton_AddConnection">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item row="1" column="3">
<widget class="NoScrollComboBox" name="comboBox_DiveMap">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Add a new connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Destination map name when using &lt;span style=&quot; font-weight:600;&quot;&gt;Dive&lt;/span&gt;. If empty, no such connection will exist.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<activeon>:/icons/add.ico</activeon>
</iconset>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_RemoveConnection">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Remove the currently-selected connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<activeon>:/icons/delete.ico</activeon>
</iconset>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_13">
<property name="text">
<string>Number of Connections:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_NumConnections">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_11">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<item row="0" column="7">
<widget class="QCheckBox" name="checkBox_MirrorConnections">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If enabled, connections will automatically be updated on the connected map.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>If enabled, connections will automatically be updated on the connected map.</string>
</property>
<property name="text">
<string>Mirror to Connecting Maps</string>
@ -2691,144 +2611,31 @@
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="6" column="0">
<widget class="QGraphicsView" name="graphicsView_Connections">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="mouseTracking">
<bool>false</bool>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustIgnored</enum>
</property>
<property name="dragMode">
<enum>QGraphicsView::NoDrag</enum>
</property>
<property name="transformationAnchor">
<enum>QGraphicsView::AnchorUnderMouse</enum>
</property>
<property name="resizeAnchor">
<enum>QGraphicsView::AnchorUnderMouse</enum>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QFrame" name="horizontalFrame2">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>32</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="0,0,0,0,0,0">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item>
<widget class="QLabel" name="label_11">
<item row="1" column="1">
<widget class="QLabel" name="label_DiveMap">
<property name="text">
<string>Map</string>
<string>Dive Map</string>
</property>
</widget>
</item>
<item>
<widget class="NoScrollComboBox" name="comboBox_ConnectedMap">
<item row="0" column="1">
<widget class="QLabel" name="label_EmergeMap">
<property name="text">
<string>Emerge Map</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="NoScrollComboBox" name="comboBox_EmergeMap">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The destination map name of the connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Destination map name when emerging using &lt;span style=&quot; font-weight:600;&quot;&gt;Dive&lt;/span&gt;. If empty, no such connection will exist.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_12">
<property name="text">
<string>Offset</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBox_ConnectionOffset">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The number of metatiles to offset the connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="minimum">
<number>-999</number>
</property>
<property name="maximum">
<number>999</number>
</property>
</widget>
</item>
<item>
<widget class="NoScrollComboBox" name="comboBox_ConnectionDirection">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The direction of the connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<item>
<property name="text">
<string>up</string>
</property>
</item>
<item>
<property name="text">
<string>right</string>
</property>
</item>
<item>
<property name="text">
<string>down</string>
</property>
</item>
<item>
<property name="text">
<string>left</string>
</property>
</item>
</widget>
</item>
<item>
<item row="0" column="6">
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -2841,87 +2648,274 @@
</property>
</spacer>
</item>
<item row="1" column="0">
<widget class="QToolButton" name="button_OpenDiveMap">
<property name="toolTip">
<string>Open the selected Dive Map</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../resources/images.qrc">
<normaloff>:/icons/map_go.ico</normaloff>:/icons/map_go.ico</iconset>
</property>
</widget>
</item>
<item row="0" column="4" rowspan="2">
<widget class="QGroupBox" name="groupBox_DiveMapOpacity">
<property name="toolTip">
<string>If enabled, the connected Emerge and/or Dive maps will be displayed with an opacity set using the slider.</string>
</property>
<property name="title">
<string>Show Emerge/Dive Maps</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<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="QStackedWidget" name="stackedWidget_DiveMapOpacity">
<property name="currentIndex">
<number>1</number>
</property>
<widget class="QWidget" name="page_OpacitySeparate">
<layout class="QVBoxLayout" name="verticalLayout_19">
<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="QSlider" name="slider_EmergeMapOpacity">
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>90</number>
</property>
<property name="value">
<number>30</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="slider_DiveMapOpacity">
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>90</number>
</property>
<property name="value">
<number>30</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_OpacityCombined">
<layout class="QVBoxLayout" name="verticalLayout_20">
<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="QSlider" name="slider_DiveEmergeMapOpacity">
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>90</number>
</property>
<property name="value">
<number>30</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0">
<widget class="QToolButton" name="button_OpenEmergeMap">
<property name="toolTip">
<string>Open the selected Emerge Map</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../resources/images.qrc">
<normaloff>:/icons/map_go.ico</normaloff>:/icons/map_go.ico</iconset>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="5" column="0">
<widget class="QFrame" name="horizontalFrame3">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<item>
<widget class="QSplitter" name="splitter_Connections">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="spacing">
<number>4</number>
<widget class="QGraphicsView" name="graphicsView_Connections">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="leftMargin">
<number>4</number>
<property name="mouseTracking">
<bool>false</bool>
</property>
<property name="topMargin">
<number>4</number>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="rightMargin">
<number>4</number>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<property name="bottomMargin">
<number>4</number>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<item>
<widget class="QLabel" name="label_14">
<property name="text">
<string>Dive Map</string>
</property>
</widget>
</item>
<item>
<widget class="NoScrollComboBox" name="comboBox_DiveMap">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Destination map name when using &lt;span style=&quot; font-weight:600;&quot;&gt;Dive&lt;/span&gt;. If empty, no such connection will exist.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_15">
<property name="text">
<string>Emerge Map</string>
</property>
</widget>
</item>
<item>
<widget class="NoScrollComboBox" name="comboBox_EmergeMap">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Destination map name when emerging using &lt;span style=&quot; font-weight:600;&quot;&gt;Dive&lt;/span&gt;. If empty, no such connection will exist.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_10">
<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>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustIgnored</enum>
</property>
<property name="dragMode">
<enum>QGraphicsView::NoDrag</enum>
</property>
<property name="transformationAnchor">
<enum>QGraphicsView::AnchorUnderMouse</enum>
</property>
<property name="resizeAnchor">
<enum>QGraphicsView::AnchorUnderMouse</enum>
</property>
</widget>
<widget class="QFrame" name="frame_EditConnectionsPanel">
<property name="minimumSize">
<size>
<width>230</width>
<height>0</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_17">
<item>
<widget class="QPushButton" name="pushButton_AddConnection">
<property name="text">
<string>Add Connection</string>
</property>
<property name="icon">
<iconset resource="../resources/images.qrc">
<normaloff>:/icons/add.ico</normaloff>:/icons/add.ico</iconset>
</property>
</widget>
</item>
<item>
<widget class="QScrollArea" name="scrollArea_ConnectionsList">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaContents_ConnectionsList">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>365</width>
<height>651</height>
</rect>
</property>
<layout class="QVBoxLayout" name="layout_ConnectionsList">
<property name="spacing">
<number>8</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
@ -3092,6 +3086,7 @@
<addaction name="actionCursor_Tile_Outline"/>
<addaction name="actionPlayer_View_Rectangle"/>
<addaction name="actionBetter_Cursors"/>
<addaction name="actionDive_Emerge_Map"/>
</widget>
<widget class="QMenu" name="menuTools">
<property name="title">
@ -3431,6 +3426,17 @@
<string>Ctrl+W</string>
</property>
</action>
<action name="actionDive_Emerge_Map">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Dive/Emerge Map</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>

View file

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NewMapConnectionDialog</class>
<widget class="QDialog" name="NewMapConnectionDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>234</width>
<height>162</height>
</rect>
</property>
<property name="windowTitle">
<string>Add New Map Connection</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<layout class="QFormLayout" name="formLayout_2">
<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_Map">
<property name="text">
<string>Map</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="NoScrollComboBox" name="comboBox_Map">
<property name="toolTip">
<string>The name of the map to connect to the current map.</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_Direction">
<property name="text">
<string>Direction</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="NoScrollComboBox" name="comboBox_Direction">
<property name="toolTip">
<string>Where the connected map should be positioned relative to the current map.</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="label_Warning">
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 0)</string>
</property>
<property name="text">
<string>'Map' must be the name of an existing map.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</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>NewMapConnectionDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>20</x>
<y>20</y>
</hint>
<hint type="destinationlabel">
<x>20</x>
<y>20</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>NewMapConnectionDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>20</x>
<y>20</y>
</hint>
<hint type="destinationlabel">
<x>20</x>
<y>20</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -58,6 +58,11 @@ public:
this->reopenOnLaunch = true;
this->mapSortOrder = MapSortOrder::Group;
this->prettyCursors = true;
this->mirrorConnectingMaps = true;
this->showDiveEmergeMaps = false;
this->diveEmergeMapOpacity = 30;
this->diveMapOpacity = 15;
this->emergeMapOpacity = 15;
this->collisionOpacity = 50;
this->collisionZoom = 30;
this->metatilesZoom = 30;
@ -104,6 +109,11 @@ public:
bool projectManuallyClosed;
MapSortOrder mapSortOrder;
bool prettyCursors;
bool mirrorConnectingMaps;
bool showDiveEmergeMaps;
int diveEmergeMapOpacity;
int diveMapOpacity;
int emergeMapOpacity;
int collisionOpacity;
int collisionZoom;
int metatilesZoom;

View file

@ -3,9 +3,11 @@
#define EDITCOMMANDS_H
#include "blockdata.h"
#include "mapconnection.h"
#include <QUndoCommand>
#include <QList>
#include <QPointer>
class MapPixmapItem;
class Map;
@ -31,6 +33,11 @@ enum CommandId {
ID_EventDelete,
ID_EventDuplicate,
ID_EventPaste,
ID_MapConnectionMove,
ID_MapConnectionChangeDirection,
ID_MapConnectionChangeMap,
ID_MapConnectionAdd,
ID_MapConnectionRemove,
};
#define IDMask_EventType_Object (1 << 8)
@ -379,4 +386,113 @@ private:
int newBorderHeight;
};
/// Implements a command to commit Map Connectien move actions.
/// Actions are merged into one until the mouse is released when editing by click-and-drag,
/// or when the offset spin box loses focus when editing with the list UI.
class MapConnectionMove : public QUndoCommand {
public:
MapConnectionMove(MapConnection *connection, int newOffset, unsigned actionId,
QUndoCommand *parent = nullptr);
void undo() override;
void redo() override;
bool mergeWith(const QUndoCommand *command) override;
int id() const override { return CommandId::ID_MapConnectionMove; }
private:
MapConnection *connection;
int newOffset;
int oldOffset;
bool mirrored;
unsigned actionId;
};
/// Implements a command to commit changes to a Map Connectien's 'direction' field.
class MapConnectionChangeDirection : public QUndoCommand {
public:
MapConnectionChangeDirection(MapConnection *connection, QString newDirection,
QUndoCommand *parent = nullptr);
void undo() override;
void redo() override;
int id() const override { return CommandId::ID_MapConnectionChangeDirection; }
private:
QPointer<MapConnection> connection;
QString newDirection;
QString oldDirection;
int oldOffset;
int newOffset;
bool mirrored;
};
/// Implements a command to commit changes to a Map Connectien's 'map' field.
class MapConnectionChangeMap : public QUndoCommand {
public:
MapConnectionChangeMap(MapConnection *connection, QString newMapName,
QUndoCommand *parent = nullptr);
void undo() override;
void redo() override;
int id() const override { return CommandId::ID_MapConnectionChangeMap; }
private:
QPointer<MapConnection> connection;
QString newMapName;
QString oldMapName;
int oldOffset;
int newOffset;
bool mirrored;
};
/// Implements a command to commit adding a Map Connection to a map.
class MapConnectionAdd : public QUndoCommand {
public:
MapConnectionAdd(Map *map, MapConnection *connection,
QUndoCommand *parent = nullptr);
void undo() override;
void redo() override;
int id() const override { return CommandId::ID_MapConnectionAdd; }
private:
Map *map = nullptr;
Map *mirrorMap = nullptr;
QPointer<MapConnection> connection = nullptr;
QPointer<MapConnection> mirror = nullptr;
};
/// Implements a command to commit removing a Map Connection from a map.
class MapConnectionRemove : public QUndoCommand {
public:
MapConnectionRemove(Map *map, MapConnection *connection,
QUndoCommand *parent = nullptr);
void undo() override;
void redo() override;
int id() const override { return CommandId::ID_MapConnectionRemove; }
private:
Map *map = nullptr;
Map *mirrorMap = nullptr;
QPointer<MapConnection> connection = nullptr;
QPointer<MapConnection> mirror = nullptr;
};
#endif // EDITCOMMANDS_H

View file

@ -69,7 +69,6 @@ public:
QMap<Event::Group, QList<Event *>> events;
QList<Event *> ownedEvents; // for memory management
QList<MapConnection*> connections;
QList<int> metatileLayerOrder;
QList<float> metatileLayerOpacity;
@ -98,7 +97,13 @@ public:
QStringList getScriptLabels(Event::Group group = Event::Group::None);
void removeEvent(Event *);
void addEvent(Event *);
QPixmap renderConnection(MapConnection, MapLayout *);
void deleteConnections();
QList<MapConnection*> getConnections() const;
void removeConnection(MapConnection *);
void addConnection(MapConnection *);
void loadConnection(MapConnection *);
QRect getConnectionRect(const QString &direction, MapLayout *fromLayout = nullptr);
QPixmap renderConnection(const QString &direction, MapLayout *fromLayout = nullptr);
QPixmap renderBorder(bool ignoreCache = false);
void setDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false);
void setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false);
@ -122,17 +127,24 @@ public:
QUndoStack editHistory;
void modify();
void clean();
void pruneEditHistory();
private:
void setNewDimensionsBlockdata(int newWidth, int newHeight);
void setNewBorderDimensionsBlockdata(int newWidth, int newHeight);
void trackConnection(MapConnection*);
// MapConnections in 'ownedConnections' but not 'connections' persist in the edit history.
QList<MapConnection*> connections;
QSet<MapConnection*> ownedConnections;
signals:
void mapChanged(Map *map);
void modified();
void mapDimensionsChanged(const QSize &size);
void mapNeedsRedrawing();
void openScriptRequested(QString label);
void connectionAdded(MapConnection*);
void connectionRemoved(MapConnection*);
};
#endif // MAP_H

View file

@ -3,21 +3,61 @@
#define MAPCONNECTION_H
#include <QString>
#include <QHash>
#include <QObject>
#include <QMap>
class MapConnection {
class Project;
class Map;
class MapConnection : public QObject
{
Q_OBJECT
public:
QString direction;
int offset;
QString map_name;
MapConnection(const QString &targetMapName, const QString &direction, int offset = 0);
Map* parentMap() const { return m_parentMap; }
QString parentMapName() const;
void setParentMap(Map* map, bool mirror = true);
Map* targetMap() const;
QString targetMapName() const { return m_targetMapName; }
void setTargetMapName(const QString &targetMapName, bool mirror = true);
QString direction() const { return m_direction; }
void setDirection(const QString &direction, bool mirror = true);
int offset() const { return m_offset; }
void setOffset(int offset, bool mirror = true);
MapConnection* findMirror();
MapConnection* createMirror();
QPixmap getPixmap();
static QPointer<Project> project;
static const QMap<QString, QString> oppositeDirections;
static const QStringList cardinalDirections;
static bool isCardinal(const QString &direction);
static bool isHorizontal(const QString &direction);
static bool isVertical(const QString &direction);
static bool isDiving(const QString &direction);
static QString oppositeDirection(const QString &direction) { return oppositeDirections.value(direction, direction); }
static bool areMirrored(const MapConnection*, const MapConnection*);
private:
Map* m_parentMap;
QString m_targetMapName;
QString m_direction;
int m_offset;
void markMapEdited();
Map* getMap(const QString& mapName) const;
signals:
void parentMapChanged(Map* before, Map* after);
void targetMapNameChanged(QString before, QString after);
void directionChanged(QString before, QString after);
void offsetChanged(int before, int after);
};
inline bool operator==(const MapConnection &c1, const MapConnection &c2) {
return c1.map_name == c2.map_name;
}
inline uint qHash(const MapConnection &key) {
return qHash(key.map_name);
}
#endif // MAPCONNECTION_H

View file

@ -18,6 +18,7 @@
#include "ui_mainwindow.h"
#include "bordermetatilespixmapitem.h"
#include "connectionpixmapitem.h"
#include "divingmappixmapitem.h"
#include "currentselectedmetatilespixmapitem.h"
#include "collisionpixmapitem.h"
#include "mappixmapitem.h"
@ -46,6 +47,7 @@ public:
QPointer<Project> project = nullptr;
Map *map = nullptr;
Settings *settings;
void setProject(Project * project);
void saveProject();
void save();
void closeProject();
@ -74,19 +76,18 @@ public:
void setEditingObjects();
void setEditingConnections();
void setMapEditingButtonsEnabled(bool enabled);
void setCurrentConnectionDirection(QString curDirection);
void updateCurrentConnectionDirection(QString curDirection);
void setConnectionsVisibility(bool visible);
void updateConnectionOffset(int offset);
void setConnectionMap(QString mapName);
void addNewConnection();
void removeCurrentConnection();
void updateDivingMapsVisibility();
void renderDivingConnections();
void addConnection(MapConnection* connection);
void removeConnection(MapConnection* connection);
void removeSelectedConnection();
void addNewWildMonGroup(QWidget *window);
void deleteWildMonGroup();
QTableView* getCurrentWildMonTable();
void updateDiveMap(QString mapName);
void updateEmergeMap(QString mapName);
void setSelectedConnectionFromMap(QString mapName);
void setSelectedConnection(MapConnection *connection);
void updatePrimaryTileset(QString tilesetLabel, bool forceLoad = false);
void updateSecondaryTileset(QString tilesetLabel, bool forceLoad = false);
void toggleBorderVisibility(bool visible, bool enableScriptCallback = true);
@ -111,8 +112,8 @@ public:
QPointer<QGraphicsScene> scene = nullptr;
QGraphicsPixmapItem *current_view = nullptr;
QPointer<MapPixmapItem> map_item = nullptr;
ConnectionPixmapItem* selected_connection_item = nullptr;
QList<ConnectionPixmapItem*> connection_items;
QList<QPointer<ConnectionPixmapItem>> connection_items;
QMap<QString, QPointer<DivingMapPixmapItem>> diving_map_items;
QGraphicsPathItem *connection_mask = nullptr;
QPointer<CollisionPixmapItem> collision_item = nullptr;
QGraphicsItemGroup *events_group = nullptr;
@ -133,6 +134,8 @@ public:
QPointer<MovementPermissionsSelector> movement_permissions_selector_item = nullptr;
QList<DraggablePixmapItem *> *selected_events = nullptr;
QPointer<ConnectionPixmapItem> selected_connection_item = nullptr;
QPointer<MapConnection> connection_to_select = nullptr;
QString map_edit_mode = "paint";
QString obj_edit_mode = "select";
@ -176,25 +179,20 @@ private:
void clearBorderMetatiles();
void clearCurrentMetatilesSelection();
void clearMapEvents();
//void clearMapConnections();
void clearMapConnections();
void clearConnectionMask();
void clearMapBorder();
void clearMapGrid();
void clearWildMonTables();
void setConnectionItemsVisible(bool);
void setBorderItemsVisible(bool, qreal = 1);
void setConnectionEditControlValues(MapConnection*);
void setConnectionEditControlsEnabled(bool);
void setConnectionsEditable(bool);
void createConnectionItem(MapConnection* connection);
void populateConnectionMapPickers();
void setDiveEmergeControls();
void updateDiveEmergeMap(QString mapName, QString direction);
void onConnectionOffsetChanged(int newOffset);
void removeMirroredConnection(MapConnection*);
void updateMirroredConnectionOffset(MapConnection*);
void updateMirroredConnectionDirection(MapConnection*, QString);
void updateMirroredConnectionMap(MapConnection*, QString);
void updateMirroredConnection(MapConnection*, QString, QString, bool isDelete = false);
void updateBorderVisibility();
void disconnectMapConnection(MapConnection *connection);
QPoint getConnectionOrigin(MapConnection *connection);
void removeConnectionPixmap(MapConnection *connection);
void updateConnectionPixmap(ConnectionPixmapItem *connectionItem);
void displayConnection(MapConnection *connection);
void displayDivingConnection(MapConnection *connection);
void setDivingMapName(QString mapName, QString direction);
void removeDivingMapPixmap(MapConnection *connection);
void updateEncounterFields(EncounterFields newFields);
QString getMovementPermissionText(uint16_t collision, uint16_t elevation);
QString getMetatileDisplayMessage(uint16_t metatileId);
@ -210,10 +208,7 @@ private slots:
void setStraightPathCursorMode(QGraphicsSceneMouseEvent *event);
void mouseEvent_map(QGraphicsSceneMouseEvent *event, MapPixmapItem *item);
void mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixmapItem *item);
void onConnectionMoved(MapConnection*);
void onConnectionItemSelected(ConnectionPixmapItem* connectionItem);
void onConnectionItemDoubleClicked(ConnectionPixmapItem* connectionItem);
void onConnectionDirectionChanged(QString newDirection);
void setSelectedConnectionItem(ConnectionPixmapItem *connectionItem);
void onHoveredMovementPermissionChanged(uint16_t, uint16_t);
void onHoveredMovementPermissionCleared();
void onHoveredMetatileSelectionChanged(uint16_t);
@ -228,12 +223,11 @@ private slots:
signals:
void objectsChanged();
void loadMapRequested(QString, QString);
void openConnectedMap(MapConnection*);
void wildMonDataChanged();
void warpEventDoubleClicked(QString, int, Event::Group);
void currentMetatilesSelectionChanged();
void mapRulerStatusChanged(const QString &);
void editedMapData();
void tilesetUpdated(QString);
};

View file

@ -180,14 +180,14 @@ private slots:
void copy();
void paste();
void onLoadMapRequested(QString, QString);
void onMapChanged(Map *map);
void onOpenConnectedMap(MapConnection*);
void onMapNeedsRedrawing();
void onTilesetsSaved(QString, QString);
void onWildMonDataChanged();
void openNewMapPopupWindow();
void onNewMapCreated();
void onMapCacheCleared();
void onMapLoaded(Map *map);
void importMapFromAdvanceMap1_92();
void onMapRulerStatusChanged(const QString &);
void applyUserShortcuts();
@ -220,6 +220,7 @@ private slots:
void on_actionMove_triggered();
void on_actionMap_Shift_triggered();
void onDeleteKeyPressed();
void on_toolButton_deleteObject_clicked();
void addNewEvent(Event::Type type);
@ -246,11 +247,9 @@ private slots:
void on_actionExport_Map_Timelapse_Image_triggered();
void on_actionImport_Map_from_Advance_Map_1_92_triggered();
void on_comboBox_ConnectionDirection_currentTextChanged(const QString &arg1);
void on_spinBox_ConnectionOffset_valueChanged(int offset);
void on_comboBox_ConnectedMap_currentTextChanged(const QString &mapName);
void on_pushButton_AddConnection_clicked();
void on_pushButton_RemoveConnection_clicked();
void on_button_OpenDiveMap_clicked();
void on_button_OpenEmergeMap_clicked();
void on_comboBox_DiveMap_currentTextChanged(const QString &mapName);
void on_comboBox_EmergeMap_currentTextChanged(const QString &mapName);
void on_comboBox_PrimaryTileset_currentTextChanged(const QString &arg1);
@ -273,6 +272,12 @@ private slots:
void eventTabChanged(int index);
void on_checkBox_MirrorConnections_stateChanged(int selected);
void on_actionDive_Emerge_Map_triggered();
void on_groupBox_DiveMapOpacity_toggled(bool on);
void on_slider_DiveEmergeMapOpacity_valueChanged(int value);
void on_slider_DiveMapOpacity_valueChanged(int value);
void on_slider_EmergeMapOpacity_valueChanged(int value);
void on_horizontalSlider_CollisionTransparency_valueChanged(int value);
void on_toolButton_ExpandAll_clicked();
void on_toolButton_CollapseAll_clicked();
@ -321,9 +326,7 @@ private:
QStandardItemModel *mapListModel;
QList<QStandardItem*> *mapGroupItemsList;
QMap<QString, QModelIndex> mapListIndexes;
QIcon* mapIcon;
QIcon* mapEditedIcon;
QIcon* mapOpenedIcon;
QIcon mapIcon;
QAction *undoAction = nullptr;
QAction *redoAction = nullptr;
@ -334,10 +337,10 @@ private:
QMap<Event::Group, DraggablePixmapItem*> lastSelectedEvent;
bool isProgrammaticEventTabChange;
bool projectHasUnsavedChanges;
bool newMapDefaultsSet = false;
bool tilesetNeedsRedraw = false;
bool userSetMap(QString, bool scrollTreeView = false);
bool setMap(QString, bool scrollTreeView = false);
void redrawMapScene();
void refreshMapScene();
@ -356,7 +359,7 @@ private:
QStandardItem* createMapItem(QString mapName, int groupNum, int inGroupNum);
void refreshRecentProjectsMenu();
void drawMapListIcons(QAbstractItemModel *model);
void updateMapListIcon(const QString &mapName);
void updateMapList();
void displayMapProperties();
@ -364,6 +367,7 @@ private:
void clickToolButtonFromEditMode(QString editMode);
void markMapEdited();
void markMapEdited(Map*);
void showWindowTitle();
void initWindow();
@ -401,6 +405,7 @@ private:
int insertTilesetLabel(QStringList * list, QString label);
void checkForUpdates(bool requestedByUser);
void setDivingMapsVisible(bool visible);
};
enum MapListUserRoles {
@ -409,4 +414,23 @@ enum MapListUserRoles {
TypeRole2, // Used for various extra data needed.
};
// These are namespaced in a struct to avoid colliding with e.g. class Map.
struct MainTab {
enum {
Map,
Events,
Header,
Connections,
WildPokemon,
};
};
struct MapViewTab {
enum {
Metatiles,
Collision,
Prefabs,
};
};
#endif // MAINWINDOW_H

View file

@ -257,6 +257,7 @@ signals:
void reloadProject();
void uncheckMonitorFilesAction();
void mapCacheCleared();
void mapLoaded(Map *map);
};
#endif // PROJECT_H

View file

@ -4,44 +4,46 @@
#include "mapconnection.h"
#include <QGraphicsPixmapItem>
#include <QPainter>
#include <QPointer>
class ConnectionPixmapItem : public QObject, public QGraphicsPixmapItem {
Q_OBJECT
public:
ConnectionPixmapItem(QPixmap pixmap, MapConnection* connection, int x, int y, int baseMapWidth, int baseMapHeight): QGraphicsPixmapItem(pixmap) {
this->basePixmap = pixmap;
this->connection = connection;
setFlag(ItemIsMovable);
setFlag(ItemSendsGeometryChanges);
this->initialX = x;
this->initialY = y;
this->initialOffset = connection->offset;
this->baseMapWidth = baseMapWidth;
this->baseMapHeight = baseMapHeight;
}
QPixmap basePixmap;
MapConnection* connection;
int initialX;
int initialY;
int initialOffset;
int baseMapWidth;
int baseMapHeight;
void render(qreal opacity = 1);
int getMinOffset();
int getMaxOffset();
ConnectionPixmapItem(MapConnection* connection, int originX, int originY);
ConnectionPixmapItem(MapConnection* connection, QPoint origin);
const QPointer<MapConnection> connection;
void setOrigin(int x, int y);
void setOrigin(QPoint pos);
void setEditable(bool editable);
bool getEditable();
void updateHighlight(bool selected);
void setSelected(bool selected);
void updatePos();
void render(bool ignoreCache = false);
private:
QPixmap basePixmap;
qreal originX;
qreal originY;
bool selected = false;
unsigned actionId = 0;
static const int mWidth = 16;
static const int mHeight = 16;
protected:
QVariant itemChange(GraphicsItemChange change, const QVariant &value);
void mousePressEvent(QGraphicsSceneMouseEvent*);
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*);
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
void mousePressEvent(QGraphicsSceneMouseEvent*) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override;
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override;
signals:
void connectionItemSelected(ConnectionPixmapItem* connectionItem);
void connectionItemDoubleClicked(ConnectionPixmapItem* connectionItem);
void connectionMoved(MapConnection*);
void connectionItemDoubleClicked(MapConnection*);
void selectionChanged(bool selected);
};
#endif // CONNECTIONPIXMAPITEM_H

View file

@ -0,0 +1,52 @@
#ifndef CONNECTIONSLISTITEM_H
#define CONNECTIONSLISTITEM_H
#include "mapconnection.h"
#include "map.h"
#include <QFrame>
#include <QMouseEvent>
#include <QPointer>
namespace Ui {
class ConnectionsListItem;
}
// We show the data for each map connection in the panel on the right side of the Connections tab.
// An instance of this class is used for each item in that list.
// It communicates with the ConnectionPixmapItem on the map through a shared MapConnection pointer.
class ConnectionsListItem : public QFrame
{
Q_OBJECT
public:
explicit ConnectionsListItem(QWidget *parent, MapConnection *connection, const QStringList &mapNames);
~ConnectionsListItem();
void updateUI();
void setSelected(bool selected);
private:
Ui::ConnectionsListItem *ui;
QPointer<MapConnection> connection;
Map *map;
bool isSelected = false;
unsigned actionId = 0;
protected:
void mousePressEvent(QMouseEvent*) override;
signals:
void selected();
void removed(MapConnection*);
void openMapClicked(MapConnection*);
private slots:
void on_comboBox_Direction_currentTextChanged(QString direction);
void on_comboBox_Map_currentTextChanged(QString mapName);
void on_spinBox_Offset_valueChanged(int offset);
void on_button_Delete_clicked();
void on_button_OpenMap_clicked();
};
#endif // CONNECTIONSLISTITEM_H

View file

@ -0,0 +1,30 @@
#ifndef DIVINGMAPPIXMAPITEM_H
#define DIVINGMAPPIXMAPITEM_H
#include "mapconnection.h"
#include <QGraphicsPixmapItem>
#include <QPointer>
#include <QComboBox>
class DivingMapPixmapItem : public QObject, public QGraphicsPixmapItem {
Q_OBJECT
public:
DivingMapPixmapItem(MapConnection *connection, QComboBox *combo);
~DivingMapPixmapItem();
MapConnection* connection() const { return m_connection; }
void updatePixmap();
private:
QPointer<MapConnection> m_connection;
QPointer<QComboBox> m_combo;
void setComboText(const QString &text);
static QPixmap getBasePixmap(MapConnection* connection);
private slots:
void onTargetMapChanged();
};
#endif // DIVINGMAPPIXMAPITEM_H

View file

@ -50,9 +50,10 @@ private:
ImageExporterMode mode = ImageExporterMode::Normal;
void updatePreview();
void updateShowBorderState();
void saveImage();
QPixmap getStitchedImage(QProgressDialog *progress, bool includeBorder);
QPixmap getFormattedMapPixmap(Map *map, bool ignoreBorder);
QPixmap getFormattedMapPixmap(Map *map, bool ignoreBorder = false);
bool historyItemAppliesToFrame(const QUndoCommand *command);
private slots:

View file

@ -0,0 +1,32 @@
#ifndef NEWMAPCONNECTIONDIALOG_H
#define NEWMAPCONNECTIONDIALOG_H
#include <QDialog>
#include "map.h"
#include "mapconnection.h"
namespace Ui {
class NewMapConnectionDialog;
}
class NewMapConnectionDialog : public QDialog
{
Q_OBJECT
public:
explicit NewMapConnectionDialog(QWidget *parent, Map* map, const QStringList &mapNames);
~NewMapConnectionDialog();
virtual void accept() override;
signals:
void accepted(MapConnection *result);
private:
Ui::NewMapConnectionDialog *ui;
bool mapNameIsValid();
void setWarningVisible(bool visible);
};
#endif // NEWMAPCONNECTIONDIALOG_H

View file

@ -13,11 +13,15 @@ public:
void setTextItem(const QString &text);
void setNumberItem(int value);
void setHexItem(uint32_t value);
void setClearButtonEnabled(bool enabled);
void setEditable(bool editable);
void setLineEdit(QLineEdit *edit);
void setFocusedScrollingEnabled(bool enabled);
private:
void setItem(int index, const QString &text);
bool focusedScrollingEnabled = true;
};
#endif // NOSCROLLCOMBOBOX_H

View file

@ -24,6 +24,7 @@ SOURCES += src/core/block.cpp \
src/core/heallocation.cpp \
src/core/imageexport.cpp \
src/core/map.cpp \
src/core/mapconnection.cpp \
src/core/maplayout.cpp \
src/core/mapparser.cpp \
src/core/metatile.cpp \
@ -46,13 +47,16 @@ SOURCES += src/core/block.cpp \
src/scriptapi/apiutility.cpp \
src/scriptapi/scripting.cpp \
src/ui/aboutporymap.cpp \
src/ui/connectionslistitem.cpp \
src/ui/customscriptseditor.cpp \
src/ui/customscriptslistitem.cpp \
src/ui/divingmappixmapitem.cpp \
src/ui/draggablepixmapitem.cpp \
src/ui/bordermetatilespixmapitem.cpp \
src/ui/collisionpixmapitem.cpp \
src/ui/connectionpixmapitem.cpp \
src/ui/currentselectedmetatilespixmapitem.cpp \
src/ui/newmapconnectiondialog.cpp \
src/ui/overlay.cpp \
src/ui/prefab.cpp \
src/ui/projectsettingseditor.cpp \
@ -140,13 +144,16 @@ HEADERS += include/core/block.h \
include/lib/orderedmap.h \
include/lib/orderedjson.h \
include/ui/aboutporymap.h \
include/ui/connectionslistitem.h \
include/ui/customscriptseditor.h \
include/ui/customscriptslistitem.h \
include/ui/divingmappixmapitem.h \
include/ui/draggablepixmapitem.h \
include/ui/bordermetatilespixmapitem.h \
include/ui/collisionpixmapitem.h \
include/ui/connectionpixmapitem.h \
include/ui/currentselectedmetatilespixmapitem.h \
include/ui/newmapconnectiondialog.h \
include/ui/prefabframe.h \
include/ui/projectsettingseditor.h \
include/ui/regionmaplayoutpixmapitem.h \
@ -207,6 +214,8 @@ HEADERS += include/core/block.h \
include/ui/wildmonchart.h
FORMS += forms/mainwindow.ui \
forms/connectionslistitem.ui \
forms/newmapconnectiondialog.ui \
forms/prefabcreationdialog.ui \
forms/prefabframe.ui \
forms/tileseteditor.ui \

BIN
resources/icons/map_go.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -66,5 +66,6 @@
<file>images/Entities_16x16.png</file>
<file>images/pokemon_icon_placeholder.png</file>
<file>icons/clipboard.ico</file>
<file>icons/map_go.ico</file>
</qresource>
</RCC>

View file

@ -330,6 +330,16 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) {
this->mainSplitterState = bytesFromString(value);
} else if (key == "metatiles_splitter_state") {
this->metatilesSplitterState = bytesFromString(value);
} else if (key == "mirror_connecting_maps") {
this->mirrorConnectingMaps = getConfigBool(key, value);
} else if (key == "show_dive_emerge_maps") {
this->showDiveEmergeMaps = getConfigBool(key, value);
} else if (key == "dive_emerge_map_opacity") {
this->diveEmergeMapOpacity = getConfigInteger(key, value, 10, 90, 30);
} else if (key == "dive_map_opacity") {
this->diveMapOpacity = getConfigInteger(key, value, 10, 90, 15);
} else if (key == "emerge_map_opacity") {
this->emergeMapOpacity = getConfigInteger(key, value, 10, 90, 15);
} else if (key == "collision_opacity") {
this->collisionOpacity = getConfigInteger(key, value, 0, 100, 50);
} else if (key == "tileset_editor_geometry") {
@ -444,6 +454,11 @@ 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("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));
map.insert("dive_map_opacity", QString::number(this->diveMapOpacity));
map.insert("emerge_map_opacity", QString::number(this->emergeMapOpacity));
map.insert("collision_opacity", QString::number(this->collisionOpacity));
map.insert("collision_zoom", QString::number(this->collisionZoom));
map.insert("metatiles_zoom", QString::number(this->metatilesZoom));

View file

@ -569,3 +569,205 @@ void ScriptEditMap::undo() {
QUndoCommand::undo();
}
/******************************************************************************
************************************************************************
******************************************************************************/
MapConnectionMove::MapConnectionMove(MapConnection *connection, int newOffset, unsigned actionId,
QUndoCommand *parent) : QUndoCommand(parent) {
setText("Move Map Connection");
this->connection = connection;
this->oldOffset = connection->offset();
this->newOffset = newOffset;
this->mirrored = porymapConfig.mirrorConnectingMaps;
this->actionId = actionId;
}
void MapConnectionMove::redo() {
QUndoCommand::redo();
if (this->connection)
this->connection->setOffset(this->newOffset, this->mirrored);
}
void MapConnectionMove::undo() {
if (this->connection)
this->connection->setOffset(this->oldOffset, this->mirrored);
QUndoCommand::undo();
}
bool MapConnectionMove::mergeWith(const QUndoCommand *command) {
if (this->id() != command->id())
return false;
const MapConnectionMove *other = static_cast<const MapConnectionMove *>(command);
if (this->connection != other->connection)
return false;
if (this->actionId != other->actionId)
return false;
this->newOffset = other->newOffset;
return true;
}
/******************************************************************************
************************************************************************
******************************************************************************/
MapConnectionChangeDirection::MapConnectionChangeDirection(MapConnection *connection, QString newDirection,
QUndoCommand *parent) : QUndoCommand(parent) {
setText("Change Map Connection Direction");
this->connection = connection;
this->oldDirection = connection->direction();
this->newDirection = newDirection;
this->oldOffset = connection->offset();
// If the direction changes between vertical/horizontal then the old offset may not make sense, so we reset it.
if (MapConnection::isHorizontal(this->oldDirection) != MapConnection::isHorizontal(this->newDirection)
|| MapConnection::isVertical(this->oldDirection) != MapConnection::isVertical(this->newDirection)) {
this->newOffset = 0;
} else {
this->newOffset = oldOffset;
}
this->mirrored = porymapConfig.mirrorConnectingMaps;
}
void MapConnectionChangeDirection::redo() {
QUndoCommand::redo();
if (this->connection) {
this->connection->setDirection(this->newDirection, this->mirrored);
this->connection->setOffset(this->newOffset, this->mirrored);
}
}
void MapConnectionChangeDirection::undo() {
if (this->connection) {
this->connection->setDirection(this->oldDirection, this->mirrored);
this->connection->setOffset(this->oldOffset, this->mirrored);
}
QUndoCommand::undo();
}
/******************************************************************************
************************************************************************
******************************************************************************/
MapConnectionChangeMap::MapConnectionChangeMap(MapConnection *connection, QString newMapName,
QUndoCommand *parent) : QUndoCommand(parent) {
setText("Change Map Connection Map");
this->connection = connection;
this->oldMapName = connection->targetMapName();
this->newMapName = newMapName;
this->oldOffset = connection->offset();
this->newOffset = 0; // The old offset may not make sense, so we reset it
this->mirrored = porymapConfig.mirrorConnectingMaps;
}
void MapConnectionChangeMap::redo() {
QUndoCommand::redo();
if (this->connection) {
this->connection->setTargetMapName(this->newMapName, this->mirrored);
this->connection->setOffset(this->newOffset, this->mirrored);
}
}
void MapConnectionChangeMap::undo() {
if (this->connection) {
this->connection->setTargetMapName(this->oldMapName, this->mirrored);
this->connection->setOffset(this->oldOffset, this->mirrored);
}
QUndoCommand::undo();
}
/******************************************************************************
************************************************************************
******************************************************************************/
MapConnectionAdd::MapConnectionAdd(Map *map, MapConnection *connection,
QUndoCommand *parent) : QUndoCommand(parent) {
setText("Add Map Connection");
this->map = map;
this->connection = connection;
// Set this now because it's needed to create a mirror below.
// It would otherwise be set by Map::addConnection.
this->connection->setParentMap(this->map, false);
if (porymapConfig.mirrorConnectingMaps) {
this->mirror = this->connection->createMirror();
this->mirrorMap = this->connection->targetMap();
}
}
void MapConnectionAdd::redo() {
QUndoCommand::redo();
this->map->addConnection(this->connection);
if (this->mirrorMap)
this->mirrorMap->addConnection(this->mirror);
}
void MapConnectionAdd::undo() {
if (this->mirrorMap) {
// We can't guarantee that the mirror we created earlier is still our connection's
// mirror because there is no strict source->mirror pairing for map connections
// (a different identical map connection can take its place during any mirrored change).
if (!MapConnection::areMirrored(this->connection, this->mirror))
this->mirror = this->connection->findMirror();
this->mirrorMap->removeConnection(this->mirror);
}
this->map->removeConnection(this->connection);
QUndoCommand::undo();
}
/******************************************************************************
************************************************************************
******************************************************************************/
MapConnectionRemove::MapConnectionRemove(Map *map, MapConnection *connection,
QUndoCommand *parent) : QUndoCommand(parent) {
setText("Remove Map Connection");
this->map = map;
this->connection = connection;
if (porymapConfig.mirrorConnectingMaps) {
this->mirror = this->connection->findMirror();
this->mirrorMap = this->connection->targetMap();
}
}
void MapConnectionRemove::redo() {
QUndoCommand::redo();
if (this->mirrorMap) {
// See comment in MapConnectionAdd::undo
if (!MapConnection::areMirrored(this->connection, this->mirror))
this->mirror = this->connection->findMirror();
this->mirrorMap->removeConnection(this->mirror);
}
this->map->removeConnection(this->connection);
}
void MapConnectionRemove::undo() {
this->map->addConnection(this->connection);
if (this->mirrorMap)
this->mirrorMap->addConnection(this->mirror);
QUndoCommand::undo();
}

View file

@ -17,11 +17,9 @@ Map::Map(QObject *parent) : QObject(parent)
}
Map::~Map() {
// delete all associated events
while (!ownedEvents.isEmpty()) {
Event *last = ownedEvents.takeLast();
if (last) delete last;
}
qDeleteAll(ownedEvents);
ownedEvents.clear();
deleteConnections();
}
void Map::setName(QString mapName) {
@ -217,38 +215,47 @@ QPixmap Map::renderBorder(bool ignoreCache) {
return layout->border_pixmap;
}
QPixmap Map::renderConnection(MapConnection connection, MapLayout * fromLayout) {
int x, y, w, h;
if (connection.direction == "up") {
x = 0;
y = getHeight() - BORDER_DISTANCE;
w = getWidth();
h = BORDER_DISTANCE;
} else if (connection.direction == "down") {
x = 0;
y = 0;
w = getWidth();
h = BORDER_DISTANCE;
} else if (connection.direction == "left") {
x = getWidth() - BORDER_DISTANCE;
y = 0;
w = BORDER_DISTANCE;
h = getHeight();
} else if (connection.direction == "right") {
x = 0;
y = 0;
w = BORDER_DISTANCE;
h = getHeight();
} else {
// this should not happen
x = 0;
y = 0;
w = getWidth();
h = getHeight();
}
// Get the portion of the map that can be rendered when rendered as a map connection.
// Cardinal connections render the nearest segment of their map and within the bounds of the border draw distance,
// Dive/Emerge connections are rendered normally within the bounds of their parent map.
QRect Map::getConnectionRect(const QString &direction, MapLayout * fromLayout) {
int x = 0, y = 0;
int w = getWidth(), h = getHeight();
render(true, fromLayout, QRect(x, y, w, h));
QImage connection_image = image.copy(x * 16, y * 16, w * 16, h * 16);
if (direction == "up") {
h = qMin(h, BORDER_DISTANCE);
y = getHeight() - h;
} else if (direction == "down") {
h = qMin(h, BORDER_DISTANCE);
} else if (direction == "left") {
w = qMin(w, BORDER_DISTANCE);
x = getWidth() - w;
} else if (direction == "right") {
w = qMin(w, BORDER_DISTANCE);
} else if (MapConnection::isDiving(direction)) {
if (fromLayout) {
w = qMin(w, fromLayout->getWidth());
h = qMin(h, fromLayout->getHeight());
}
} else {
// Unknown direction
return QRect();
}
return QRect(x, y, w, h);
}
QPixmap Map::renderConnection(const QString &direction, MapLayout * fromLayout) {
QRect bounds = getConnectionRect(direction, fromLayout);
if (!bounds.isValid())
return QPixmap();
// 'fromLayout' will be used in 'render' to get the palettes from the parent map.
// Dive/Emerge connections render normally with their own palettes, so we ignore this.
if (MapConnection::isDiving(direction))
fromLayout = nullptr;
render(true, fromLayout, bounds);
QImage connection_image = image.copy(bounds.x() * 16, bounds.y() * 16, bounds.width() * 16, bounds.height() * 16);
return QPixmap::fromImage(connection_image);
}
@ -304,8 +311,8 @@ void Map::setDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool
Scripting::cb_MapResized(oldWidth, oldHeight, newWidth, newHeight);
}
emit mapChanged(this);
emit mapDimensionsChanged(QSize(getWidth(), getHeight()));
modify();
}
void Map::setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) {
@ -322,7 +329,7 @@ void Map::setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata,
Scripting::cb_BorderResized(oldWidth, oldHeight, newWidth, newHeight);
}
emit mapChanged(this);
modify();
}
void Map::openScript(QString label) {
@ -523,6 +530,72 @@ void Map::addEvent(Event *event) {
if (!ownedEvents.contains(event)) ownedEvents.append(event);
}
void Map::deleteConnections() {
qDeleteAll(this->ownedConnections);
this->ownedConnections.clear();
this->connections.clear();
}
QList<MapConnection*> Map::getConnections() const {
return this->connections;
}
void Map::addConnection(MapConnection *connection) {
if (!connection || this->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) {
if (i->direction() == connection->direction()) {
trackConnection(connection);
return;
}
}
}
loadConnection(connection);
modify();
emit connectionAdded(connection);
return;
}
void Map::loadConnection(MapConnection *connection) {
if (!connection)
return;
if (!this->connections.contains(connection))
this->connections.append(connection);
trackConnection(connection);
}
void Map::trackConnection(MapConnection *connection) {
connection->setParentMap(this, false);
if (!this->ownedConnections.contains(connection)) {
this->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);
QObject::disconnect(connection, &MapConnection::parentMapChanged, this, nullptr);
}
});
}
}
// 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))
return;
connection->setParentMap(nullptr, false);
modify();
emit connectionRemoved(connection);
}
void Map::modify() {
emit modified();
}
@ -535,6 +608,27 @@ bool Map::hasUnsavedChanges() {
return !editHistory.isClean() || hasUnsavedDataChanges || !isPersistedToFile;
}
void Map::pruneEditHistory() {
// Edit history for map connections gets messy because edits on other maps can affect the current map.
// To avoid complications we clear MapConnection edit history when the user opens a different map.
// No other edits within a single map depend on MapConnections so they can be pruned safely.
static const QSet<int> mapConnectionIds = {
ID_MapConnectionMove,
ID_MapConnectionChangeDirection,
ID_MapConnectionChangeMap,
ID_MapConnectionAdd,
ID_MapConnectionRemove
};
for (int i = 0; i < this->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));
if (mapConnectionIds.contains(command->id()))
command->setObsolete(true);
}
}
bool Map::isWithinBounds(int x, int y) {
return (x >= 0 && x < this->getWidth() && y >= 0 && y < this->getHeight());
}

163
src/core/mapconnection.cpp Normal file
View file

@ -0,0 +1,163 @@
#include "mapconnection.h"
#include "project.h"
QPointer<Project> MapConnection::project = nullptr;
const QMap<QString, QString> MapConnection::oppositeDirections = {
{"up", "down"}, {"down", "up"},
{"right", "left"}, {"left", "right"},
{"dive", "emerge"}, {"emerge", "dive"}
};
MapConnection::MapConnection(const QString &targetMapName, const QString &direction, int offset) {
m_parentMap = nullptr;
m_targetMapName = targetMapName;
m_direction = direction;
m_offset = offset;
}
bool MapConnection::areMirrored(const MapConnection* a, const MapConnection* b) {
if (!a || !b || !a->m_parentMap || !b->m_parentMap)
return false;
return a->parentMapName() == b->m_targetMapName
&& a->m_targetMapName == b->parentMapName()
&& a->m_offset == -b->m_offset
&& a->m_direction == oppositeDirection(b->m_direction);
}
MapConnection* MapConnection::findMirror() {
auto map = targetMap();
if (!map)
return nullptr;
// Find the matching connection in the connected map.
// Note: There is no strict source -> mirror pairing, i.e. we are not guaranteed
// to always get the same MapConnection if there are multiple identical copies.
for (auto connection : map->getConnections()) {
if (this != connection && areMirrored(this, connection))
return connection;
}
return nullptr;
}
MapConnection * MapConnection::createMirror() {
auto mirror = new MapConnection(parentMapName(), oppositeDirection(m_direction), -m_offset);
mirror->setParentMap(targetMap(), false);
return mirror;
}
void MapConnection::markMapEdited() {
if (m_parentMap)
m_parentMap->modify();
}
Map* MapConnection::getMap(const QString& mapName) const {
return project ? project->getMap(mapName) : nullptr;
}
Map* MapConnection::targetMap() const {
return getMap(m_targetMapName);
}
QPixmap MapConnection::getPixmap() {
auto map = targetMap();
if (!map)
return QPixmap();
return map->renderConnection(m_direction, m_parentMap ? m_parentMap->layout : nullptr);
}
void MapConnection::setParentMap(Map* map, bool mirror) {
if (map == m_parentMap)
return;
if (mirror) {
auto connection = findMirror();
if (connection)
connection->setTargetMapName(map ? map->name : QString(), false);
}
if (m_parentMap)
m_parentMap->removeConnection(this);
auto before = m_parentMap;
m_parentMap = map;
if (m_parentMap)
m_parentMap->addConnection(this);
emit parentMapChanged(before, m_parentMap);
}
QString MapConnection::parentMapName() const {
return m_parentMap ? m_parentMap->name : QString();
}
void MapConnection::setTargetMapName(const QString &targetMapName, bool mirror) {
if (targetMapName == m_targetMapName)
return;
if (mirror) {
auto connection = findMirror();
if (connection)
connection->setParentMap(getMap(targetMapName), false);
}
auto before = m_targetMapName;
m_targetMapName = targetMapName;
emit targetMapNameChanged(before, m_targetMapName);
markMapEdited();
}
void MapConnection::setDirection(const QString &direction, bool mirror) {
if (direction == m_direction)
return;
if (mirror) {
auto connection = findMirror();
if (connection)
connection->setDirection(oppositeDirection(direction), false);
}
auto before = m_direction;
m_direction = direction;
emit directionChanged(before, m_direction);
markMapEdited();
}
void MapConnection::setOffset(int offset, bool mirror) {
if (offset == m_offset)
return;
if (mirror) {
auto connection = findMirror();
if (connection)
connection->setOffset(-offset, false);
}
auto before = m_offset;
m_offset = offset;
emit offsetChanged(before, m_offset);
markMapEdited();
}
const QStringList MapConnection::cardinalDirections = {
"up", "down", "left", "right"
};
bool MapConnection::isCardinal(const QString &direction) {
return cardinalDirections.contains(direction);
}
bool MapConnection::isHorizontal(const QString &direction) {
return direction == "left" || direction == "right";
}
bool MapConnection::isVertical(const QString &direction) {
return direction == "up" || direction == "down";
}
bool MapConnection::isDiving(const QString &direction) {
return direction == "dive" || direction == "emerge";
}

View file

@ -2,7 +2,7 @@
#include "draggablepixmapitem.h"
#include "imageproviders.h"
#include "log.h"
#include "mapconnection.h"
#include "connectionslistitem.h"
#include "currentselectedmetatilespixmapitem.h"
#include "mapsceneeventfilter.h"
#include "metatile.h"
@ -78,6 +78,14 @@ void Editor::saveUiFields() {
saveEncounterTabData();
}
void Editor::setProject(Project * project) {
if (this->project) {
closeProject();
}
this->project = project;
MapConnection::project = project;
}
void Editor::closeProject() {
if (!this->project)
return;
@ -92,7 +100,6 @@ void Editor::setEditingMap() {
current_view = map_item;
if (map_item) {
map_item->paintingMode = MapPixmapItem::PaintMode::Metatiles;
displayMapConnections();
map_item->draw();
map_item->setVisible(true);
}
@ -102,9 +109,7 @@ void Editor::setEditingMap() {
if (events_group) {
events_group->setVisible(false);
}
setBorderItemsVisible(ui->checkBox_ToggleBorder->isChecked());
setConnectionItemsVisible(ui->checkBox_ToggleBorder->isChecked());
setConnectionsEditable(false);
updateBorderVisibility();
this->cursorMapTileRect->stopSingleTileMode();
this->cursorMapTileRect->setActive(true);
@ -114,7 +119,6 @@ void Editor::setEditingMap() {
void Editor::setEditingCollision() {
current_view = collision_item;
if (collision_item) {
displayMapConnections();
collision_item->draw();
collision_item->setVisible(true);
}
@ -126,9 +130,7 @@ void Editor::setEditingCollision() {
if (events_group) {
events_group->setVisible(false);
}
setBorderItemsVisible(ui->checkBox_ToggleBorder->isChecked());
setConnectionItemsVisible(ui->checkBox_ToggleBorder->isChecked());
setConnectionsEditable(false);
updateBorderVisibility();
this->cursorMapTileRect->setSingleTileMode();
this->cursorMapTileRect->setActive(true);
@ -142,16 +144,13 @@ void Editor::setEditingObjects() {
}
if (map_item) {
map_item->paintingMode = MapPixmapItem::PaintMode::EventObjects;
displayMapConnections();
map_item->draw();
map_item->setVisible(true);
}
if (collision_item) {
collision_item->setVisible(false);
}
setBorderItemsVisible(ui->checkBox_ToggleBorder->isChecked());
setConnectionItemsVisible(ui->checkBox_ToggleBorder->isChecked());
setConnectionsEditable(false);
updateBorderVisibility();
this->cursorMapTileRect->setSingleTileMode();
this->cursorMapTileRect->setActive(false);
updateWarpEventWarnings();
@ -181,17 +180,6 @@ void Editor::setEditingConnections() {
map_item->paintingMode = MapPixmapItem::PaintMode::Disabled;
map_item->draw();
map_item->setVisible(true);
populateConnectionMapPickers();
ui->label_NumConnections->setText(QString::number(map->connections.length()));
setDiveEmergeControls();
bool controlsEnabled = selected_connection_item != nullptr;
setConnectionEditControlsEnabled(controlsEnabled);
if (selected_connection_item) {
onConnectionOffsetChanged(selected_connection_item->connection->offset);
setConnectionMap(selected_connection_item->connection->map_name);
setCurrentConnectionDirection(selected_connection_item->connection->direction);
}
maskNonVisibleConnectionTiles();
}
if (collision_item) {
collision_item->setVisible(false);
@ -199,9 +187,7 @@ void Editor::setEditingConnections() {
if (events_group) {
events_group->setVisible(false);
}
setBorderItemsVisible(true, 0.4);
setConnectionItemsVisible(true);
setConnectionsEditable(true);
updateBorderVisibility();
this->cursorMapTileRect->setSingleTileMode();
this->cursorMapTileRect->setActive(false);
}
@ -753,193 +739,297 @@ void Editor::updateEncounterFields(EncounterFields newFields) {
project->wildMonFields = newFields;
}
void Editor::setDiveEmergeControls() {
ui->comboBox_DiveMap->blockSignals(true);
ui->comboBox_EmergeMap->blockSignals(true);
ui->comboBox_DiveMap->setCurrentText("");
ui->comboBox_EmergeMap->setCurrentText("");
for (MapConnection* connection : map->connections) {
if (connection->direction == "dive") {
ui->comboBox_DiveMap->setCurrentText(connection->map_name);
} else if (connection->direction == "emerge") {
ui->comboBox_EmergeMap->setCurrentText(connection->map_name);
void Editor::disconnectMapConnection(MapConnection *connection) {
// Disconnect MapConnection's signals used by the display.
// It'd be nice if we could just 'connection->disconnect(this)' but that doesn't account for lambda functions.
QObject::disconnect(connection, &MapConnection::targetMapNameChanged, nullptr, nullptr);
QObject::disconnect(connection, &MapConnection::directionChanged, nullptr, nullptr);
QObject::disconnect(connection, &MapConnection::offsetChanged, nullptr, nullptr);
}
void Editor::displayConnection(MapConnection *connection) {
if (!connection)
return;
if (MapConnection::isDiving(connection->direction())) {
displayDivingConnection(connection);
return;
}
// Create connection image
ConnectionPixmapItem *pixmapItem = new ConnectionPixmapItem(connection, getConnectionOrigin(connection));
pixmapItem->render();
scene->addItem(pixmapItem);
maskNonVisibleConnectionTiles();
// Create item for the list panel
ConnectionsListItem *listItem = new ConnectionsListItem(ui->scrollAreaContents_ConnectionsList, pixmapItem->connection, project->mapNames);
ui->layout_ConnectionsList->insertWidget(ui->layout_ConnectionsList->count() - 1, listItem); // Insert above the vertical spacer
// Double clicking the pixmap or clicking the list item's map button opens the connected map
connect(listItem, &ConnectionsListItem::openMapClicked, this, &Editor::openConnectedMap);
connect(pixmapItem, &ConnectionPixmapItem::connectionItemDoubleClicked, this, &Editor::openConnectedMap);
// Sync the selection highlight between the list UI and the pixmap
connect(pixmapItem, &ConnectionPixmapItem::selectionChanged, [=](bool selected) {
listItem->setSelected(selected);
if (selected) setSelectedConnectionItem(pixmapItem);
});
connect(listItem, &ConnectionsListItem::selected, [=] {
setSelectedConnectionItem(pixmapItem);
});
// Sync edits to 'offset' between the list UI and the pixmap
connect(connection, &MapConnection::offsetChanged, [=](int, int) {
listItem->updateUI();
pixmapItem->updatePos();
maskNonVisibleConnectionTiles();
});
// Sync edits to 'direction' between the list UI and the pixmap
connect(connection, &MapConnection::directionChanged, [=](QString, QString) {
listItem->updateUI();
updateConnectionPixmap(pixmapItem);
});
// Sync edits to 'map' between the list UI and the pixmap
connect(connection, &MapConnection::targetMapNameChanged, [=](QString, QString) {
listItem->updateUI();
updateConnectionPixmap(pixmapItem);
});
// When the pixmap is deleted, remove its associated list item
connect(pixmapItem, &ConnectionPixmapItem::destroyed, listItem, &ConnectionsListItem::deleteLater);
connection_items.append(pixmapItem);
// If this was a recent addition from the user we should select it.
// We intentionally exclude connections added programmatically, e.g. by mirroring.
if (connection_to_select == connection) {
connection_to_select = nullptr;
setSelectedConnectionItem(pixmapItem);
}
}
void Editor::addConnection(MapConnection *connection) {
if (!connection)
return;
// Mark this connection to be selected once its display elements have been created.
// 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));
}
void Editor::removeConnection(MapConnection *connection) {
if (!connection)
return;
this->map->editHistory.push(new MapConnectionRemove(this->map, connection));
}
void Editor::removeSelectedConnection() {
if (selected_connection_item)
removeConnection(selected_connection_item->connection);
}
void Editor::removeConnectionPixmap(MapConnection *connection) {
if (!connection)
return;
disconnectMapConnection(connection);
if (MapConnection::isDiving(connection->direction())) {
removeDivingMapPixmap(connection);
return;
}
int i;
for (i = 0; i < connection_items.length(); i++) {
if (connection_items.at(i)->connection == connection)
break;
}
if (i == connection_items.length())
return; // Connection is not displayed, nothing to do.
auto pixmapItem = connection_items.takeAt(i);
if (pixmapItem == selected_connection_item) {
// This was the selected connection, select the next one up in the list.
selected_connection_item = nullptr;
if (i != 0) i--;
if (connection_items.length() > i)
setSelectedConnectionItem(connection_items.at(i));
}
if (pixmapItem->scene())
pixmapItem->scene()->removeItem(pixmapItem);
delete pixmapItem;
}
void Editor::displayDivingConnection(MapConnection *connection) {
if (!connection)
return;
const QString direction = connection->direction();
if (!MapConnection::isDiving(direction))
return;
// Note: We only support editing 1 Dive and Emerge connection per map.
// In a vanilla game only the first Dive/Emerge connection is considered, so allowing
// users to have multiple is likely to lead to confusion. In case users have changed
// this we won't delete extra diving connections, but we'll only display the first one.
if (diving_map_items.value(direction))
return;
// Create map display
auto comboBox = (direction == "dive") ? ui->comboBox_DiveMap : ui->comboBox_EmergeMap;
auto item = new DivingMapPixmapItem(connection, comboBox);
scene->addItem(item);
diving_map_items.insert(direction, item);
updateDivingMapsVisibility();
}
void Editor::renderDivingConnections() {
for (auto item : diving_map_items.values())
item->updatePixmap();
}
void Editor::removeDivingMapPixmap(MapConnection *connection) {
if (!connection)
return;
const QString direction = connection->direction();
if (!diving_map_items.contains(direction))
return;
// If the diving map being removed is different than the one that's currently displayed we don't need to do anything.
if (diving_map_items.value(direction)->connection() != connection)
return;
// Delete map image
auto pixmapItem = diving_map_items.take(direction);
if (pixmapItem->scene())
pixmapItem->scene()->removeItem(pixmapItem);
delete pixmapItem;
// Reveal any previously-hidden connection (because we only ever display one diving map of each type).
// Note: When this occurs as a result of the user clicking the 'X' clear button it seems the QComboBox
// doesn't expect the line edit to be immediately repopulated, and the 'X' doesn't reappear.
// As a workaround we wait before displaying the new text. The wait time is essentially arbitrary.
for (auto i : map->getConnections()) {
if (i->direction() == direction) {
QTimer::singleShot(10, Qt::CoarseTimer, [this, i]() { displayDivingConnection(i); });
break;
}
}
ui->comboBox_DiveMap->blockSignals(false);
ui->comboBox_EmergeMap->blockSignals(false);
updateDivingMapsVisibility();
}
void Editor::populateConnectionMapPickers() {
ui->comboBox_ConnectedMap->blockSignals(true);
ui->comboBox_DiveMap->blockSignals(true);
ui->comboBox_EmergeMap->blockSignals(true);
ui->comboBox_ConnectedMap->clear();
ui->comboBox_ConnectedMap->addItems(project->mapNames);
ui->comboBox_DiveMap->clear();
ui->comboBox_DiveMap->addItems(project->mapNames);
ui->comboBox_EmergeMap->clear();
ui->comboBox_EmergeMap->addItems(project->mapNames);
ui->comboBox_ConnectedMap->blockSignals(false);
ui->comboBox_DiveMap->blockSignals(true);
ui->comboBox_EmergeMap->blockSignals(true);
void Editor::updateDiveMap(QString mapName) {
setDivingMapName(mapName, "dive");
}
void Editor::setConnectionItemsVisible(bool visible) {
for (ConnectionPixmapItem* item : connection_items) {
item->setVisible(visible);
item->setEnabled(visible);
void Editor::updateEmergeMap(QString mapName) {
setDivingMapName(mapName, "emerge");
}
void Editor::setDivingMapName(QString mapName, QString direction) {
auto pixmapItem = diving_map_items.value(direction);
MapConnection *connection = pixmapItem ? pixmapItem->connection() : nullptr;
if (connection) {
if (mapName == connection->targetMapName())
return; // No change
// Update existing connection
if (mapName.isEmpty()) {
removeConnection(connection);
} else {
map->editHistory.push(new MapConnectionChangeMap(connection, mapName));
}
} else if (!mapName.isEmpty()) {
// Create new connection
addConnection(new MapConnection(mapName, direction));
}
}
void Editor::setBorderItemsVisible(bool visible, qreal opacity) {
for (QGraphicsPixmapItem* item : borderItems) {
item->setVisible(visible);
item->setOpacity(opacity);
void Editor::updateDivingMapsVisibility() {
auto dive = diving_map_items.value("dive");
auto emerge = diving_map_items.value("emerge");
if (dive && emerge) {
// Both connections in use, use separate sliders
ui->stackedWidget_DiveMapOpacity->setCurrentIndex(0);
dive->setOpacity(!porymapConfig.showDiveEmergeMaps ? 0 : static_cast<qreal>(porymapConfig.diveMapOpacity) / 100);
emerge->setOpacity(!porymapConfig.showDiveEmergeMaps ? 0 : static_cast<qreal>(porymapConfig.emergeMapOpacity) / 100);
} else {
// One connection in use (or none), use single slider
ui->stackedWidget_DiveMapOpacity->setCurrentIndex(1);
qreal opacity = !porymapConfig.showDiveEmergeMaps ? 0 : static_cast<qreal>(porymapConfig.diveEmergeMapOpacity) / 100;
if (dive) dive->setOpacity(opacity);
else if (emerge) emerge->setOpacity(opacity);
}
}
void Editor::setCurrentConnectionDirection(QString curDirection) {
if (!selected_connection_item)
return;
Map *connected_map = project->getMap(selected_connection_item->connection->map_name);
if (!connected_map) {
return;
}
// Get the 'origin' point for the connection's pixmap, i.e. where it should be positioned in the editor when connection->offset() == 0.
// This differs depending on the connection's direction and the dimensions of its target map or parent map.
QPoint Editor::getConnectionOrigin(MapConnection *connection) {
if (!connection)
return QPoint(0, 0);
selected_connection_item->connection->direction = curDirection;
QPixmap pixmap = connected_map->renderConnection(*selected_connection_item->connection, map->layout);
int offset = selected_connection_item->connection->offset;
selected_connection_item->initialOffset = offset;
Map *parentMap = connection->parentMap();
Map *targetMap = connection->targetMap();
const QString direction = connection->direction();
int x = 0, y = 0;
if (selected_connection_item->connection->direction == "up") {
x = offset * 16;
y = -pixmap.height();
} else if (selected_connection_item->connection->direction == "down") {
x = offset * 16;
y = map->getHeight() * 16;
} else if (selected_connection_item->connection->direction == "left") {
x = -pixmap.width();
y = offset * 16;
} else if (selected_connection_item->connection->direction == "right") {
x = map->getWidth() * 16;
y = offset * 16;
if (direction == "right") {
if (parentMap) x = parentMap->getWidth();
} else if (direction == "down") {
if (parentMap) y = parentMap->getHeight();
} else if (direction == "left") {
if (targetMap) x = -targetMap->getConnectionRect(direction).width();
} else if (direction == "up") {
if (targetMap) y = -targetMap->getConnectionRect(direction).height();
}
selected_connection_item->basePixmap = pixmap;
QPainter painter(&pixmap);
painter.setPen(QColor(255, 0, 255));
painter.drawRect(0, 0, pixmap.width() - 1, pixmap.height() - 1);
painter.end();
selected_connection_item->setPixmap(pixmap);
selected_connection_item->initialX = x;
selected_connection_item->initialY = y;
selected_connection_item->blockSignals(true);
selected_connection_item->setX(x);
selected_connection_item->setY(y);
selected_connection_item->setZValue(-1);
selected_connection_item->blockSignals(false);
setConnectionEditControlValues(selected_connection_item->connection);
return QPoint(x * 16, y * 16);
}
void Editor::updateCurrentConnectionDirection(QString curDirection) {
if (!selected_connection_item)
void Editor::updateConnectionPixmap(ConnectionPixmapItem *pixmapItem) {
if (!pixmapItem)
return;
QString originalDirection = selected_connection_item->connection->direction;
setCurrentConnectionDirection(curDirection);
updateMirroredConnectionDirection(selected_connection_item->connection, originalDirection);
pixmapItem->setOrigin(getConnectionOrigin(pixmapItem->connection));
pixmapItem->render(true); // Full render to reflect map changes
maskNonVisibleConnectionTiles();
}
void Editor::onConnectionMoved(MapConnection* connection) {
updateMirroredConnectionOffset(connection);
onConnectionOffsetChanged(connection->offset);
maskNonVisibleConnectionTiles();
}
void Editor::onConnectionOffsetChanged(int newOffset) {
ui->spinBox_ConnectionOffset->blockSignals(true);
ui->spinBox_ConnectionOffset->setValue(newOffset);
ui->spinBox_ConnectionOffset->blockSignals(false);
}
void Editor::setConnectionEditControlValues(MapConnection* connection) {
QString mapName = connection ? connection->map_name : "";
QString direction = connection ? connection->direction : "";
int offset = connection ? connection->offset : 0;
ui->comboBox_ConnectedMap->blockSignals(true);
ui->comboBox_ConnectionDirection->blockSignals(true);
ui->spinBox_ConnectionOffset->blockSignals(true);
ui->comboBox_ConnectedMap->setCurrentText(mapName);
ui->comboBox_ConnectionDirection->setCurrentText(direction);
ui->spinBox_ConnectionOffset->setValue(offset);
ui->comboBox_ConnectedMap->blockSignals(false);
ui->comboBox_ConnectionDirection->blockSignals(false);
ui->spinBox_ConnectionOffset->blockSignals(false);
}
void Editor::setConnectionEditControlsEnabled(bool enabled) {
ui->comboBox_ConnectionDirection->setEnabled(enabled);
ui->comboBox_ConnectedMap->setEnabled(enabled);
ui->spinBox_ConnectionOffset->setEnabled(enabled);
if (!enabled) {
setConnectionEditControlValues(nullptr);
}
}
void Editor::setConnectionsEditable(bool editable) {
for (ConnectionPixmapItem* item : connection_items) {
item->setEditable(editable);
item->updateHighlight(item == selected_connection_item);
}
}
void Editor::onConnectionItemSelected(ConnectionPixmapItem* connectionItem) {
if (!connectionItem)
void Editor::setSelectedConnectionItem(ConnectionPixmapItem *pixmapItem) {
if (!pixmapItem || pixmapItem == selected_connection_item)
return;
selected_connection_item = connectionItem;
for (ConnectionPixmapItem* item : connection_items)
item->updateHighlight(item == selected_connection_item);
setConnectionEditControlsEnabled(true);
setConnectionEditControlValues(selected_connection_item->connection);
ui->spinBox_ConnectionOffset->setMaximum(selected_connection_item->getMaxOffset());
ui->spinBox_ConnectionOffset->setMinimum(selected_connection_item->getMinOffset());
onConnectionOffsetChanged(selected_connection_item->connection->offset);
if (selected_connection_item) selected_connection_item->setSelected(false);
selected_connection_item = pixmapItem;
selected_connection_item->setSelected(true);
}
void Editor::setSelectedConnectionFromMap(QString mapName) {
// Search for the first connection that connects to the given map map.
for (ConnectionPixmapItem* item : connection_items) {
if (item->connection->map_name == mapName) {
onConnectionItemSelected(item);
void Editor::setSelectedConnection(MapConnection *connection) {
if (!connection)
return;
for (auto item : connection_items) {
if (item->connection == connection) {
setSelectedConnectionItem(item);
break;
}
}
}
void Editor::onConnectionItemDoubleClicked(ConnectionPixmapItem* connectionItem) {
emit loadMapRequested(connectionItem->connection->map_name, map->name);
}
void Editor::onConnectionDirectionChanged(QString newDirection) {
ui->comboBox_ConnectionDirection->blockSignals(true);
ui->comboBox_ConnectionDirection->setCurrentText(newDirection);
ui->comboBox_ConnectionDirection->blockSignals(false);
}
void Editor::onBorderMetatilesChanged() {
displayMapBorder();
setBorderItemsVisible(ui->checkBox_ToggleBorder->isChecked());
updateBorderVisibility();
}
void Editor::onHoveredMovementPermissionChanged(uint16_t collision, uint16_t elevation) {
@ -1119,7 +1209,10 @@ bool Editor::setMap(QString map_name) {
// 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())
disconnectMapConnection(connection);
}
if (project) {
@ -1139,6 +1232,8 @@ bool Editor::setMap(QString map_name) {
map_ruler->setMapDimensions(QSize(map->getWidth(), map->getHeight()));
connect(map, &Map::mapDimensionsChanged, map_ruler, &MapRuler::setMapDimensions);
connect(map, &Map::openScriptRequested, this, &Editor::openScript);
connect(map, &Map::connectionAdded, this, &Editor::displayConnection);
connect(map, &Map::connectionRemoved, this, &Editor::removeConnectionPixmap);
updateSelectedEvents();
}
@ -1360,16 +1455,13 @@ void Editor::clearMap() {
clearBorderMetatiles();
clearCurrentMetatilesSelection();
clearMapEvents();
//clearMapConnections();
clearMapConnections();
clearMapBorder();
clearMapGrid();
clearWildMonTables();
clearConnectionMask();
// TODO: Handle connections after redesign PR.
selected_connection_item = nullptr;
connection_items.clear();
connection_mask = nullptr;
// Clear pointers to objects deleted elsewhere
current_view = nullptr;
map = nullptr;
@ -1399,6 +1491,7 @@ bool Editor::displayMap() {
displayMapBorder();
displayMapGrid();
displayWildMonTables();
maskNonVisibleConnectionTiles();
this->map_ruler->setZValue(1000);
scene->addItem(this->map_ruler);
@ -1617,66 +1710,43 @@ DraggablePixmapItem *Editor::addMapEvent(Event *event) {
return object;
}
void Editor::displayMapConnections() {
for (ConnectionPixmapItem* item : connection_items) {
if (item->scene()) {
void Editor::clearMapConnections() {
for (auto item : connection_items) {
if (item->scene())
item->scene()->removeItem(item);
}
delete item;
}
selected_connection_item = nullptr;
connection_items.clear();
for (MapConnection *connection : map->connections) {
if (connection->direction == "dive" || connection->direction == "emerge") {
continue;
}
createConnectionItem(connection);
}
const QSignalBlocker blocker1(ui->comboBox_DiveMap);
const QSignalBlocker blocker2(ui->comboBox_EmergeMap);
ui->comboBox_DiveMap->setCurrentText("");
ui->comboBox_EmergeMap->setCurrentText("");
if (!connection_items.empty()) {
onConnectionItemSelected(connection_items.first());
for (auto item : diving_map_items.values()) {
if (item->scene())
item->scene()->removeItem(item);
delete item;
}
diving_map_items.clear();
maskNonVisibleConnectionTiles();
// Reset to single opacity slider
ui->stackedWidget_DiveMapOpacity->setCurrentIndex(1);
selected_connection_item = nullptr;
}
void Editor::createConnectionItem(MapConnection* connection) {
Map *connected_map = project->getMap(connection->map_name);
if (!connected_map) {
return;
}
void Editor::displayMapConnections() {
clearMapConnections();
QPixmap pixmap = connected_map->renderConnection(*connection, map->layout);
int offset = connection->offset;
int x = 0, y = 0;
if (connection->direction == "up") {
x = offset * 16;
y = -pixmap.height();
} else if (connection->direction == "down") {
x = offset * 16;
y = map->getHeight() * 16;
} else if (connection->direction == "left") {
x = -pixmap.width();
y = offset * 16;
} else if (connection->direction == "right") {
x = map->getWidth() * 16;
y = offset * 16;
}
for (auto connection : map->getConnections())
displayConnection(connection);
ConnectionPixmapItem *item = new ConnectionPixmapItem(pixmap, connection, x, y, map->getWidth(), map->getHeight());
item->setX(x);
item->setY(y);
item->setZValue(-1);
scene->addItem(item);
connect(item, &ConnectionPixmapItem::connectionMoved, this, &Editor::onConnectionMoved);
connect(item, &ConnectionPixmapItem::connectionItemSelected, this, &Editor::onConnectionItemSelected);
connect(item, &ConnectionPixmapItem::connectionItemDoubleClicked, this, &Editor::onConnectionItemDoubleClicked);
connection_items.append(item);
if (!connection_items.isEmpty())
setSelectedConnectionItem(connection_items.first());
}
// Hides connected map tiles that cannot be seen from the current map (beyond BORDER_DISTANCE).
void Editor::maskNonVisibleConnectionTiles() {
void Editor::clearConnectionMask() {
if (connection_mask) {
if (connection_mask->scene()) {
connection_mask->scene()->removeItem(connection_mask);
@ -1684,6 +1754,11 @@ void Editor::maskNonVisibleConnectionTiles() {
delete connection_mask;
connection_mask = nullptr;
}
}
// Hides connected map tiles that cannot be seen from the current map (beyond BORDER_DISTANCE).
void Editor::maskNonVisibleConnectionTiles() {
clearConnectionMask();
QPainterPath mask;
mask.addRect(scene->itemsBoundingRect().toRect());
@ -1725,7 +1800,7 @@ void Editor::displayMapBorder() {
item->setX(x * 16);
item->setY(y * 16);
item->setZValue(-3);
scene->addItem(item); // TODO: If the scene is taking ownership here is a double-free possible?
scene->addItem(item);
borderItems.append(item);
}
}
@ -1738,17 +1813,8 @@ void Editor::updateMapBorder() {
}
void Editor::updateMapConnections() {
for (int i = 0; i < connection_items.size(); i++) {
Map *connected_map = project->getMap(connection_items[i]->connection->map_name);
if (!connected_map)
continue;
QPixmap pixmap = connected_map->renderConnection(*(connection_items[i]->connection), map->layout);
connection_items[i]->basePixmap = pixmap;
connection_items[i]->setPixmap(pixmap);
}
maskNonVisibleConnectionTiles();
for (auto item : connection_items)
item->render(true);
}
int Editor::getBorderDrawDistance(int dimension) {
@ -1798,214 +1864,6 @@ void Editor::displayMapGrid() {
connect(ui->checkBox_ToggleGrid, &QCheckBox::toggled, this, &Editor::onToggleGridClicked);
}
void Editor::updateConnectionOffset(int offset) {
if (!selected_connection_item)
return;
selected_connection_item->blockSignals(true);
offset = qMin(offset, selected_connection_item->getMaxOffset());
offset = qMax(offset, selected_connection_item->getMinOffset());
selected_connection_item->connection->offset = offset;
if (selected_connection_item->connection->direction == "up" || selected_connection_item->connection->direction == "down") {
selected_connection_item->setX(selected_connection_item->initialX + (offset - selected_connection_item->initialOffset) * 16);
} else if (selected_connection_item->connection->direction == "left" || selected_connection_item->connection->direction == "right") {
selected_connection_item->setY(selected_connection_item->initialY + (offset - selected_connection_item->initialOffset) * 16);
}
selected_connection_item->blockSignals(false);
updateMirroredConnectionOffset(selected_connection_item->connection);
maskNonVisibleConnectionTiles();
}
void Editor::setConnectionMap(QString mapName) {
if (!mapName.isEmpty() && !project->mapNames.contains(mapName)) {
logError(QString("Invalid map name '%1' specified for connection.").arg(mapName));
return;
}
if (!selected_connection_item)
return;
if (mapName.isEmpty() || mapName == DYNAMIC_MAP_NAME) {
removeCurrentConnection();
return;
}
QString originalMapName = selected_connection_item->connection->map_name;
setConnectionEditControlsEnabled(true);
selected_connection_item->connection->map_name = mapName;
setCurrentConnectionDirection(selected_connection_item->connection->direction);
// New map may have a different minimum offset than the last one. The maximum will be the same.
int min = selected_connection_item->getMinOffset();
ui->spinBox_ConnectionOffset->setMinimum(min);
onConnectionOffsetChanged(qMax(min, selected_connection_item->connection->offset));
updateMirroredConnectionMap(selected_connection_item->connection, originalMapName);
maskNonVisibleConnectionTiles();
}
void Editor::addNewConnection() {
// Find direction with least number of connections.
QMap<QString, int> directionCounts = QMap<QString, int>({{"up", 0}, {"right", 0}, {"down", 0}, {"left", 0}});
for (MapConnection* connection : map->connections) {
directionCounts[connection->direction]++;
}
QString minDirection = "up";
int minCount = INT_MAX;
for (QString direction : directionCounts.keys()) {
if (directionCounts[direction] < minCount) {
minDirection = direction;
minCount = directionCounts[direction];
}
}
// Don't connect the map to itself.
QString defaultMapName = project->mapNames.first();
if (defaultMapName == map->name) {
defaultMapName = project->mapNames.value(1);
}
MapConnection* newConnection = new MapConnection;
newConnection->direction = minDirection;
newConnection->offset = 0;
newConnection->map_name = defaultMapName;
map->connections.append(newConnection);
createConnectionItem(newConnection);
onConnectionItemSelected(connection_items.last());
ui->label_NumConnections->setText(QString::number(map->connections.length()));
updateMirroredConnection(newConnection, newConnection->direction, newConnection->map_name);
}
void Editor::updateMirroredConnectionOffset(MapConnection* connection) {
updateMirroredConnection(connection, connection->direction, connection->map_name);
}
void Editor::updateMirroredConnectionDirection(MapConnection* connection, QString originalDirection) {
updateMirroredConnection(connection, originalDirection, connection->map_name);
}
void Editor::updateMirroredConnectionMap(MapConnection* connection, QString originalMapName) {
updateMirroredConnection(connection, connection->direction, originalMapName);
}
void Editor::removeMirroredConnection(MapConnection* connection) {
updateMirroredConnection(connection, connection->direction, connection->map_name, true);
}
void Editor::updateMirroredConnection(MapConnection* connection, QString originalDirection, QString originalMapName, bool isDelete) {
if (!ui->checkBox_MirrorConnections->isChecked())
return;
Map* otherMap = project->getMap(originalMapName);
if (!otherMap)
return;
static QMap<QString, QString> oppositeDirections = QMap<QString, QString>({
{"up", "down"}, {"right", "left"},
{"down", "up"}, {"left", "right"},
{"dive", "emerge"},{"emerge", "dive"}});
QString oppositeDirection = oppositeDirections.value(originalDirection);
// Find the matching connection in the connected map.
MapConnection* mirrorConnection = nullptr;
for (MapConnection* conn : otherMap->connections) {
if (conn->direction == oppositeDirection && conn->map_name == map->name) {
mirrorConnection = conn;
}
}
if (isDelete) {
if (mirrorConnection) {
otherMap->connections.removeOne(mirrorConnection);
delete mirrorConnection;
}
return;
}
if (connection->direction != originalDirection || connection->map_name != originalMapName) {
if (mirrorConnection) {
otherMap->connections.removeOne(mirrorConnection);
delete mirrorConnection;
mirrorConnection = nullptr;
otherMap = project->getMap(connection->map_name);
}
}
// Create a new mirrored connection, if a matching one doesn't already exist.
if (!mirrorConnection) {
mirrorConnection = new MapConnection;
mirrorConnection->direction = oppositeDirections.value(connection->direction);
mirrorConnection->map_name = map->name;
otherMap->connections.append(mirrorConnection);
}
mirrorConnection->offset = -connection->offset;
}
void Editor::removeCurrentConnection() {
if (!selected_connection_item)
return;
map->connections.removeOne(selected_connection_item->connection);
connection_items.removeOne(selected_connection_item);
removeMirroredConnection(selected_connection_item->connection);
if (selected_connection_item && selected_connection_item->scene()) {
selected_connection_item->scene()->removeItem(selected_connection_item);
delete selected_connection_item;
}
selected_connection_item = nullptr;
setConnectionEditControlsEnabled(false);
ui->spinBox_ConnectionOffset->setValue(0);
ui->label_NumConnections->setText(QString::number(map->connections.length()));
if (connection_items.length() > 0) {
onConnectionItemSelected(connection_items.last());
}
}
void Editor::updateDiveMap(QString mapName) {
updateDiveEmergeMap(mapName, "dive");
}
void Editor::updateEmergeMap(QString mapName) {
updateDiveEmergeMap(mapName, "emerge");
}
void Editor::updateDiveEmergeMap(QString mapName, QString direction) {
if (!mapName.isEmpty() && !project->mapNamesToMapConstants.contains(mapName)) {
logError(QString("Invalid %1 connection map name: '%2'").arg(direction).arg(mapName));
return;
}
MapConnection* connection = nullptr;
for (MapConnection* conn : map->connections) {
if (conn->direction == direction) {
connection = conn;
break;
}
}
if (mapName.isEmpty() || mapName == DYNAMIC_MAP_NAME) {
// Remove dive/emerge connection
if (connection) {
map->connections.removeOne(connection);
removeMirroredConnection(connection);
}
} else {
if (!connection) {
connection = new MapConnection;
connection->direction = direction;
connection->offset = 0;
connection->map_name = mapName;
map->connections.append(connection);
updateMirroredConnection(connection, connection->direction, connection->map_name);
} else {
QString originalMapName = connection->map_name;
connection->map_name = mapName;
updateMirroredConnectionMap(connection, originalMapName);
}
}
ui->label_NumConnections->setText(QString::number(map->connections.length()));
}
void Editor::updatePrimaryTileset(QString tilesetLabel, bool forceLoad)
{
if (map->layout->tileset_primary_label != tilesetLabel || forceLoad)
@ -2028,17 +1886,42 @@ void Editor::updateSecondaryTileset(QString tilesetLabel, bool forceLoad)
void Editor::toggleBorderVisibility(bool visible, bool enableScriptCallback)
{
this->setBorderItemsVisible(visible);
this->setConnectionItemsVisible(visible);
porymapConfig.showBorder = visible;
updateBorderVisibility();
if (enableScriptCallback)
Scripting::cb_BorderVisibilityToggled(visible);
}
void Editor::updateBorderVisibility() {
// On the connections tab the border is always visible, and the connections can be edited.
bool editingConnections = (ui->mainTabBar->currentIndex() == MainTab::Connections);
bool visible = (editingConnections || ui->checkBox_ToggleBorder->isChecked());
// Update border
const qreal borderOpacity = editingConnections ? 0.4 : 1;
for (QGraphicsPixmapItem* item : borderItems) {
item->setVisible(visible);
item->setOpacity(borderOpacity);
}
// Update map connections
for (ConnectionPixmapItem* item : connection_items) {
item->setVisible(visible);
item->setEditable(editingConnections);
item->setEnabled(visible);
// 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());
item->render(fullRender);
}
}
void Editor::updateCustomMapHeaderValues(QTableWidget *table)
{
map->customHeaders = CustomAttributesTable::getAttributes(table);
emit editedMapData();
map->modify();
}
Tileset* Editor::getCurrentMapPrimaryTileset()

View file

@ -18,6 +18,7 @@
#include "prefab.h"
#include "montabwidget.h"
#include "imageexport.h"
#include "newmapconnectiondialog.h"
#include <QFileDialog>
#include <QClipboard>
@ -150,9 +151,9 @@ void MainWindow::initExtraShortcuts() {
shortcutDuplicate_Events->setWhatsThis("Duplicate Selected Event(s)");
auto *shortcutDelete_Object = new Shortcut(
{QKeySequence("Del"), QKeySequence("Backspace")}, this, SLOT(on_toolButton_deleteObject_clicked()));
{QKeySequence("Del"), QKeySequence("Backspace")}, this, SLOT(onDeleteKeyPressed()));
shortcutDelete_Object->setObjectName("shortcutDelete_Object");
shortcutDelete_Object->setWhatsThis("Delete Selected Event(s)");
shortcutDelete_Object->setWhatsThis("Delete Selected Item(s)");
auto *shortcutToggle_Border = new Shortcut(QKeySequence(), ui->checkBox_ToggleBorder, SLOT(toggle()));
shortcutToggle_Border->setObjectName("shortcutToggle_Border");
@ -208,16 +209,23 @@ void MainWindow::applyUserShortcuts() {
shortcut->setKeys(shortcutsConfig.userShortcuts(shortcut));
}
static const QMap<int, QString> mainTabNames = {
{MainTab::Map, "Map"},
{MainTab::Events, "Events"},
{MainTab::Header, "Header"},
{MainTab::Connections, "Connections"},
{MainTab::WildPokemon, "Wild Pokemon"},
};
void MainWindow::initCustomUI() {
// Set up the tab bar
while (ui->mainTabBar->count()) ui->mainTabBar->removeTab(0);
ui->mainTabBar->addTab("Map");
ui->mainTabBar->setTabIcon(0, QIcon(QStringLiteral(":/icons/map.ico")));
ui->mainTabBar->addTab("Events");
ui->mainTabBar->addTab("Header");
ui->mainTabBar->addTab("Connections");
ui->mainTabBar->addTab("Wild Pokemon");
ui->mainTabBar->setTabIcon(4, QIcon(QStringLiteral(":/icons/tall_grass.ico")));
for (int i = 0; i < mainTabNames.count(); i++)
ui->mainTabBar->addTab(mainTabNames.value(i));
ui->mainTabBar->setTabIcon(MainTab::Map, QIcon(QStringLiteral(":/icons/map.ico")));
ui->mainTabBar->setTabIcon(MainTab::WildPokemon, QIcon(QStringLiteral(":/icons/tall_grass.ico")));
}
void MainWindow::initExtraSignals() {
@ -298,12 +306,11 @@ void MainWindow::checkForUpdates(bool) {}
void MainWindow::initEditor() {
this->editor = new Editor(ui);
connect(this->editor, &Editor::objectsChanged, this, &MainWindow::updateObjects);
connect(this->editor, &Editor::loadMapRequested, this, &MainWindow::onLoadMapRequested);
connect(this->editor, &Editor::openConnectedMap, this, &MainWindow::onOpenConnectedMap);
connect(this->editor, &Editor::warpEventDoubleClicked, this, &MainWindow::openWarpMap);
connect(this->editor, &Editor::currentMetatilesSelectionChanged, this, &MainWindow::currentMetatilesSelectionChanged);
connect(this->editor, &Editor::wildMonDataChanged, this, &MainWindow::onWildMonDataChanged);
connect(this->editor, &Editor::mapRulerStatusChanged, this, &MainWindow::onMapRulerStatusChanged);
connect(this->editor, &Editor::editedMapData, this, &MainWindow::markMapEdited);
connect(this->editor, &Editor::tilesetUpdated, this, &Scripting::cb_TilesetUpdated);
connect(ui->toolButton_Open_Scripts, &QToolButton::pressed, this->editor, &Editor::openMapScripts);
connect(ui->actionOpen_Project_in_Text_Editor, &QAction::triggered, this->editor, &Editor::openProjectInTextEditor);
@ -355,9 +362,7 @@ void MainWindow::initEditor() {
}
void MainWindow::initMiscHeapObjects() {
mapIcon = new QIcon(QStringLiteral(":/icons/map.ico"));
mapEditedIcon = new QIcon(QStringLiteral(":/icons/map_edited.ico"));
mapOpenedIcon = new QIcon(QStringLiteral(":/icons/map_opened.ico"));
mapIcon = QIcon(QStringLiteral(":/icons/map.ico"));
mapListModel = new QStandardItemModel;
mapGroupItemsList = new QList<QStandardItem*>;
@ -400,10 +405,17 @@ void MainWindow::showWindowTitle() {
}
void MainWindow::markMapEdited() {
if (editor && editor->map) {
editor->map->hasUnsavedDataChanges = true;
if (editor) markMapEdited(editor->map);
}
void MainWindow::markMapEdited(Map* map) {
if (!map)
return;
map->hasUnsavedDataChanges = true;
updateMapListIcon(map->name);
if (editor && editor->map == map)
showWindowTitle();
}
}
void MainWindow::mapSortOrder_changed(QAction *action)
@ -448,6 +460,13 @@ void MainWindow::applyMapListFilter(QString filterText)
}
void MainWindow::loadUserSettings() {
const QSignalBlocker blocker1(ui->horizontalSlider_CollisionTransparency);
const QSignalBlocker blocker2(ui->slider_DiveEmergeMapOpacity);
const QSignalBlocker blocker3(ui->slider_DiveMapOpacity);
const QSignalBlocker blocker4(ui->slider_EmergeMapOpacity);
const QSignalBlocker blocker5(ui->horizontalSlider_MetatileZoom);
const QSignalBlocker blocker6(ui->horizontalSlider_CollisionZoom);
ui->actionBetter_Cursors->setChecked(porymapConfig.prettyCursors);
this->editor->settings->betterCursors = porymapConfig.prettyCursors;
ui->actionPlayer_View_Rectangle->setChecked(porymapConfig.showPlayerView);
@ -456,17 +475,17 @@ void MainWindow::loadUserSettings() {
this->editor->settings->cursorTileRectEnabled = porymapConfig.showCursorTile;
ui->checkBox_ToggleBorder->setChecked(porymapConfig.showBorder);
ui->checkBox_ToggleGrid->setChecked(porymapConfig.showGrid);
ui->horizontalSlider_CollisionTransparency->blockSignals(true);
ui->checkBox_MirrorConnections->setChecked(porymapConfig.mirrorConnectingMaps);
this->editor->collisionOpacity = static_cast<qreal>(porymapConfig.collisionOpacity) / 100;
ui->horizontalSlider_CollisionTransparency->setValue(porymapConfig.collisionOpacity);
ui->horizontalSlider_CollisionTransparency->blockSignals(false);
ui->horizontalSlider_MetatileZoom->blockSignals(true);
ui->slider_DiveEmergeMapOpacity->setValue(porymapConfig.diveEmergeMapOpacity);
ui->slider_DiveMapOpacity->setValue(porymapConfig.diveMapOpacity);
ui->slider_EmergeMapOpacity->setValue(porymapConfig.emergeMapOpacity);
ui->horizontalSlider_MetatileZoom->setValue(porymapConfig.metatilesZoom);
ui->horizontalSlider_MetatileZoom->blockSignals(false);
ui->horizontalSlider_CollisionZoom->blockSignals(true);
ui->horizontalSlider_CollisionZoom->setValue(porymapConfig.collisionZoom);
ui->horizontalSlider_CollisionZoom->blockSignals(false);
setTheme(porymapConfig.theme);
setDivingMapsVisible(porymapConfig.showDiveEmergeMaps);
}
void MainWindow::restoreWindowState() {
@ -537,15 +556,17 @@ bool MainWindow::openProject(QString dir, bool initial) {
Scripting::init(this);
// Create the project
this->editor->project = new Project(this);
QObject::connect(this->editor->project, &Project::reloadProject, this, &MainWindow::on_action_Reload_Project_triggered);
QObject::connect(this->editor->project, &Project::mapCacheCleared, this, &MainWindow::onMapCacheCleared);
QObject::connect(this->editor->project, &Project::uncheckMonitorFilesAction, [this]() {
auto project = new Project(this);
project->set_root(dir);
QObject::connect(project, &Project::reloadProject, this, &MainWindow::on_action_Reload_Project_triggered);
QObject::connect(project, &Project::mapCacheCleared, this, &MainWindow::onMapCacheCleared);
QObject::connect(project, &Project::mapLoaded, this, &MainWindow::onMapLoaded);
QObject::connect(project, &Project::uncheckMonitorFilesAction, [this]() {
porymapConfig.monitorFiles = false;
if (this->preferenceEditor)
this->preferenceEditor->updateFields();
});
this->editor->project->set_root(dir);
this->editor->setProject(project);
// Make sure project looks reasonable before attempting to load it
if (!checkProjectSanity()) {
@ -720,9 +741,34 @@ void MainWindow::on_action_Close_Project_triggered() {
porymapConfig.projectManuallyClosed = true;
}
// setMap, but with a visible error message in case of failure.
// Use when the user is specifically requesting a map to open.
bool MainWindow::userSetMap(QString map_name, bool scrollTreeView) {
if (editor->map && editor->map->name == map_name)
return true; // Already set
if (map_name == DYNAMIC_MAP_NAME) {
QMessageBox msgBox(this);
QString errorMsg = QString("The map '%1' can't be opened, it's a placeholder to indicate the specified map will be set programmatically.").arg(map_name);
msgBox.critical(nullptr, "Error Opening Map", errorMsg);
return false;
}
if (!setMap(map_name, scrollTreeView)) {
QMessageBox msgBox(this);
QString errorMsg = QString("There was an error opening map %1. Please see %2 for full error details.\n\n%3")
.arg(map_name)
.arg(getLogPath())
.arg(getMostRecentError());
msgBox.critical(nullptr, "Error Opening Map", errorMsg);
return false;
}
return true;
}
bool MainWindow::setMap(QString map_name, bool scrollTreeView) {
logInfo(QString("Setting map to '%1'").arg(map_name));
if (map_name.isEmpty()) {
if (map_name.isEmpty() || map_name == DYNAMIC_MAP_NAME) {
return false;
}
@ -750,12 +796,13 @@ bool MainWindow::setMap(QString map_name, bool scrollTreeView) {
showWindowTitle();
connect(editor->map, &Map::mapChanged, this, &MainWindow::onMapChanged);
connect(editor->map, &Map::mapNeedsRedrawing, this, &MainWindow::onMapNeedsRedrawing);
connect(editor->map, &Map::modified, [this](){ this->markMapEdited(); });
// Swap the "currently-open" icon from the old map to the new map
if (!userConfig.recentMap.isEmpty() && userConfig.recentMap != map_name)
updateMapListIcon(userConfig.recentMap);
userConfig.recentMap = map_name;
updateMapList();
updateMapListIcon(userConfig.recentMap);
Scripting::cb_MapOpened(map_name);
prefab.updatePrefabUi(editor->map);
@ -801,10 +848,6 @@ void MainWindow::refreshMapScene()
}
void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_group) {
// Can't warp to dynamic maps
if (map_name == DYNAMIC_MAP_NAME)
return;
// Ensure valid destination map name.
if (!editor->project->mapNames.contains(map_name)) {
logError(QString("Invalid map name '%1'").arg(map_name));
@ -812,15 +855,8 @@ void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_
}
// Open the destination map.
if (!setMap(map_name, true)) {
QMessageBox msgBox(this);
QString errorMsg = QString("There was an error opening map %1. Please see %2 for full error details.\n\n%3")
.arg(map_name)
.arg(getLogPath())
.arg(getMostRecentError());
msgBox.critical(nullptr, "Error Opening Map", errorMsg);
if (!userSetMap(map_name, true))
return;
}
// Select the target event.
int index = event_id - Event::getIndexOffset(event_group);
@ -869,12 +905,8 @@ void MainWindow::displayMapProperties() {
ui->frame_3->setEnabled(true);
Map *map = editor->map;
ui->comboBox_PrimaryTileset->blockSignals(true);
ui->comboBox_SecondaryTileset->blockSignals(true);
ui->comboBox_PrimaryTileset->setCurrentText(map->layout->tileset_primary_label);
ui->comboBox_SecondaryTileset->setCurrentText(map->layout->tileset_secondary_label);
ui->comboBox_PrimaryTileset->blockSignals(false);
ui->comboBox_SecondaryTileset->blockSignals(false);
ui->comboBox_Song->setCurrentText(map->song);
ui->comboBox_Location->setCurrentText(map->location);
@ -997,6 +1029,8 @@ bool MainWindow::setProjectUI() {
const QSignalBlocker blocker5(ui->comboBox_Weather);
const QSignalBlocker blocker6(ui->comboBox_BattleScene);
const QSignalBlocker blocker7(ui->comboBox_Type);
const QSignalBlocker blocker8(ui->comboBox_DiveMap);
const QSignalBlocker blocker9(ui->comboBox_EmergeMap);
// Set up project comboboxes
ui->comboBox_Song->clear();
@ -1013,14 +1047,21 @@ bool MainWindow::setProjectUI() {
ui->comboBox_BattleScene->addItems(project->mapBattleScenes);
ui->comboBox_Type->clear();
ui->comboBox_Type->addItems(project->mapTypes);
ui->comboBox_DiveMap->clear();
ui->comboBox_DiveMap->addItems(project->mapNames);
ui->comboBox_DiveMap->setClearButtonEnabled(true);
ui->comboBox_DiveMap->setFocusedScrollingEnabled(false);
ui->comboBox_EmergeMap->clear();
ui->comboBox_EmergeMap->addItems(project->mapNames);
ui->comboBox_EmergeMap->setClearButtonEnabled(true);
ui->comboBox_EmergeMap->setFocusedScrollingEnabled(false);
sortMapList();
// Show/hide parts of the UI that are dependent on the user's project settings
// Wild Encounters tab
// TODO: This index should come from an enum
ui->mainTabBar->setTabEnabled(4, editor->project->wildEncountersLoaded);
ui->mainTabBar->setTabEnabled(MainTab::WildPokemon, editor->project->wildEncountersLoaded);
bool hasFlags = projectConfig.mapAllowFlagsEnabled;
ui->checkBox_AllowRunning->setVisible(hasFlags);
@ -1055,6 +1096,8 @@ void MainWindow::clearProjectUI() {
const QSignalBlocker blocker5(ui->comboBox_Weather);
const QSignalBlocker blocker6(ui->comboBox_BattleScene);
const QSignalBlocker blocker7(ui->comboBox_Type);
const QSignalBlocker blocker8(ui->comboBox_DiveMap);
const QSignalBlocker blocker9(ui->comboBox_EmergeMap);
ui->comboBox_Song->clear();
ui->comboBox_Location->clear();
@ -1063,6 +1106,8 @@ void MainWindow::clearProjectUI() {
ui->comboBox_Weather->clear();
ui->comboBox_BattleScene->clear();
ui->comboBox_Type->clear();
ui->comboBox_DiveMap->clear();
ui->comboBox_EmergeMap->clear();
// Clear map list
mapListModel->clear();
@ -1180,7 +1225,7 @@ void MainWindow::sortMapList() {
QStandardItem* MainWindow::createMapItem(QString mapName, int groupNum, int inGroupNum) {
QStandardItem *map = new QStandardItem;
map->setText(QString("[%1.%2] ").arg(groupNum).arg(inGroupNum, 2, 10, QLatin1Char('0')) + mapName);
map->setIcon(*mapIcon);
map->setIcon(mapIcon);
map->setEditable(false);
map->setData(mapName, Qt::UserRole);
map->setData("map_name", MapListUserRoles::TypeRole);
@ -1268,6 +1313,16 @@ void MainWindow::onNewMapCreated() {
sortMapList();
setMap(newMapName, true);
// Refresh any combo box that displays map names and persists between maps
// (other combo boxes like for warp destinations are repopulated when the map changes).
int index = this->editor->project->mapNames.indexOf(newMapName);
if (index >= 0) {
const QSignalBlocker blocker1(ui->comboBox_DiveMap);
const QSignalBlocker blocker2(ui->comboBox_EmergeMap);
ui->comboBox_DiveMap->insertItem(index, newMapName);
ui->comboBox_EmergeMap->insertItem(index, newMapName);
}
if (newMap->needsHealLocation) {
addNewEvent(Event::Type::HealLocation);
editor->project->saveHealLocations(newMap);
@ -1472,51 +1527,46 @@ void MainWindow::currentMetatilesSelectionChanged() {
void MainWindow::on_mapList_activated(const QModelIndex &index)
{
QVariant data = index.data(Qt::UserRole);
if (index.data(MapListUserRoles::TypeRole) == "map_name" && !data.isNull()) {
QString mapName = data.toString();
if (!setMap(mapName)) {
QMessageBox msgBox(this);
QString errorMsg = QString("There was an error opening map %1. Please see %2 for full error details.\n\n%3")
.arg(mapName)
.arg(getLogPath())
.arg(getMostRecentError());
msgBox.critical(nullptr, "Error Opening Map", errorMsg);
}
}
if (index.data(MapListUserRoles::TypeRole) == "map_name" && !data.isNull())
userSetMap(data.toString());
}
void MainWindow::drawMapListIcons(QAbstractItemModel *model) {
projectHasUnsavedChanges = false;
QList<QModelIndex> list;
list.append(QModelIndex());
while (list.length()) {
QModelIndex parent = list.takeFirst();
for (int i = 0; i < model->rowCount(parent); i++) {
QModelIndex index = model->index(i, 0, parent);
if (model->hasChildren(index)) {
list.append(index);
}
QVariant data = index.data(Qt::UserRole);
if (!data.isNull()) {
QString map_name = data.toString();
if (editor->project && editor->project->mapCache.contains(map_name)) {
QStandardItem *map = mapListModel->itemFromIndex(mapListIndexes.value(map_name));
map->setIcon(*mapIcon);
if (editor->project->mapCache.value(map_name)->hasUnsavedChanges()) {
map->setIcon(*mapEditedIcon);
projectHasUnsavedChanges = true;
}
if (editor->map->name == map_name) {
map->setIcon(*mapOpenedIcon);
}
}
}
}
void MainWindow::updateMapListIcon(const QString &mapName) {
if (!editor->project || !editor->project->mapCache.contains(mapName))
return;
QStandardItem *item = mapListModel->itemFromIndex(mapListIndexes.value(mapName));
if (!item)
return;
static const QIcon mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico"));
static const QIcon mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico"));
if (editor->map && editor->map->name == mapName) {
item->setIcon(mapOpenedIcon);
} else if (editor->project->mapCache.value(mapName)->hasUnsavedChanges()) {
item->setIcon(mapEditedIcon);
} else {
item->setIcon(mapIcon);
}
}
void MainWindow::updateMapList() {
drawMapListIcons(mapListModel);
QList<QModelIndex> list;
list.append(QModelIndex());
while (list.length()) {
QModelIndex parent = list.takeFirst();
for (int i = 0; i < mapListModel->rowCount(parent); i++) {
QModelIndex index = mapListModel->index(i, 0, parent);
if (mapListModel->hasChildren(index)) {
list.append(index);
}
QVariant data = index.data(Qt::UserRole);
if (!data.isNull()) {
updateMapListIcon(data.toString());
}
}
}
}
void MainWindow::on_action_Save_Project_triggered() {
@ -1525,6 +1575,13 @@ void MainWindow::on_action_Save_Project_triggered() {
showWindowTitle();
}
void MainWindow::on_action_Save_triggered() {
editor->save();
if (editor->map)
updateMapListIcon(editor->map->name);
showWindowTitle();
}
void MainWindow::duplicate() {
editor->duplicateSelectedEvents();
}
@ -1574,7 +1631,7 @@ void MainWindow::copy() {
{
default:
break;
case 0:
case MainTab::Map:
{
// copy the map image
QPixmap pixmap = editor->map ? editor->map->render(true) : QPixmap();
@ -1582,7 +1639,7 @@ void MainWindow::copy() {
logInfo("Copied current map image to clipboard");
break;
}
case 1:
case MainTab::Events:
{
if (!editor || !editor->project) break;
@ -1622,7 +1679,7 @@ void MainWindow::copy() {
}
}
}
else if (this->ui->mainTabBar->currentIndex() == 4) {
else if (this->ui->mainTabBar->currentIndex() == MainTab::WildPokemon) {
QWidget *w = this->ui->stackedWidget_WildMons->currentWidget();
if (w) {
MonTabWidget *mtw = static_cast<MonTabWidget *>(w);
@ -1653,7 +1710,7 @@ void MainWindow::paste() {
QClipboard *clipboard = QGuiApplication::clipboard();
QString clipboardText(clipboard->text());
if (ui->mainTabBar->currentIndex() == 4) {
if (ui->mainTabBar->currentIndex() == MainTab::WildPokemon) {
QWidget *w = this->ui->stackedWidget_WildMons->currentWidget();
if (w) {
w->setFocus();
@ -1681,7 +1738,7 @@ void MainWindow::paste() {
{
default:
break;
case 0:
case MainTab::Map:
{
// can only paste currently selected metatiles on this tab
if (pasteObject["object"].toString() != "metatile_selection") {
@ -1777,12 +1834,6 @@ void MainWindow::paste() {
}
}
void MainWindow::on_action_Save_triggered() {
editor->save();
updateMapList();
showWindowTitle();
}
void MainWindow::on_mapViewTab_tabBarClicked(int index)
{
int oldIndex = ui->mapViewTab->currentIndex();
@ -1790,11 +1841,11 @@ void MainWindow::on_mapViewTab_tabBarClicked(int index)
if (index != oldIndex)
Scripting::cb_MapViewTabChanged(oldIndex, index);
if (index == 0) {
if (index == MapViewTab::Metatiles) {
editor->setEditingMap();
} else if (index == 1) {
} else if (index == MapViewTab::Collision) {
editor->setEditingCollision();
} else if (index == 2) {
} else if (index == MapViewTab::Prefabs) {
editor->setEditingMap();
if (projectConfig.prefabFilepath.isEmpty() && !projectConfig.prefabImportPrompted) {
// User hasn't set up prefabs and hasn't been prompted before.
@ -1813,25 +1864,33 @@ void MainWindow::on_mainTabBar_tabBarClicked(int index)
if (index != oldIndex)
Scripting::cb_MainTabChanged(oldIndex, index);
int tabIndexToStackIndex[5] = {0, 0, 1, 2, 3};
ui->mainStackedWidget->setCurrentIndex(tabIndexToStackIndex[index]);
static const QMap<int, int> tabIndexToStackIndex = {
{MainTab::Map, 0},
{MainTab::Events, 0},
{MainTab::Header, 1},
{MainTab::Connections, 2},
{MainTab::WildPokemon, 3},
};
ui->mainStackedWidget->setCurrentIndex(tabIndexToStackIndex.value(index));
if (index == 0) {
if (index == MainTab::Map) {
ui->stackedWidget_MapEvents->setCurrentIndex(0);
on_mapViewTab_tabBarClicked(ui->mapViewTab->currentIndex());
clickToolButtonFromEditMode(editor->map_edit_mode);
} else if (index == 1) {
} else if (index == MainTab::Events) {
ui->stackedWidget_MapEvents->setCurrentIndex(1);
editor->setEditingObjects();
clickToolButtonFromEditMode(editor->obj_edit_mode);
} else if (index == 3) {
} else if (index == MainTab::Connections) {
editor->setEditingConnections();
// Stop the Dive/Emerge combo boxes from getting the initial focus
ui->graphicsView_Connections->setFocus();
}
if (index != 4) {
if (index != MainTab::WildPokemon) {
if (editor->project && editor->project->wildEncountersLoaded)
editor->saveEncounterTabData();
}
if (index != 1) {
if (index != MainTab::Events) {
editor->map_ruler->setEnabled(false);
}
}
@ -2197,17 +2256,73 @@ void MainWindow::eventTabChanged(int index) {
isProgrammaticEventTabChange = false;
}
void MainWindow::on_actionDive_Emerge_Map_triggered() {
setDivingMapsVisible(ui->actionDive_Emerge_Map->isChecked());
}
void MainWindow::on_groupBox_DiveMapOpacity_toggled(bool on) {
setDivingMapsVisible(on);
}
void MainWindow::setDivingMapsVisible(bool visible) {
// Qt doesn't change the style of disabled sliders, so we do it ourselves
QString stylesheet = visible ? "" : "QSlider::groove:horizontal {border: 1px solid #999999; border-radius: 3px; height: 2px; background: #B1B1B1;}"
"QSlider::handle:horizontal {border: 1px solid #444444; border-radius: 3px; width: 10px; height: 9px; margin: -5px -1px; background: #5C5C5C; }";
ui->slider_DiveEmergeMapOpacity->setStyleSheet(stylesheet);
ui->slider_DiveMapOpacity->setStyleSheet(stylesheet);
ui->slider_EmergeMapOpacity->setStyleSheet(stylesheet);
// Sync UI toggle elements
const QSignalBlocker blocker1(ui->groupBox_DiveMapOpacity);
const QSignalBlocker blocker2(ui->actionDive_Emerge_Map);
ui->groupBox_DiveMapOpacity->setChecked(visible);
ui->actionDive_Emerge_Map->setChecked(visible);
porymapConfig.showDiveEmergeMaps = visible;
if (visible) {
// We skip rendering diving maps if this setting is not enabled,
// so when we enable it we need to make sure they've rendered.
this->editor->renderDivingConnections();
}
this->editor->updateDivingMapsVisibility();
}
// Normally a map only has either a Dive map connection or an Emerge map connection,
// in which case we only show a single opacity slider to modify the one in use.
// If a user has both connections we show two separate opacity sliders so they can
// modify them independently.
void MainWindow::on_slider_DiveEmergeMapOpacity_valueChanged(int value) {
porymapConfig.diveEmergeMapOpacity = value;
this->editor->updateDivingMapsVisibility();
}
void MainWindow::on_slider_DiveMapOpacity_valueChanged(int value) {
porymapConfig.diveMapOpacity = value;
this->editor->updateDivingMapsVisibility();
}
void MainWindow::on_slider_EmergeMapOpacity_valueChanged(int value) {
porymapConfig.emergeMapOpacity = value;
this->editor->updateDivingMapsVisibility();
}
void MainWindow::on_horizontalSlider_CollisionTransparency_valueChanged(int value) {
this->editor->collisionOpacity = static_cast<qreal>(value) / 100;
porymapConfig.collisionOpacity = value;
this->editor->collision_item->draw(true);
}
void MainWindow::on_toolButton_deleteObject_clicked() {
if (ui->mainTabBar->currentIndex() != 1) {
// do not delete an event when not on event tab
return;
void MainWindow::onDeleteKeyPressed() {
auto tab = ui->mainTabBar->currentIndex();
if (tab == MainTab::Events) {
on_toolButton_deleteObject_clicked();
} else if (tab == MainTab::Connections) {
if (editor) editor->removeSelectedConnection();
}
}
void MainWindow::on_toolButton_deleteObject_clicked() {
if (editor && editor->selected_events) {
if (editor->selected_events->length()) {
DraggablePixmapItem *nextSelectedEvent = nullptr;
@ -2254,15 +2369,14 @@ void MainWindow::on_toolButton_deleteObject_clicked() {
void MainWindow::on_toolButton_Paint_clicked()
{
if (ui->mainTabBar->currentIndex() == 0)
if (ui->mainTabBar->currentIndex() == MainTab::Map)
editor->map_edit_mode = "paint";
else
editor->obj_edit_mode = "paint";
editor->settings->mapCursor = QCursor(QPixmap(":/icons/pencil_cursor.ico"), 10, 10);
// do not stop single tile mode when editing collision
if (ui->mapViewTab->currentIndex() != 1)
if (ui->mapViewTab->currentIndex() != MapViewTab::Collision)
editor->cursorMapTileRect->stopSingleTileMode();
ui->graphicsView_Map->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
@ -2276,7 +2390,7 @@ void MainWindow::on_toolButton_Paint_clicked()
void MainWindow::on_toolButton_Select_clicked()
{
if (ui->mainTabBar->currentIndex() == 0)
if (ui->mainTabBar->currentIndex() == MainTab::Map)
editor->map_edit_mode = "select";
else
editor->obj_edit_mode = "select";
@ -2295,7 +2409,7 @@ void MainWindow::on_toolButton_Select_clicked()
void MainWindow::on_toolButton_Fill_clicked()
{
if (ui->mainTabBar->currentIndex() == 0)
if (ui->mainTabBar->currentIndex() == MainTab::Map)
editor->map_edit_mode = "fill";
else
editor->obj_edit_mode = "fill";
@ -2314,7 +2428,7 @@ void MainWindow::on_toolButton_Fill_clicked()
void MainWindow::on_toolButton_Dropper_clicked()
{
if (ui->mainTabBar->currentIndex() == 0)
if (ui->mainTabBar->currentIndex() == MainTab::Map)
editor->map_edit_mode = "pick";
else
editor->obj_edit_mode = "pick";
@ -2333,7 +2447,7 @@ void MainWindow::on_toolButton_Dropper_clicked()
void MainWindow::on_toolButton_Move_clicked()
{
if (ui->mainTabBar->currentIndex() == 0)
if (ui->mainTabBar->currentIndex() == MainTab::Map)
editor->map_edit_mode = "move";
else
editor->obj_edit_mode = "move";
@ -2352,7 +2466,7 @@ void MainWindow::on_toolButton_Move_clicked()
void MainWindow::on_toolButton_Shift_clicked()
{
if (ui->mainTabBar->currentIndex() == 0)
if (ui->mainTabBar->currentIndex() == MainTab::Map)
editor->map_edit_mode = "shift";
else
editor->obj_edit_mode = "shift";
@ -2371,7 +2485,7 @@ void MainWindow::on_toolButton_Shift_clicked()
void MainWindow::checkToolButtons() {
QString edit_mode;
if (ui->mainTabBar->currentIndex() == 0) {
if (ui->mainTabBar->currentIndex() == MainTab::Map) {
edit_mode = editor->map_edit_mode;
} else {
edit_mode = editor->obj_edit_mode;
@ -2405,21 +2519,11 @@ void MainWindow::clickToolButtonFromEditMode(QString editMode) {
}
}
void MainWindow::onLoadMapRequested(QString mapName, QString fromMapName) {
if (!setMap(mapName, true)) {
QMessageBox msgBox(this);
QString errorMsg = QString("There was an error opening map %1. Please see %2 for full error details.\n\n%3")
.arg(mapName)
.arg(getLogPath())
.arg(getMostRecentError());
msgBox.critical(nullptr, "Error Opening Map", errorMsg);
void MainWindow::onOpenConnectedMap(MapConnection *connection) {
if (!connection)
return;
}
editor->setSelectedConnectionFromMap(fromMapName);
}
void MainWindow::onMapChanged(Map *) {
updateMapList();
if (userSetMap(connection->targetMapName(), true))
editor->setSelectedConnection(connection->findMirror());
}
void MainWindow::onMapNeedsRedrawing() {
@ -2430,6 +2534,10 @@ void MainWindow::onMapCacheCleared() {
editor->map = nullptr;
}
void MainWindow::onMapLoaded(Map *map) {
connect(map, &Map::modified, [this, map] { this->markMapEdited(map); });
}
void MainWindow::onTilesetsSaved(QString primaryTilesetLabel, QString secondaryTilesetLabel) {
// If saved tilesets are currently in-use, update them and redraw
// Otherwise overwrite the cache for the saved tileset
@ -2532,36 +2640,13 @@ void MainWindow::showExportMapImageWindow(ImageExporterMode mode) {
openSubWindow(this->mapImageExporter);
}
void MainWindow::on_comboBox_ConnectionDirection_currentTextChanged(const QString &direction)
{
editor->updateCurrentConnectionDirection(direction);
markMapEdited();
}
void MainWindow::on_pushButton_AddConnection_clicked() {
if (!this->editor || !this->editor->map || !this->editor->project)
return;
void MainWindow::on_spinBox_ConnectionOffset_valueChanged(int offset)
{
editor->updateConnectionOffset(offset);
markMapEdited();
}
void MainWindow::on_comboBox_ConnectedMap_currentTextChanged(const QString &mapName)
{
if (mapName.isEmpty() || editor->project->mapNames.contains(mapName)) {
editor->setConnectionMap(mapName);
markMapEdited();
}
}
void MainWindow::on_pushButton_AddConnection_clicked()
{
editor->addNewConnection();
markMapEdited();
}
void MainWindow::on_pushButton_RemoveConnection_clicked()
{
editor->removeCurrentConnection();
markMapEdited();
auto dialog = new NewMapConnectionDialog(this, this->editor->map, this->editor->project->mapNames);
connect(dialog, &NewMapConnectionDialog::accepted, this->editor, &Editor::addConnection);
dialog->exec();
}
void MainWindow::on_pushButton_NewWildMonGroup_clicked() {
@ -2588,20 +2673,27 @@ void MainWindow::on_pushButton_ConfigureEncountersJSON_clicked() {
editor->configureEncounterJSON(this);
}
void MainWindow::on_comboBox_DiveMap_currentTextChanged(const QString &mapName)
{
if (mapName.isEmpty() || editor->project->mapNames.contains(mapName)) {
editor->updateDiveMap(mapName);
markMapEdited();
}
void MainWindow::on_button_OpenDiveMap_clicked() {
const QString mapName = ui->comboBox_DiveMap->currentText();
if (editor->project->mapNames.contains(mapName))
userSetMap(mapName, true);
}
void MainWindow::on_comboBox_EmergeMap_currentTextChanged(const QString &mapName)
{
if (mapName.isEmpty() || editor->project->mapNames.contains(mapName)) {
void MainWindow::on_button_OpenEmergeMap_clicked() {
const QString mapName = ui->comboBox_EmergeMap->currentText();
if (editor->project->mapNames.contains(mapName))
userSetMap(mapName, true);
}
void MainWindow::on_comboBox_DiveMap_currentTextChanged(const QString &mapName) {
// Include empty names as an update (user is deleting the connection)
if (mapName.isEmpty() || editor->project->mapNames.contains(mapName))
editor->updateDiveMap(mapName);
}
void MainWindow::on_comboBox_EmergeMap_currentTextChanged(const QString &mapName) {
if (mapName.isEmpty() || editor->project->mapNames.contains(mapName))
editor->updateEmergeMap(mapName);
markMapEdited();
}
}
void MainWindow::on_comboBox_PrimaryTileset_currentTextChanged(const QString &tilesetLabel)
@ -2727,8 +2819,12 @@ void MainWindow::on_checkBox_smartPaths_stateChanged(int selected)
void MainWindow::on_checkBox_ToggleBorder_stateChanged(int selected)
{
bool visible = selected != 0;
editor->toggleBorderVisibility(visible);
editor->toggleBorderVisibility(selected != 0);
}
void MainWindow::on_checkBox_MirrorConnections_stateChanged(int selected)
{
porymapConfig.mirrorConnectingMaps = (selected == Qt::Checked);
}
void MainWindow::on_actionTileset_Editor_triggered()
@ -3046,7 +3142,16 @@ bool MainWindow::closeProject() {
if (!isProjectOpen())
return true;
if (projectHasUnsavedChanges || (editor->map && editor->map->hasUnsavedChanges())) {
// Check loaded maps for unsaved changes
bool unsavedChanges = false;
for (auto map : editor->project->mapCache.values()) {
if (map && map->hasUnsavedChanges()) {
unsavedChanges = true;
break;
}
}
if (unsavedChanges) {
QMessageBox::StandardButton result = QMessageBox::question(
this, "porymap", "The project has been modified, save changes?",
QMessageBox::No | QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes);

View file

@ -171,6 +171,9 @@ void Project::clearTilesetCache() {
}
Map* Project::loadMap(QString map_name) {
if (map_name == DYNAMIC_MAP_NAME)
return nullptr;
Map *map;
if (mapCache.contains(map_name)) {
map = mapCache.value(map_name);
@ -183,17 +186,16 @@ Map* Project::loadMap(QString map_name) {
map->setName(map_name);
}
if (!(loadMapData(map) && loadMapLayout(map)))
if (!(loadMapData(map) && loadMapLayout(map))){
delete map;
return nullptr;
}
mapCache.insert(map_name, map);
emit mapLoaded(map);
return map;
}
void Project::setNewMapConnections(Map *map) {
map->connections.clear();
}
const QSet<QString> defaultTopLevelMapFields = {
"id",
"name",
@ -362,18 +364,17 @@ bool Project::loadMapData(Map* map) {
}
}
map->connections.clear();
map->deleteConnections();
QJsonArray connectionsArr = mapObj["connections"].toArray();
if (!connectionsArr.isEmpty()) {
for (int i = 0; i < connectionsArr.size(); i++) {
QJsonObject connectionObj = connectionsArr[i].toObject();
MapConnection *connection = new MapConnection;
connection->direction = ParseUtil::jsonToQString(connectionObj["direction"]);
connection->offset = ParseUtil::jsonToInt(connectionObj["offset"]);
QString mapConstant = ParseUtil::jsonToQString(connectionObj["map"]);
const QString direction = ParseUtil::jsonToQString(connectionObj["direction"]);
int offset = ParseUtil::jsonToInt(connectionObj["offset"]);
const QString mapConstant = ParseUtil::jsonToQString(connectionObj["map"]);
if (mapConstantsToMapNames.contains(mapConstant)) {
connection->map_name = mapConstantsToMapNames.value(mapConstant);
map->connections.append(connection);
// Successully read map connection
map->loadConnection(new MapConnection(mapConstantsToMapNames.value(mapConstant), direction, offset));
} else {
logError(QString("Failed to find connected map for map constant '%1'").arg(mapConstant));
}
@ -1274,17 +1275,18 @@ void Project::saveMap(Map *map) {
mapObj["battle_scene"] = map->battle_scene;
// Connections
if (map->connections.length() > 0) {
auto connections = map->getConnections();
if (connections.length() > 0) {
OrderedJson::array connectionsArr;
for (MapConnection* connection : map->connections) {
if (mapNamesToMapConstants.contains(connection->map_name)) {
for (auto connection : connections) {
if (mapNamesToMapConstants.contains(connection->targetMapName())) {
OrderedJson::object connectionObj;
connectionObj["map"] = this->mapNamesToMapConstants.value(connection->map_name);
connectionObj["offset"] = connection->offset;
connectionObj["direction"] = connection->direction;
connectionObj["map"] = this->mapNamesToMapConstants.value(connection->targetMapName());
connectionObj["offset"] = connection->offset();
connectionObj["direction"] = connection->direction();
connectionsArr.append(connectionObj);
} else {
logError(QString("Failed to write map connection. '%1' is not a valid map name").arg(connection->map_name));
logError(QString("Failed to write map connection. '%1' is not a valid map name").arg(connection->targetMapName()));
}
}
mapObj["connections"] = connectionsArr;
@ -1816,18 +1818,22 @@ bool Project::readMapGroups() {
}
Map* Project::addNewMapToGroup(QString mapName, int groupNum, Map *newMap, bool existingLayout, bool importedMap) {
mapNames.append(mapName);
mapGroups.insert(mapName, groupNum);
groupedMapNames[groupNum].append(mapName);
int mapNamePos = 0;
for (int i = 0; i <= groupNum; i++)
mapNamePos += this->groupedMapNames.value(i).length();
this->mapNames.insert(mapNamePos, mapName);
this->mapGroups.insert(mapName, groupNum);
this->groupedMapNames[groupNum].append(mapName);
newMap->isPersistedToFile = false;
newMap->setName(mapName);
mapConstantsToMapNames.insert(newMap->constantName, newMap->name);
mapNamesToMapConstants.insert(newMap->name, newMap->constantName);
this->mapConstantsToMapNames.insert(newMap->constantName, newMap->name);
this->mapNamesToMapConstants.insert(newMap->name, newMap->constantName);
if (!existingLayout) {
mapLayouts.insert(newMap->layoutId, newMap->layout);
mapLayoutsTable.append(newMap->layoutId);
this->mapLayouts.insert(newMap->layoutId, newMap->layout);
this->mapLayoutsTable.append(newMap->layoutId);
if (!importedMap) {
setNewMapBlockdata(newMap);
}
@ -1838,7 +1844,6 @@ Map* Project::addNewMapToGroup(QString mapName, int groupNum, Map *newMap, bool
loadLayoutTilesets(newMap->layout);
setNewMapEvents(newMap);
setNewMapConnections(newMap);
return newMap;
}

View file

@ -164,7 +164,10 @@ int ScriptUtility::getMapViewTab() {
}
void ScriptUtility::setMapViewTab(int index) {
if (this->getMainTab() != 0 || !window->ui->mapViewTab || index < 0 || index >= window->ui->mapViewTab->count())
if (this->getMainTab() != MainTab::Map || !window->ui->mapViewTab || index < 0 || index >= window->ui->mapViewTab->count())
return;
// Can't select tab if it's disabled
if (!window->ui->mapViewTab->isTabEnabled(index))
return;
window->on_mapViewTab_tabBarClicked(index);
}
@ -178,7 +181,7 @@ bool ScriptUtility::getGridVisibility() {
}
void ScriptUtility::setBorderVisibility(bool visible) {
window->editor->toggleBorderVisibility(visible, false);
window->ui->checkBox_ToggleBorder->setChecked(visible);
}
bool ScriptUtility::getBorderVisibility() {

View file

@ -1,30 +1,48 @@
#include "connectionpixmapitem.h"
#include "editcommands.h"
#include "map.h"
#include <math.h>
void ConnectionPixmapItem::render(qreal opacity) {
QPixmap newPixmap = this->basePixmap.copy(0, 0, this->basePixmap.width(), this->basePixmap.height());
if (opacity < 1) {
QPainter painter(&newPixmap);
int alpha = static_cast<int>(255 * (1 - opacity));
painter.fillRect(0, 0, newPixmap.width(), newPixmap.height(), QColor(0, 0, 0, alpha));
painter.end();
ConnectionPixmapItem::ConnectionPixmapItem(MapConnection* connection, int x, int y)
: QGraphicsPixmapItem(connection->getPixmap()),
connection(connection)
{
this->setEditable(true);
this->basePixmap = pixmap();
this->setOrigin(x, y);
}
ConnectionPixmapItem::ConnectionPixmapItem(MapConnection* connection, QPoint pos)
: ConnectionPixmapItem(connection, pos.x(), pos.y())
{}
// Render additional visual effects on top of the base map image.
void ConnectionPixmapItem::render(bool ignoreCache) {
if (ignoreCache)
this->basePixmap = this->connection->getPixmap();
QPixmap pixmap = this->basePixmap.copy(0, 0, this->basePixmap.width(), this->basePixmap.height());
this->setZValue(-1);
// When editing is inactive the current selection is ignored, all connections should appear normal.
if (this->getEditable()) {
if (this->selected) {
// Draw highlight
QPainter painter(&pixmap);
painter.setPen(QColor(255, 0, 255));
painter.drawRect(0, 0, pixmap.width() - 1, pixmap.height() - 1);
painter.end();
} else {
// Darken the image
this->setZValue(-2);
QPainter painter(&pixmap);
int alpha = static_cast<int>(255 * 0.25);
painter.fillRect(0, 0, pixmap.width(), pixmap.height(), QColor(0, 0, 0, alpha));
painter.end();
}
}
this->setPixmap(newPixmap);
}
int ConnectionPixmapItem::getMinOffset() {
if (this->connection->direction == "up" || this->connection->direction == "down")
return -(this->pixmap().width() / 16) - 6;
else
return -(this->pixmap().height() / 16) - 6;
}
int ConnectionPixmapItem::getMaxOffset() {
if (this->connection->direction == "up" || this->connection->direction == "down")
return this->baseMapWidth + 6;
else
return this->baseMapHeight + 6;
this->setPixmap(pixmap);
}
QVariant ConnectionPixmapItem::itemChange(GraphicsItemChange change, const QVariant &value)
@ -32,32 +50,23 @@ QVariant ConnectionPixmapItem::itemChange(GraphicsItemChange change, const QVari
if (change == ItemPositionChange) {
QPointF newPos = value.toPointF();
qreal x, y;
int newOffset = this->initialOffset;
if (this->connection->direction == "up" || this->connection->direction == "down") {
x = round(newPos.x() / 16) * 16;
newOffset += (x - initialX) / 16;
newOffset = qMin(newOffset, this->getMaxOffset());
newOffset = qMax(newOffset, this->getMinOffset());
x = newOffset * 16;
}
else {
x = this->initialX;
qreal x = this->originX;
qreal y = this->originY;
int newOffset = this->connection->offset();
// Restrict movement to the metatile grid and perpendicular to the connection direction.
if (MapConnection::isVertical(this->connection->direction())) {
x = (round(newPos.x() / this->mWidth) * this->mWidth) - this->originX;
newOffset = x / this->mWidth;
} else if (MapConnection::isHorizontal(this->connection->direction())) {
y = (round(newPos.y() / this->mHeight) * this->mHeight) - this->originY;
newOffset = y / this->mHeight;
}
if (this->connection->direction == "right" || this->connection->direction == "left") {
y = round(newPos.y() / 16) * 16;
newOffset += (y - this->initialY) / 16;
newOffset = qMin(newOffset, this->getMaxOffset());
newOffset = qMax(newOffset, this->getMinOffset());
y = newOffset * 16;
}
else {
y = this->initialY;
}
// 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->offset = newOffset;
emit connectionMoved(this->connection);
return QPointF(x, y);
}
else {
@ -65,6 +74,32 @@ QVariant ConnectionPixmapItem::itemChange(GraphicsItemChange change, const QVari
}
}
// If connection->offset changed externally we call this to correct our position.
void ConnectionPixmapItem::updatePos() {
const QSignalBlocker blocker(this);
qreal x = this->originX;
qreal y = this->originY;
if (MapConnection::isVertical(this->connection->direction())) {
x += this->connection->offset() * this->mWidth;
} else if (MapConnection::isHorizontal(this->connection->direction())) {
y += this->connection->offset() * this->mHeight;
}
this->setPos(x, y);
}
// Set the pixmap's external origin point, i.e. the pixmap's position when connection->offset == 0
void ConnectionPixmapItem::setOrigin(int x, int y) {
this->originX = x;
this->originY = y;
updatePos();
}
void ConnectionPixmapItem::setOrigin(QPoint pos) {
this->setOrigin(pos.x(), pos.y());
}
void ConnectionPixmapItem::setEditable(bool editable) {
setFlag(ItemIsMovable, editable);
setFlag(ItemSendsGeometryChanges, editable);
@ -74,28 +109,25 @@ bool ConnectionPixmapItem::getEditable() {
return (this->flags() & ItemIsMovable) != 0;
}
void ConnectionPixmapItem::updateHighlight(bool selected) {
bool editable = this->getEditable();
int zValue = (selected || !editable) ? -1 : -2;
qreal opacity = (selected || !editable) ? 1 : 0.75;
this->setZValue(zValue);
this->render(opacity);
if (editable && selected) {
QPixmap pixmap = this->pixmap();
QPainter painter(&pixmap);
painter.setPen(QColor(255, 0, 255));
painter.drawRect(0, 0, pixmap.width() - 1, pixmap.height() - 1);
painter.end();
this->setPixmap(pixmap);
}
void ConnectionPixmapItem::setSelected(bool selected) {
if (this->selected == selected)
return;
this->selected = selected;
this->render();
emit selectionChanged(selected);
}
void ConnectionPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *) {
if (!this->getEditable())
return;
emit connectionItemSelected(this);
this->setSelected(true);
}
void ConnectionPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
this->actionId++; // Distinguish between move actions for the edit history
QGraphicsPixmapItem::mouseReleaseEvent(event);
}
void ConnectionPixmapItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *) {
emit connectionItemDoubleClicked(this);
emit connectionItemDoubleClicked(this->connection);
}

View file

@ -0,0 +1,103 @@
#include "connectionslistitem.h"
#include "ui_connectionslistitem.h"
#include "editcommands.h"
#include "map.h"
#include <QLineEdit>
ConnectionsListItem::ConnectionsListItem(QWidget *parent, MapConnection * connection, const QStringList &mapNames) :
QFrame(parent),
ui(new Ui::ConnectionsListItem)
{
ui->setupUi(this);
const QSignalBlocker blocker1(ui->comboBox_Direction);
const QSignalBlocker blocker2(ui->comboBox_Map);
const QSignalBlocker blocker3(ui->spinBox_Offset);
ui->comboBox_Direction->setEditable(false);
ui->comboBox_Direction->setMinimumContentsLength(0);
ui->comboBox_Direction->addItems(MapConnection::cardinalDirections);
ui->comboBox_Map->setMinimumContentsLength(6);
ui->comboBox_Map->addItems(mapNames);
ui->comboBox_Map->setFocusedScrollingEnabled(false); // Scrolling could cause rapid changes to many different maps
ui->comboBox_Map->setInsertPolicy(QComboBox::NoInsert);
ui->spinBox_Offset->setMinimum(INT_MIN);
ui->spinBox_Offset->setMaximum(INT_MAX);
// Invalid map names are not considered a change. If editing finishes with an invalid name, restore the previous name.
connect(ui->comboBox_Map->lineEdit(), &QLineEdit::editingFinished, [this] {
const QSignalBlocker blocker(ui->comboBox_Map);
if (ui->comboBox_Map->findText(ui->comboBox_Map->currentText()) < 0)
ui->comboBox_Map->setTextItem(this->connection->targetMapName());
});
// Distinguish between move actions for the edit history
connect(ui->spinBox_Offset, &QSpinBox::editingFinished, [this] { this->actionId++; });
this->connection = connection;
this->map = connection->parentMap();
this->updateUI();
}
ConnectionsListItem::~ConnectionsListItem()
{
delete ui;
}
void ConnectionsListItem::updateUI() {
if (!this->connection)
return;
const QSignalBlocker blocker1(ui->comboBox_Direction);
const QSignalBlocker blocker2(ui->comboBox_Map);
const QSignalBlocker blocker3(ui->spinBox_Offset);
ui->comboBox_Direction->setTextItem(this->connection->direction());
ui->comboBox_Map->setTextItem(this->connection->targetMapName());
ui->spinBox_Offset->setValue(this->connection->offset());
}
void ConnectionsListItem::setSelected(bool selected) {
if (selected == this->isSelected)
return;
this->isSelected = selected;
this->setStyleSheet(selected ? ".ConnectionsListItem { border: 1px solid rgb(255, 0, 255); }"
: ".ConnectionsListItem { border-width: 1px; }");
if (selected)
emit this->selected();
}
void ConnectionsListItem::mousePressEvent(QMouseEvent *) {
this->setSelected(true);
}
void ConnectionsListItem::on_comboBox_Direction_currentTextChanged(QString direction) {
this->setSelected(true);
if (this->map)
this->map->editHistory.push(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));
}
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));
}
void ConnectionsListItem::on_button_Delete_clicked() {
if (this->map)
this->map->editHistory.push(new MapConnectionRemove(this->map, this->connection));
}
void ConnectionsListItem::on_button_OpenMap_clicked() {
emit openMapClicked(this->connection);
}

View file

@ -0,0 +1,46 @@
#include "divingmappixmapitem.h"
#include "config.h"
DivingMapPixmapItem::DivingMapPixmapItem(MapConnection *connection, QComboBox *combo)
: QGraphicsPixmapItem(getBasePixmap(connection))
{
m_connection = connection;
m_combo = combo;
setComboText(connection->targetMapName());
// Update display if the connected map is swapped.
connect(m_connection, &MapConnection::targetMapNameChanged, this, &DivingMapPixmapItem::onTargetMapChanged);
}
DivingMapPixmapItem::~DivingMapPixmapItem() {
// Clear map name from combo box
setComboText("");
}
QPixmap DivingMapPixmapItem::getBasePixmap(MapConnection* connection) {
if (!connection)
return QPixmap();
if (!porymapConfig.showDiveEmergeMaps)
return QPixmap(); // Save some rendering time if it won't be displayed
if (connection->targetMapName() == connection->parentMapName())
return QPixmap(); // If the map is connected to itself then rendering is pointless.
return connection->getPixmap();
}
void DivingMapPixmapItem::updatePixmap() {
setPixmap(getBasePixmap(m_connection));
}
void DivingMapPixmapItem::onTargetMapChanged() {
updatePixmap();
setComboText(m_connection->targetMapName());
}
void DivingMapPixmapItem::setComboText(const QString &text) {
if (!m_combo)
return;
const QSignalBlocker blocker(m_combo);
m_combo->setCurrentText(text);
}

View file

@ -33,7 +33,7 @@ MapImageExporter::MapImageExporter(QWidget *parent_, Editor *editor_, ImageExpor
this->editor = editor_;
this->mode = mode;
this->setWindowTitle(getTitle(this->mode));
this->ui->groupBox_Connections->setVisible(this->mode == ImageExporterMode::Normal);
this->ui->groupBox_Connections->setVisible(this->mode != ImageExporterMode::Stitch);
this->ui->groupBox_Timelapse->setVisible(this->mode == ImageExporterMode::Timelapse);
this->ui->comboBox_MapSelection->addItems(editor->project->mapNames);
@ -144,7 +144,7 @@ void MapImageExporter::saveImage() {
this->map->editHistory.redo();
}
progress.setValue(progress.maximum() - i);
QPixmap pixmap = this->getFormattedMapPixmap(this->map, !this->showBorder);
QPixmap pixmap = this->getFormattedMapPixmap(this->map);
if (pixmap.width() < maxWidth || pixmap.height() < maxHeight) {
QPixmap pixmap2 = QPixmap(maxWidth, maxHeight);
QPainter painter(&pixmap2);
@ -167,7 +167,7 @@ void MapImageExporter::saveImage() {
}
}
// The latest map state is the last animated frame.
QPixmap pixmap = this->getFormattedMapPixmap(this->map, !this->showBorder);
QPixmap pixmap = this->getFormattedMapPixmap(this->map);
timelapseImg.addFrame(pixmap.toImage());
timelapseImg.save(filepath);
progress.close();
@ -178,6 +178,9 @@ void MapImageExporter::saveImage() {
}
bool MapImageExporter::historyItemAppliesToFrame(const QUndoCommand *command) {
if (command->isObsolete())
return false;
switch (command->id() & 0xFF) {
case CommandId::ID_PaintMetatile:
case CommandId::ID_BucketFillMetatile:
@ -192,6 +195,12 @@ bool MapImageExporter::historyItemAppliesToFrame(const QUndoCommand *command) {
return this->showCollision;
case CommandId::ID_PaintBorder:
return this->showBorder;
case CommandId::ID_MapConnectionMove:
case CommandId::ID_MapConnectionChangeDirection:
case CommandId::ID_MapConnectionChangeMap:
case CommandId::ID_MapConnectionAdd:
case CommandId::ID_MapConnectionRemove:
return this->showUpConnections || this->showDownConnections || this->showLeftConnections || this->showRightConnections;
case CommandId::ID_EventMove:
case CommandId::ID_EventShift:
case CommandId::ID_EventCreate:
@ -238,25 +247,29 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress, bool inclu
visited.insert(cur.map->name);
stitchedMaps.append(cur);
for (MapConnection *connection : cur.map->connections) {
if (connection->direction == "dive" || connection->direction == "emerge")
continue;
for (MapConnection *connection : cur.map->getConnections()) {
const QString direction = connection->direction();
int x = cur.x;
int y = cur.y;
int offset = connection->offset;
Map *connectionMap = this->editor->project->loadMap(connection->map_name);
if (connection->direction == "up") {
int offset = connection->offset();
Map *connectionMap = connection->targetMap();
if (!connectionMap)
continue;
if (direction == "up") {
x += offset;
y -= connectionMap->getHeight();
} else if (connection->direction == "down") {
} else if (direction == "down") {
x += offset;
y += cur.map->getHeight();
} else if (connection->direction == "left") {
} else if (direction == "left") {
x -= connectionMap->getWidth();
y += offset;
} else if (connection->direction == "right") {
} else if (direction == "right") {
x += cur.map->getWidth();
y += offset;
} else {
// Ignore Dive/Emerge connections and unrecognized directions
continue;
}
unvisited.append(StitchedMap{x, y, connectionMap});
}
@ -310,7 +323,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, false);
QPixmap pixmap = this->getFormattedMapPixmap(map.map);
painter.drawPixmap(pixelX, pixelY, pixmap);
}
@ -345,7 +358,7 @@ void MapImageExporter::updatePreview() {
scene = nullptr;
}
preview = getFormattedMapPixmap(this->map, false);
preview = getFormattedMapPixmap(this->map);
scene = new QGraphicsScene;
scene->addPixmap(preview);
this->scene->setSceneRect(this->scene->itemsBoundingRect());
@ -373,8 +386,7 @@ 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;
bool forceDrawBorder = showUpConnections || showDownConnections || showLeftConnections || showRightConnections;
if (!ignoreBorder && (showBorder || forceDrawBorder)) {
if (!ignoreBorder && this->showBorder) {
int borderDistance = this->mode ? STITCH_MODE_BORDER_DISTANCE : BORDER_DISTANCE;
map->renderBorder();
int borderHorzDist = editor->getBorderDrawDistance(map->getBorderWidth());
@ -393,17 +405,17 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) {
pixmap = newPixmap;
}
if (!this->mode) {
if (!ignoreBorder && (this->showUpConnections || this->showDownConnections || this->showLeftConnections || this->showRightConnections)) {
// if showing connections, draw on outside of image
QPainter connectionPainter(&pixmap);
for (auto connectionItem : editor->connection_items) {
QString direction = connectionItem->connection->direction;
const QString direction = connectionItem->connection->direction();
if ((showUpConnections && direction == "up")
|| (showDownConnections && direction == "down")
|| (showLeftConnections && direction == "left")
|| (showRightConnections && direction == "right"))
connectionPainter.drawImage(connectionItem->initialX + borderWidth, connectionItem->initialY + borderHeight,
connectionItem->basePixmap.toImage());
connectionPainter.drawImage(connectionItem->x() + borderWidth, connectionItem->y() + borderHeight,
connectionItem->connection->getPixmap().toImage());
}
connectionPainter.end();
}
@ -412,7 +424,7 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) {
QPainter eventPainter(&pixmap);
QList<Event *> events = map->getAllEvents();
int pixelOffset = 0;
if (!ignoreBorder && showBorder) {
if (!ignoreBorder && this->showBorder) {
pixelOffset = this->mode == ImageExporterMode::Normal ? BORDER_DISTANCE * 16 : STITCH_MODE_BORDER_DISTANCE * 16;
}
for (Event *event : events) {
@ -450,6 +462,18 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) {
return pixmap;
}
void MapImageExporter::updateShowBorderState() {
// If any of the Connections settings are enabled then this setting is locked (it's implicitly enabled)
const QSignalBlocker blocker(ui->checkBox_Border);
if (showUpConnections || showDownConnections || showLeftConnections || showRightConnections) {
ui->checkBox_Border->setChecked(true);
ui->checkBox_Border->setDisabled(true);
showBorder = true;
} else {
ui->checkBox_Border->setDisabled(false);
}
}
void MapImageExporter::on_checkBox_Elevation_stateChanged(int state) {
showCollision = (state == Qt::Checked);
updatePreview();
@ -492,21 +516,25 @@ void MapImageExporter::on_checkBox_HealSpots_stateChanged(int state) {
void MapImageExporter::on_checkBox_ConnectionUp_stateChanged(int state) {
showUpConnections = (state == Qt::Checked);
updateShowBorderState();
updatePreview();
}
void MapImageExporter::on_checkBox_ConnectionDown_stateChanged(int state) {
showDownConnections = (state == Qt::Checked);
updateShowBorderState();
updatePreview();
}
void MapImageExporter::on_checkBox_ConnectionLeft_stateChanged(int state) {
showLeftConnections = (state == Qt::Checked);
updateShowBorderState();
updatePreview();
}
void MapImageExporter::on_checkBox_ConnectionRight_stateChanged(int state) {
showRightConnections = (state == Qt::Checked);
updateShowBorderState();
updatePreview();
}

View file

@ -0,0 +1,72 @@
#include "newmapconnectiondialog.h"
#include "ui_newmapconnectiondialog.h"
NewMapConnectionDialog::NewMapConnectionDialog(QWidget *parent, Map* map, const QStringList &mapNames) :
QDialog(parent),
ui(new Ui::NewMapConnectionDialog)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
ui->comboBox_Direction->setEditable(false);
ui->comboBox_Direction->addItems(MapConnection::cardinalDirections);
ui->comboBox_Map->addItems(mapNames);
ui->comboBox_Map->setInsertPolicy(QComboBox::NoInsert);
// Choose default direction
QMap<QString, int> directionCounts;
for (auto connection : map->getConnections()) {
directionCounts[connection->direction()]++;
}
QString defaultDirection;
int minCount = INT_MAX;
for (auto direction : MapConnection::cardinalDirections) {
if (directionCounts[direction] < minCount) {
defaultDirection = direction;
minCount = directionCounts[direction];
}
}
ui->comboBox_Direction->setTextItem(defaultDirection);
// Choose default map
QString defaultMapName;
if (mapNames.isEmpty()) {
defaultMapName = QString();
} else if (mapNames.first() == map->name && mapNames.length() > 1) {
// Prefer not to connect the map to itself
defaultMapName = mapNames.at(1);
} else {
defaultMapName = mapNames.first();
}
ui->comboBox_Map->setTextItem(defaultMapName);
connect(ui->comboBox_Map, &QComboBox::currentTextChanged, [this] {
if (ui->label_Warning->isVisible() && mapNameIsValid())
setWarningVisible(false);
});
setWarningVisible(false);
}
NewMapConnectionDialog::~NewMapConnectionDialog()
{
delete ui;
}
bool NewMapConnectionDialog::mapNameIsValid() {
return ui->comboBox_Map->findText(ui->comboBox_Map->currentText()) >= 0;
}
void NewMapConnectionDialog::setWarningVisible(bool visible) {
ui->label_Warning->setVisible(visible);
adjustSize();
}
void NewMapConnectionDialog::accept() {
if (!mapNameIsValid()) {
setWarningVisible(true);
return;
}
emit accepted(new MapConnection(ui->comboBox_Map->currentText(), ui->comboBox_Direction->currentText()));
QDialog::accept();
}

View file

@ -1,6 +1,7 @@
#include "noscrollcombobox.h"
#include <QCompleter>
#include <QLineEdit>
NoScrollComboBox::NoScrollComboBox(QWidget *parent)
: QComboBox(parent)
@ -36,11 +37,17 @@ void NoScrollComboBox::setLineEdit(QLineEdit *edit) {
void NoScrollComboBox::wheelEvent(QWheelEvent *event)
{
// Only allow scrolling to modify contents when it explicitly has focus.
if (hasFocus())
// By default NoScrollComboBoxes will allow scrolling to modify its contents only when it explicitly has focus.
// If focusedScrollingEnabled is false it won't allow scrolling even with focus.
if (this->focusedScrollingEnabled && hasFocus())
QComboBox::wheelEvent(event);
}
void NoScrollComboBox::setFocusedScrollingEnabled(bool enabled)
{
this->focusedScrollingEnabled = enabled;
}
void NoScrollComboBox::setItem(int index, const QString &text)
{
if (index >= 0) {
@ -73,3 +80,8 @@ void NoScrollComboBox::setHexItem(uint32_t value)
{
this->setItem(this->findData(value), "0x" + QString::number(value, 16).toUpper());
}
void NoScrollComboBox::setClearButtonEnabled(bool enabled) {
if (this->lineEdit())
this->lineEdit()->setClearButtonEnabled(enabled);
}