diff --git a/.gitignore b/.gitignore index 9157e62cf..083884971 100644 --- a/.gitignore +++ b/.gitignore @@ -8,9 +8,11 @@ porymap.app* porymap porymap.cfg porymap.log +build/ # Qt generated files ui_*.h moc_*.h moc_*.cpp qrc_*.cpp +.qtc_clangd diff --git a/CHANGELOG.md b/CHANGELOG.md index 610f62b51..199bd6594 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,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 the ability to edit layouts with no corresponding map. - Add a `Close Project` option - Add charts to the `Wild Pokémon` tab that show species and level distributions. +- Add options for customizing the map grid under `View -> Grid Settings`. - An alert will be displayed when attempting to open a seemingly invalid project. - Add support for defining project values with `enum` where `#define` was expected. +- Add button to enable editing map groups including renaming groups and rearranging the maps within them. +- Add buttons to hide and show empty folders in each map tree view. ### Changed - Edits to map connections now have Undo/Redo and can be viewed in exported timelapses. @@ -22,6 +26,8 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - The max encounter rate is now read from the project, rather than assuming the default value from RSE. - It's now possible to cancel quitting if there are unsaved changes in sub-windows. - The triple-layer metatiles setting can now be set automatically using a project constant. +- `Export Map Stitch Image` now shows a preview of the full image, not just the current map. +- Maps and layouts were internally separated. ### Fixed - Fix `Add Region Map...` not updating the region map settings file. @@ -45,6 +51,16 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix the map list filter retaining text between project open/close. - Fix the map list mishandling value gaps when sorting by Area. - Fix a freeze on startup if project values are defined with mismatched parentheses. +- Fix stitched map images sometimes rendering garbage +- Fix the `Reset` button on `Export Map Timelapse Image` not resetting the Timelapse settings. +- Stop sliders in the Palette Editor from creating a bunch of edit history when used. +- Fix scrolling on some containers locking up when the mouse stops over a spin box or combo box. +- Fix some file dialogs returning to an incorrect window when closed. +- Fix bug where reloading a layout would overwrite all unsaved changes. +- Fix bug where layout json and blockdata could be saved separately leading to inconsistent data. +- Fix crash when saving tilesets with fewer palettes than the maximum. +- Fix projects not opening on Windows if the project filepath contains certain characters. +- Fix exported tile images containing garbage pixels after the end of the tiles. ## [5.4.1] - 2024-03-21 ### Fixed diff --git a/INSTALL.md b/INSTALL.md index 34f03196f..6c26b59a5 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -39,3 +39,14 @@ qmake make ./porymap ``` + +## Arch Linux + +You need to install Qt. You can check the version of your Qt packages with `qtdiag` or `qmake --version`. + +```bash +sudo pacman -S qt6-declarative qt6-charts +qmake +make +./porymap +``` diff --git a/docsrc/manual/project-files.rst b/docsrc/manual/project-files.rst index 30f0acc89..e4f0479b5 100644 --- a/docsrc/manual/project-files.rst +++ b/docsrc/manual/project-files.rst @@ -59,7 +59,6 @@ The filepath that Porymap expects for each file can be overridden on the ``Files include/constants/event_object_movement.h, yes, no, ``constants_obj_event_movement``, include/constants/event_objects.h, yes, no, ``constants_obj_events``, include/constants/event_bg.h, yes, no, ``constants_event_bg``, - include/constants/region_map_sections.h, yes, no, ``constants_region_map_sections``, include/constants/metatile_labels.h, yes, yes, ``constants_metatile_labels``, include/constants/metatile_behaviors.h, yes, no, ``constants_metatile_behaviors``, include/constants/species.h, yes, no, ``constants_metatile_behaviors``, for the Wild Pokémon tab @@ -122,7 +121,6 @@ In addition to these files, there are some specific symbol and macro names that ``define_map_empty``, ``UNDEFINED``, macro name after prefix for empty maps ``define_map_section_prefix``, ``MAPSEC_``, expected prefix for location macro names ``define_map_section_empty``, ``NONE``, macro name after prefix for empty region map sections - ``define_map_section_count``, ``COUNT``, macro name after prefix for total number of region map sections ``define_species_prefix``, ``SPECIES_``, expected prefix for species macro names ``regex_behaviors``, ``\bMB_``, regex to find metatile behavior macro names ``regex_obj_event_gfx``, ``\bOBJ_EVENT_GFX_``, regex to find Object Event graphics ID macro names diff --git a/forms/colorinputwidget.ui b/forms/colorinputwidget.ui new file mode 100644 index 000000000..11f0bc250 --- /dev/null +++ b/forms/colorinputwidget.ui @@ -0,0 +1,310 @@ + + + ColorInputWidget + + + + 0 + 0 + 221 + 212 + + + + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Red + + + + + + + Green + + + + + + + Blue + + + + + + + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 31 + + + 4 + + + Qt::Orientation::Horizontal + + + + + + + 31 + + + 4 + + + Qt::Orientation::Horizontal + + + + + + + 31 + + + 4 + + + Qt::Orientation::Horizontal + + + + + + + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 255 + + + 8 + + + + + + + 255 + + + 8 + + + + + + + 255 + + + 8 + + + + + + + + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + QFrame::Shadow::Raised + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + # + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + + + + + + + + ... + + + + :/icons/pipette.ico:/icons/pipette.ico + + + + + + + + + + + + + diff --git a/forms/customscriptseditor.ui b/forms/customscriptseditor.ui index cc7dd4f19..e2efa2af5 100644 --- a/forms/customscriptseditor.ui +++ b/forms/customscriptseditor.ui @@ -6,7 +6,7 @@ 0 0 - 540 + 582 355 @@ -33,21 +33,30 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised - QFrame::StyledPanel - - - QFrame::Raised + QFrame::Shape::StyledPanel + + 6 + + + 6 + + + 6 + + + 6 + @@ -90,10 +99,23 @@ + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + - Qt::Horizontal + Qt::Orientation::Horizontal @@ -103,35 +125,20 @@ + + + + ... + + + + :/icons/help.ico:/icons/help.ico + + + - - - - <html><head/><body><p><a href="https://huderlem.github.io/porymap/manual/scripting-capabilities.html"><span style=" text-decoration: underline; color:#0069d9;">Help</span></a></p></body></html> - - - true - - - - - - - Qt::Vertical - - - QSizePolicy::Minimum - - - - 20 - 5 - - - - @@ -142,32 +149,32 @@ - QAbstractItemView::NoEditTriggers + QAbstractItemView::EditTrigger::NoEditTriggers false - QAbstractItemView::DragOnly + QAbstractItemView::DragDropMode::DragOnly - Qt::IgnoreAction + Qt::DropAction::IgnoreAction - QAbstractItemView::ExtendedSelection + QAbstractItemView::SelectionMode::ExtendedSelection - Qt::ElideLeft + Qt::TextElideMode::ElideLeft - QListView::Free + QListView::Movement::Free - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/forms/gridsettingsdialog.ui b/forms/gridsettingsdialog.ui new file mode 100644 index 000000000..75d31de16 --- /dev/null +++ b/forms/gridsettingsdialog.ui @@ -0,0 +1,275 @@ + + + GridSettingsDialog + + + + 0 + 0 + 331 + 467 + + + + Grid Settings + + + + + + true + + + + + 0 + 0 + 305 + 401 + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + Color + + + + + + + false + + + QComboBox::SizeAdjustPolicy::AdjustToContentsOnFirstShow + + + 0 + + + + + + + + 0 + 0 + + + + Style + + + + + + + Dimensions (in pixels) + + + + + + ... + + + + :/icons/link_broken.ico + :/icons/link.ico:/icons/link_broken.ico + + + true + + + true + + + QToolButton::ToolButtonPopupMode::InstantPopup + + + true + + + + + + + 2 + + + 999 + + + + + + + Height + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + + Width + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + + 2 + + + 999 + + + + + + + + + + Offset (in pixels) + + + + + + Y + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + + X + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + + 0 + + + 999 + + + + + + + 0 + + + 999 + + + + + + + ... + + + + :/icons/link_broken.ico + :/icons/link.ico:/icons/link_broken.ico + + + true + + + true + + + QToolButton::ToolButtonPopupMode::InstantPopup + + + true + + + + + + + + + + + + + + + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok|QDialogButtonBox::StandardButton::RestoreDefaults + + + + + + + + NoScrollSpinBox + QSpinBox +
noscrollspinbox.h
+
+ + NoScrollComboBox + QComboBox +
noscrollcombobox.h
+
+ + ColorInputWidget + QGroupBox +
colorinputwidget.h
+ 1 +
+
+ + + + +
diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index f72536156..6a373edc1 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -6,8 +6,8 @@ 0 0 - 1287 - 903 + 1298 + 963
@@ -30,9 +30,9 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - + true @@ -42,168 +42,162 @@ 0 - - - 0 - - - 0 - - - 0 - - - 3 - - - 0 - - - - - 0 - - - 3 - - - 3 - - - 3 - - - - - true - - - <html><head/><body><p>Sort map list</p></body></html> - - - - :/icons/sort_alphabet.ico:/icons/sort_alphabet.ico - - - - 16 - 16 - - - - QToolButton::InstantPopup - - - Qt::ToolButtonIconOnly - - - true - - - Qt::NoArrow - - - - - - - <html><head/><body><p>Expand all map folders</p></body></html> - - - - - - - :/icons/expand_all.ico:/icons/expand_all.ico - - - QToolButton::InstantPopup - - - true - - - - - - - <html><head/><body><p>Collapse all map list folders</p></body></html> - - - - - - - :/icons/collapse_all.ico:/icons/collapse_all.ico - - - QToolButton::InstantPopup - - - true - - - - - - - Qt::Horizontal - - - QSizePolicy::Preferred - - - - 12 - 20 - - - - - - - - true - - - - - - Filter maps... - - - true - - - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectItems - - - false - - - - + + 0 + + + + Groups + + + + 0 + + + 0 + + + 0 + + + 3 + + + 0 + + + + + + + + + 0 + 0 + + + + + 200 + 0 + + + + QAbstractItemView::SelectionMode::SingleSelection + + + QAbstractItemView::SelectionBehavior::SelectItems + + + false + + + + + + + + Areas + + + + 0 + + + 0 + + + 0 + + + 3 + + + 0 + + + + + + + + + 0 + 0 + + + + + 200 + 0 + + + + QAbstractItemView::SelectionMode::SingleSelection + + + QAbstractItemView::SelectionBehavior::SelectItems + + + false + + + + + + + + Layouts + + + + 0 + + + 0 + + + 0 + + + 3 + + + 0 + + + + + + + + + 0 + 0 + + + + + 200 + 0 + + + + QAbstractItemView::SelectionMode::SingleSelection + + + QAbstractItemView::SelectionBehavior::SelectItems + + + false + + + false + + + + +
@@ -236,7 +230,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -288,7 +282,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -298,10 +292,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised 1 @@ -325,10 +319,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -377,10 +371,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -401,10 +395,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised @@ -585,7 +579,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -608,10 +602,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -658,6 +652,9 @@ Metatiles + + QLayout::SizeConstraint::SetNoConstraint + 3 @@ -685,17 +682,17 @@ 30 - Qt::Horizontal + Qt::Orientation::Horizontal - + - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -719,7 +716,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -756,10 +753,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain true @@ -769,7 +766,7 @@ 0 0 - 423 + 424 79 @@ -792,7 +789,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -814,17 +811,17 @@ <html><head/><body><p>The border is a 2x2 metatile which is repeated outside of the map layout's boundary. Draw on this border area to modify it.</p></body></html> - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::Horizontal + Qt::Orientation::Horizontal @@ -875,10 +872,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain true @@ -888,7 +885,7 @@ 0 0 - 423 + 424 79 @@ -911,7 +908,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -936,17 +933,17 @@ - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff
- Qt::Horizontal + Qt::Orientation::Horizontal @@ -970,19 +967,19 @@ - Qt::ScrollBarAlwaysOn + Qt::ScrollBarPolicy::ScrollBarAlwaysOn - Qt::ScrollBarAsNeeded + Qt::ScrollBarPolicy::ScrollBarAsNeeded - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored true - Qt::AlignHCenter|Qt::AlignTop + Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop @@ -992,7 +989,7 @@ 8 0 - 411 + 412 446 @@ -1021,7 +1018,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1043,20 +1040,20 @@ - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1069,7 +1066,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1087,7 +1084,7 @@
- + @@ -1096,23 +1093,23 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised - + Primary Tileset - + - Qt::StrongFocus + Qt::FocusPolicy::StrongFocus <html><head/><body><p>Primary Tileset</p><p>Defines the first 0x200 metatiles available for the map.</p></body></html> @@ -1122,17 +1119,17 @@ - + Secondary Tileset - + - Qt::StrongFocus + Qt::FocusPolicy::StrongFocus <html><head/><body><p>Secondary Tileset</p><p>Defines the second 0x200 metatiles available for the map.</p></body></html> @@ -1142,6 +1139,34 @@ + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Raised + + + + 2 + + + 2 + + + + + Layout + + + + + + + + + @@ -1162,7 +1187,7 @@ - QLayout::SetDefaultConstraint + QLayout::SizeConstraint::SetDefaultConstraint 3 @@ -1193,8 +1218,8 @@ 0 0 - 427 - 594 + 428 + 633 @@ -1216,7 +1241,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1241,17 +1266,17 @@ - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1264,7 +1289,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1290,7 +1315,7 @@ 30 - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1306,17 +1331,17 @@ 50 - Qt::Horizontal + Qt::Orientation::Horizontal - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -1342,7 +1367,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1403,7 +1428,7 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true @@ -1413,8 +1438,8 @@ 0 0 - 382 - 699 + 383 + 744 @@ -1436,10 +1461,10 @@ <html><head/><body><p>No prefabs have been created for the currently-used tilesets. Create some by using the button above!</p><p>Prefabs are &quot;prefabricated&quot; metatile selections that are used for easy selecting of complicated map structures. For example, a useful prefab could be a building or tree formation, which would otherwise be annoying to paint with the regular metatile picker.</p></body></html> - Qt::RichText + Qt::TextFormat::RichText - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop true @@ -1512,10 +1537,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised 0 @@ -1552,7 +1577,7 @@ :/icons/add.ico:/icons/add.ico - Qt::ToolButtonTextBesideIcon + Qt::ToolButtonStyle::ToolButtonTextBesideIcon @@ -1575,7 +1600,7 @@ :/icons/delete.ico:/icons/delete.ico - Qt::ToolButtonTextBesideIcon + Qt::ToolButtonStyle::ToolButtonTextBesideIcon false @@ -1585,7 +1610,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1620,7 +1645,7 @@ There are no events on the current map. - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -1686,7 +1711,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1701,13 +1726,13 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop @@ -1715,7 +1740,7 @@ 0 0 100 - 30 + 16 @@ -1780,7 +1805,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1795,13 +1820,13 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop @@ -1809,7 +1834,7 @@ 0 0 100 - 30 + 16 @@ -1874,7 +1899,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1889,13 +1914,13 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop @@ -1903,7 +1928,7 @@ 0 0 100 - 30 + 16 @@ -1974,7 +1999,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1989,13 +2014,13 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop @@ -2003,7 +2028,7 @@ 0 0 100 - 30 + 16 @@ -2068,7 +2093,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2083,13 +2108,13 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop @@ -2097,7 +2122,7 @@ 0 0 100 - 30 + 16 @@ -2137,13 +2162,13 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop @@ -2206,14 +2231,14 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised - QFormLayout::FieldsStayAtSizeHint + QFormLayout::FieldGrowthPolicy::FieldsStayAtSizeHint 12 @@ -2417,10 +2442,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -2433,10 +2458,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -2468,7 +2493,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2549,10 +2574,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -2563,17 +2588,11 @@ 0 - - - 0 - 32 - - - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -2638,7 +2657,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2723,7 +2742,7 @@ 30 - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2739,7 +2758,7 @@ 30 - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2771,7 +2790,7 @@ 30 - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2802,7 +2821,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2818,22 +2837,22 @@ false - Qt::ScrollBarAsNeeded + Qt::ScrollBarPolicy::ScrollBarAsNeeded - Qt::ScrollBarAsNeeded + Qt::ScrollBarPolicy::ScrollBarAsNeeded - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored - QGraphicsView::NoDrag + QGraphicsView::DragMode::NoDrag - QGraphicsView::AnchorUnderMouse + QGraphicsView::ViewportAnchor::AnchorUnderMouse - QGraphicsView::AnchorUnderMouse + QGraphicsView::ViewportAnchor::AnchorUnderMouse @@ -2844,10 +2863,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -2864,10 +2883,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff true @@ -2877,8 +2896,8 @@ 0 0 - 365 - 651 + 204 + 16 @@ -2900,7 +2919,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -2928,19 +2947,19 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -2953,7 +2972,7 @@ - QComboBox::AdjustToContents + QComboBox::SizeAdjustPolicy::AdjustToContents @@ -2994,7 +3013,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -3009,6 +3028,10 @@ Summary Chart... + + + :/icons/chart_bar.ico:/icons/chart_bar.ico + @@ -3046,8 +3069,8 @@ 0 0 - 1287 - 22 + 1298 + 37 @@ -3083,10 +3106,13 @@ + + + @@ -3318,7 +3344,7 @@ true - Player View Rectangle + Show Player View Rectangle <html><head/><body><p>Show the player's view rectangle on the map based on the cursor's position.</p></body></html> @@ -3335,7 +3361,7 @@ true - Cursor Tile Outline + Show Cursor Tile Outline C @@ -3415,7 +3441,7 @@ Check for Updates... - QAction::ApplicationSpecificRole + QAction::MenuRole::ApplicationSpecificRole @@ -3434,7 +3460,26 @@ true - Dive/Emerge Map + Show Dive/Emerge Map + + + + + true + + + true + + + Show Grid + + + Ctrl+G + + + + + Grid Settings... @@ -3466,11 +3511,22 @@ QWidget
mapview.h
+ + MapTree + QTreeView +
maplistmodels.h
+
NoScrollGraphicsView QGraphicsView
mapview.h
+ + MapListToolBar + QFrame +
maplisttoolbar.h
+ 1 +
diff --git a/forms/mapimageexporter.ui b/forms/mapimageexporter.ui index d97099a72..933aab67d 100644 --- a/forms/mapimageexporter.ui +++ b/forms/mapimageexporter.ui @@ -6,8 +6,8 @@ 0 0 - 696 - 396 + 817 + 535
@@ -16,15 +16,313 @@ true - - - + + + + + + 0 + 0 + + + + + + + + + Map + + + + + + + QComboBox::SizeAdjustPolicy::AdjustToContents + + + + + + + + + Events + + + + + + + + Triggers + + + + + + + Objects + + + + + + + Heal Locations + + + + + + + Warps + + + + + + + All + + + + + + + BGs + + + + + + + + + + + + Connections + + + + + + + + Left + + + + + + + Up + + + + + + + Right + + + + + + + Down + + + + + + + All + + + + + + + + + + + + Miscellaneous + + + + + + + + Grid + + + + + + + Collision + + + + + + + Border + + + + + + + + + + + + Timelapse + + + + + + + + + ms + + + 1 + + + 2000 + + + 200 + + + + + + + Frame Delay + + + + + + + + + + 1 + + + 999 + + + + + + + Edit Frame Skip + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + Preview actual size + + + + + + + + + Reset + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + Cancel + + + + + + + Save + + + + + + + + + + + + + 0 + 0 + + Preview + + 6 + + + 6 + + + 6 + + + 6 + - + true @@ -33,12 +331,24 @@ 0 0 - 403 - 343 + 469 + 464 - + + 0 + + + 0 + + + 0 + + + 0 + + @@ -53,65 +363,13 @@ false - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored - QGraphicsView::NoDrag + QGraphicsView::DragMode::NoDrag - - - - Qt::Vertical - - - - 10 - 100 - - - - - - - - Qt::Vertical - - - - 10 - 100 - - - - - - - - Qt::Horizontal - - - - 100 - 10 - - - - - - - - Qt::Horizontal - - - - 100 - 10 - - - -
@@ -119,256 +377,18 @@
- - - - - - - - Map - - - - - - - QComboBox::AdjustToContents - - - - - - - - - Events - - - - - - - - Warps - - - - - - - Objects - - - - - - - BGs - - - - - - - Triggers - - - - - - - Heal Spots - - - - - - - - - - - - Connections - - - - - - - - Up - - - - - - - Down - - - - - - - Left - - - - - - - Right - - - - - - - - - - - - Miscellaneous - - - - - - - - Grid - - - - - - - Collision - - - - - - - Border - - - - - - - - - - - - Timelapse - - - - - - - - - ms - - - 1 - - - 2000 - - - 200 - - - - - - - Frame Delay - - - - - - - - - - 1 - - - 999 - - - - - - - Edit Frame Skip - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - Reset - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Cancel - - - - - - - Save - - - - - - + + + + + + + Qt::AlignmentFlag::AlignCenter + + + true + + diff --git a/forms/maplisttoolbar.ui b/forms/maplisttoolbar.ui new file mode 100644 index 000000000..54eb48d08 --- /dev/null +++ b/forms/maplisttoolbar.ui @@ -0,0 +1,175 @@ + + + MapListToolBar + + + + 0 + 0 + 274 + 32 + + + + Form + + + + 0 + + + 3 + + + 3 + + + 0 + + + 3 + + + + + Add a new folder to the list. + + + + + + + :/icons/folder_add.ico:/icons/folder_add.ico + + + QToolButton::ToolButtonPopupMode::InstantPopup + + + true + + + + + + + Hide empty folders in the list. + + + + :/icons/folder_eye_open.ico + :/icons/folder_eye_closed.ico:/icons/folder_eye_open.ico + + + true + + + QToolButton::ToolButtonPopupMode::InstantPopup + + + true + + + + + + + Expand all folders in the list. + + + + + + + :/icons/expand_all.ico:/icons/expand_all.ico + + + QToolButton::ToolButtonPopupMode::InstantPopup + + + true + + + + + + + Collapse all folders in the list. + + + + + + + :/icons/collapse_all.ico:/icons/collapse_all.ico + + + QToolButton::ToolButtonPopupMode::InstantPopup + + + true + + + + + + + If enabled, folders may be renamed and items in the list may be rearranged. + + + + + + + :/icons/lock_edit.ico + :/icons/unlock_edit.ico:/icons/lock_edit.ico + + + true + + + QToolButton::ToolButtonPopupMode::InstantPopup + + + true + + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Preferred + + + + 12 + 19 + + + + + + + + true + + + + + + Filter... + + + true + + + + + + + + + + diff --git a/forms/newmappopup.ui b/forms/newmappopup.ui index b102b1ccc..3a83073f8 100644 --- a/forms/newmappopup.ui +++ b/forms/newmappopup.ui @@ -7,7 +7,7 @@ 0 0 410 - 621 + 687 @@ -73,14 +73,14 @@ - + Map Width - + <html><head/><body><p>Width (in blocks) of the new map.</p></body></html> @@ -90,14 +90,14 @@ - + Map Height - + <html><head/><body><p>Height (in blocks) of the new map.</p></body></html> @@ -107,14 +107,14 @@ - + Border Width - + <html><head/><body><p>Width (in blocks) of the new map's border.</p></body></html> @@ -124,14 +124,14 @@ - + Border Height - + <html><head/><body><p>Height (in blocks) of the new map's border.</p></body></html> @@ -141,14 +141,14 @@ - + Primary Tileset - + <html><head/><body><p>The primary tileset for the new map.</p></body></html> @@ -158,14 +158,14 @@ - + Secondary Tileset - + <html><head/><body><p>The secondary tileset for the new map.</p></body></html> @@ -175,14 +175,14 @@ - + Type - + <html><head/><body><p>The map type is a general attribute, which is used for many different things. For example. it determines whether biking or running is allowed.</p></body></html> @@ -192,14 +192,14 @@ - + Location - + <html><head/><body><p>The section of the region map which the map is grouped under. This also determines the name of the map that is displayed when the player enters it.</p></body></html> @@ -209,14 +209,14 @@ - + Song - + <html><head/><body><p>The default background music for this map.</p></body></html> @@ -226,14 +226,14 @@ - + Can Fly To - + <html><head/><body><p>Whether to add a heal location to the new map.</p></body></html> @@ -243,14 +243,14 @@ - + Show Location Name - + <html><head/><body><p>Whether or not to display the location name when the player enters the map.</p></body></html> @@ -260,14 +260,14 @@ - + Allow Running - + <html><head/><body><p>Allows the player to use Running Shoes</p></body></html> @@ -277,14 +277,14 @@ - + Allow Biking - + <html><head/><body><p>Allows the player to use a Bike</p></body></html> @@ -294,14 +294,14 @@ - + Allow Dig & Escape Rope - + <html><head/><body><p>Allows the player to use Dig or Escape Rope</p></body></html> @@ -311,14 +311,14 @@ - + Floor Number - + <html><head/><body><p>Floor number to be used for maps with elevators.</p></body></html> @@ -328,6 +328,96 @@ + + + + false + + + + + + + Layout + + + + + + + + + Use Existing Layout + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -396,7 +486,7 @@ 0 0 410 - 21 + 22 diff --git a/forms/paletteeditor.ui b/forms/paletteeditor.ui index 1cd7c9592..c1a4ee7aa 100644 --- a/forms/paletteeditor.ui +++ b/forms/paletteeditor.ui @@ -7,7 +7,7 @@ 0 0 907 - 886 + 933 @@ -15,5039 +15,13 @@ - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Color 0 - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Red - - - - - - - Green - - - - - - - Blue - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - # - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - ... - - - - :/icons/pipette.ico:/icons/pipette.ico - - - - - - - - - - - - - - - - Color 1 - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Red - - - - - - - Green - - - - - - - Blue - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - # - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - ... - - - - :/icons/pipette.ico:/icons/pipette.ico - - - - - - - - - - - - - - - - Color 2 - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Red - - - - - - - Green - - - - - - - Blue - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - # - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - ... - - - - :/icons/pipette.ico:/icons/pipette.ico - - - - - - - - - - - - - - - - Color 3 - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Red - - - - - - - Green - - - - - - - Blue - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - # - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - ... - - - - :/icons/pipette.ico:/icons/pipette.ico - - - - - - - - - - - - - - - - Color 4 - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Red - - - - - - - Green - - - - - - - Blue - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - # - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - ... - - - - :/icons/pipette.ico:/icons/pipette.ico - - - - - - - - - - - - - - - - Color 5 - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Red - - - - - - - Green - - - - - - - Blue - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - # - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - ... - - - - :/icons/pipette.ico:/icons/pipette.ico - - - - - - - - - - - - - - - - Color 6 - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Red - - - - - - - Green - - - - - - - Blue - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - # - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - ... - - - - :/icons/pipette.ico:/icons/pipette.ico - - - - - - - - - - - - - - - - Color 7 - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Red - - - - - - - Green - - - - - - - Blue - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - # - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - ... - - - - :/icons/pipette.ico:/icons/pipette.ico - - - - - - - - - - - - - - - - Color 8 - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Red - - - - - - - Green - - - - - - - Blue - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - # - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - ... - - - - :/icons/pipette.ico:/icons/pipette.ico - - - - - - - - - - - - - - - - Color 9 - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Red - - - - - - - Green - - - - - - - Blue - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - # - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - ... - - - - :/icons/pipette.ico:/icons/pipette.ico - - - - - - - - - - - - - - - - Color 10 - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Red - - - - - - - Green - - - - - - - Blue - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - # - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - ... - - - - :/icons/pipette.ico:/icons/pipette.ico - - - - - - - - - - - - - - - - Color 11 - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Red - - - - - - - Green - - - - - - - Blue - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - # - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - ... - - - - :/icons/pipette.ico:/icons/pipette.ico - - - - - - - - - - - - - - - - Color 12 - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Red - - - - - - - Green - - - - - - - Blue - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - # - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - ... - - - - :/icons/pipette.ico:/icons/pipette.ico - - - - - - - - - - - - - - - - Color 13 - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Red - - - - - - - Green - - - - - - - Blue - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - # - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - ... - - - - :/icons/pipette.ico:/icons/pipette.ico - - - - - - - - - - - - - - - - Color 14 - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Red - - - - - - - Green - - - - - - - Blue - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - # - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - ... - - - - :/icons/pipette.ico:/icons/pipette.ico - - - - - - - - - - - - - - - - Color 15 - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Red - - - - - - - Green - - - - - - - Blue - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - 31 - - - 4 - - - Qt::Horizontal - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - 255 - - - 8 - - - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - # - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - ... - - - - :/icons/pipette.ico:/icons/pipette.ico - - - - - - - - - - - - - - - - + - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised @@ -5060,14 +34,14 @@ - Qt::StrongFocus + Qt::FocusPolicy::StrongFocus - Qt::Horizontal + Qt::Orientation::Horizontal @@ -5078,7 +52,7 @@ - + Bit Depth: @@ -5101,6 +75,40 @@ + + + + QFrame::Shape::NoFrame + + + true + + + + + 0 + 0 + 883 + 784 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + @@ -5109,7 +117,7 @@ 0 0 907 - 22 + 37 @@ -5137,10 +145,10 @@ Ctrl+Z - Qt::WindowShortcut + Qt::ShortcutContext::WindowShortcut - QAction::NormalPriority + QAction::Priority::NormalPriority @@ -5157,8 +165,6 @@ - - - + diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index dc6d730ef..9aa521a84 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -125,7 +125,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -194,10 +194,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Maximum + QSizePolicy::Policy::Maximum @@ -276,10 +276,10 @@ .QFrame { border: 1px solid red; } - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -287,7 +287,6 @@ 12 - 75 true @@ -319,7 +318,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -338,7 +337,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -567,10 +566,10 @@ .QFrame { border: 1px solid red; } - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -578,7 +577,6 @@ 12 - 75 true @@ -693,7 +691,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -712,7 +710,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -783,10 +781,10 @@ .QFrame { border: 1px solid red; } - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -794,7 +792,6 @@ 12 - 75 true @@ -839,10 +836,10 @@ - Qt::Vertical + Qt::Orientation::Vertical - QSizePolicy::Maximum + QSizePolicy::Policy::Maximum @@ -924,10 +921,10 @@ - Qt::Vertical + Qt::Orientation::Vertical - QSizePolicy::MinimumExpanding + QSizePolicy::Policy::MinimumExpanding @@ -975,7 +972,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1212,7 +1209,7 @@ true - Qt::NoTextInteraction + Qt::TextInteractionFlag::NoTextInteraction Use the dropbown and buttons to add behaviors to the list... @@ -1242,10 +1239,10 @@ .QFrame { border: 1px solid red; } - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -1253,7 +1250,6 @@ 12 - 75 true @@ -1316,7 +1312,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1335,7 +1331,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1372,18 +1368,13 @@ - + - <html><head/><body><p><a href="https://huderlem.github.io/porymap/manual/project-files.html#files"><span style=" text-decoration: underline; color:#0069d9;">Help</span></a></p></body></html> + ... - - Qt::RichText - - - Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - - - true + + + :/icons/help.ico:/icons/help.ico @@ -1419,7 +1410,7 @@ 0 0 533 - 440 + 428 @@ -1466,18 +1457,13 @@ - + - <html><head/><body><p><a href="https://huderlem.github.io/porymap/manual/project-files.html#identifiers"><span style=" text-decoration: underline; color:#0069d9;">Help</span></a></p></body></html> - - - Qt::RichText - - - Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + ... - - true + + + :/icons/help.ico:/icons/help.ico @@ -1513,7 +1499,7 @@ 0 0 533 - 440 + 428 @@ -1544,7 +1530,7 @@ - QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok|QDialogButtonBox::StandardButton::RestoreDefaults diff --git a/include/config.h b/include/config.h index b42bd4f8d..6b64b612c 100644 --- a/include/config.h +++ b/include/config.h @@ -22,12 +22,6 @@ static const QVersionNumber porymapVersion = QVersionNumber::fromString(PORYMAP_ #define CONFIG_BACKWARDS_COMPATABILITY -enum MapSortOrder { - Group = 0, - Area = 1, - Layout = 2, -}; - class KeyValueConfigBase { public: @@ -56,7 +50,7 @@ class PorymapConfig: public KeyValueConfigBase this->recentProjects.clear(); this->projectManuallyClosed = false; this->reopenOnLaunch = true; - this->mapSortOrder = MapSortOrder::Group; + this->mapListTab = 0; this->prettyCursors = true; this->mirrorConnectingMaps = true; this->showDiveEmergeMaps = false; @@ -107,7 +101,7 @@ class PorymapConfig: public KeyValueConfigBase bool reopenOnLaunch; bool projectManuallyClosed; - MapSortOrder mapSortOrder; + int mapListTab; bool prettyCursors; bool mirrorConnectingMaps; bool showDiveEmergeMaps; @@ -219,7 +213,6 @@ enum ProjectIdentifier { define_map_empty, define_map_section_prefix, define_map_section_empty, - define_map_section_count, define_species_prefix, regex_behaviors, regex_obj_event_gfx, @@ -275,7 +268,6 @@ enum ProjectFilePath { constants_obj_event_movement, constants_obj_events, constants_event_bg, - constants_region_map_sections, constants_metatile_labels, constants_metatile_behaviors, constants_species, @@ -406,7 +398,7 @@ class UserConfig: public KeyValueConfigBase reset(); } virtual void reset() override { - this->recentMap = QString(); + this->recentMapOrLayout = QString(); this->useEncounterJson = true; this->customScripts.clear(); this->readKeys.clear(); @@ -418,7 +410,7 @@ class UserConfig: public KeyValueConfigBase QList getCustomScriptsEnabled(); QString projectDir; - QString recentMap; + QString recentMapOrLayout; bool useEncounterJson; protected: diff --git a/include/core/editcommands.h b/include/core/editcommands.h index 40f0cd62e..5bf99784f 100644 --- a/include/core/editcommands.h +++ b/include/core/editcommands.h @@ -9,8 +9,8 @@ #include #include -class MapPixmapItem; class Map; +class Layout; class Blockdata; class Event; class DraggablePixmapItem; @@ -24,9 +24,9 @@ enum CommandId { ID_PaintCollision, ID_BucketFillCollision, ID_MagicFillCollision, - ID_ResizeMap, + ID_ResizeLayout, ID_PaintBorder, - ID_ScriptEditMap, + ID_ScriptEditLayout, ID_EventMove, ID_EventShift, ID_EventCreate, @@ -50,7 +50,7 @@ enum CommandId { /// onto the map using the pencil tool. class PaintMetatile : public QUndoCommand { public: - PaintMetatile(Map *map, + PaintMetatile(Layout *layout, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, unsigned actionId, QUndoCommand *parent = nullptr); @@ -61,7 +61,7 @@ class PaintMetatile : public QUndoCommand { int id() const override { return CommandId::ID_PaintMetatile; } private: - Map *map; + Layout *layout; Blockdata newMetatiles; Blockdata oldMetatiles; @@ -75,10 +75,10 @@ class PaintMetatile : public QUndoCommand { /// on the metatile collision and elevation. class PaintCollision : public PaintMetatile { public: - PaintCollision(Map *map, + PaintCollision(Layout *layout, const Blockdata &oldCollision, const Blockdata &newCollision, unsigned actionId, QUndoCommand *parent = nullptr) - : PaintMetatile(map, oldCollision, newCollision, actionId, parent) { + : PaintMetatile(layout, oldCollision, newCollision, actionId, parent) { setText("Paint Collision"); } @@ -90,7 +90,7 @@ class PaintCollision : public PaintMetatile { /// Implements a command to commit paint actions on the map border. class PaintBorder : public QUndoCommand { public: - PaintBorder(Map *map, + PaintBorder(Layout *layout, const Blockdata &oldBorder, const Blockdata &newBorder, unsigned actionId, QUndoCommand *parent = nullptr); @@ -101,7 +101,7 @@ class PaintBorder : public QUndoCommand { int id() const override { return CommandId::ID_PaintBorder; } private: - Map *map; + Layout *layout; Blockdata newBorder; Blockdata oldBorder; @@ -115,10 +115,10 @@ class PaintBorder : public QUndoCommand { /// with the bucket tool onto the map. class BucketFillMetatile : public PaintMetatile { public: - BucketFillMetatile(Map *map, + BucketFillMetatile(Layout *layout, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, unsigned actionId, QUndoCommand *parent = nullptr) - : PaintMetatile(map, oldMetatiles, newMetatiles, actionId, parent) { + : PaintMetatile(layout, oldMetatiles, newMetatiles, actionId, parent) { setText("Bucket Fill Metatiles"); } @@ -131,10 +131,10 @@ class BucketFillMetatile : public PaintMetatile { /// on the metatile collision and elevation. class BucketFillCollision : public PaintCollision { public: - BucketFillCollision(Map *map, + BucketFillCollision(Layout *layout, const Blockdata &oldCollision, const Blockdata &newCollision, QUndoCommand *parent = nullptr) - : PaintCollision(map, oldCollision, newCollision, -1, parent) { + : PaintCollision(layout, oldCollision, newCollision, -1, parent) { setText("Flood Fill Collision"); } @@ -148,10 +148,10 @@ class BucketFillCollision : public PaintCollision { /// with the bucket or paint tool onto the map. class MagicFillMetatile : public PaintMetatile { public: - MagicFillMetatile(Map *map, + MagicFillMetatile(Layout *layout, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, unsigned actionId, QUndoCommand *parent = nullptr) - : PaintMetatile(map, oldMetatiles, newMetatiles, actionId, parent) { + : PaintMetatile(layout, oldMetatiles, newMetatiles, actionId, parent) { setText("Magic Fill Metatiles"); } @@ -163,10 +163,10 @@ class MagicFillMetatile : public PaintMetatile { /// Implements a command to commit magic fill collision actions. class MagicFillCollision : public PaintCollision { public: - MagicFillCollision(Map *map, + MagicFillCollision(Layout *layout, const Blockdata &oldCollision, const Blockdata &newCollision, QUndoCommand *parent = nullptr) - : PaintCollision(map, oldCollision, newCollision, -1, parent) { + : PaintCollision(layout, oldCollision, newCollision, -1, parent) { setText("Magic Fill Collision"); } @@ -179,7 +179,7 @@ class MagicFillCollision : public PaintCollision { /// Implements a command to commit metatile shift actions. class ShiftMetatiles : public QUndoCommand { public: - ShiftMetatiles(Map *map, + ShiftMetatiles(Layout *layout, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, unsigned actionId, QUndoCommand *parent = nullptr); @@ -190,7 +190,7 @@ class ShiftMetatiles : public QUndoCommand { int id() const override { return CommandId::ID_ShiftMetatiles; } private: - Map *map; + Layout *layout= nullptr; Blockdata newMetatiles; Blockdata oldMetatiles; @@ -201,9 +201,9 @@ class ShiftMetatiles : public QUndoCommand { /// Implements a command to commit a map or border resize action. -class ResizeMap : public QUndoCommand { +class ResizeLayout : public QUndoCommand { public: - ResizeMap(Map *map, QSize oldMapDimensions, QSize newMapDimensions, + ResizeLayout(Layout *layout, QSize oldLayoutDimensions, QSize newLayoutDimensions, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, QSize oldBorderDimensions, QSize newBorderDimensions, const Blockdata &oldBorder, const Blockdata &newBorder, @@ -213,15 +213,15 @@ class ResizeMap : public QUndoCommand { void redo() override; bool mergeWith(const QUndoCommand *) override { return false; } - int id() const override { return CommandId::ID_ResizeMap; } + int id() const override { return CommandId::ID_ResizeLayout; } private: - Map *map; + Layout *layout = nullptr; - int oldMapWidth; - int oldMapHeight; - int newMapWidth; - int newMapHeight; + int oldLayoutWidth; + int oldLayoutHeight; + int newLayoutWidth; + int newLayoutHeight; int oldBorderWidth; int oldBorderHeight; @@ -351,10 +351,10 @@ class EventPaste : public EventDuplicate { /// Implements a command to commit map edits from the scripting API. /// The scripting api can edit map/border blocks and dimensions. -class ScriptEditMap : public QUndoCommand { +class ScriptEditLayout : public QUndoCommand { public: - ScriptEditMap(Map *map, - QSize oldMapDimensions, QSize newMapDimensions, + ScriptEditLayout(Layout *layout, + QSize oldLayoutDimensions, QSize newLayoutDimensions, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, QSize oldBorderDimensions, QSize newBorderDimensions, const Blockdata &oldBorder, const Blockdata &newBorder, @@ -364,10 +364,10 @@ class ScriptEditMap : public QUndoCommand { void redo() override; bool mergeWith(const QUndoCommand *) override { return false; } - int id() const override { return CommandId::ID_ScriptEditMap; } + int id() const override { return CommandId::ID_ScriptEditLayout; } private: - Map *map; + Layout *layout = nullptr; Blockdata newMetatiles; Blockdata oldMetatiles; @@ -375,10 +375,10 @@ class ScriptEditMap : public QUndoCommand { Blockdata newBorder; Blockdata oldBorder; - int oldMapWidth; - int oldMapHeight; - int newMapWidth; - int newMapHeight; + int oldLayoutWidth; + int oldLayoutHeight; + int newLayoutWidth; + int newLayoutHeight; int oldBorderWidth; int oldBorderHeight; diff --git a/include/core/events.h b/include/core/events.h index 2e00ef72c..c0a5a6250 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -187,6 +187,7 @@ class Event { static QString eventGroupToString(Event::Group group); static QString eventTypeToString(Event::Type type); static Event::Type eventTypeFromString(QString type); + static void clearIcons(); static void setIcons(); // protected attributes diff --git a/include/core/filedialog.h b/include/core/filedialog.h new file mode 100644 index 000000000..a02f6193f --- /dev/null +++ b/include/core/filedialog.h @@ -0,0 +1,65 @@ +#ifndef FILEDIALOG_H +#define FILEDIALOG_H + +#include + +/* + Static QFileDialog functions will (unless otherwise specified) use native file dialogs. + In general this is good (we want our file dialogs to be visually seamless) but unfortunately + the native file dialogs ignore the parent widget, so in some cases they'll return focus to + the main window rather than the window that opened the file dialog. + + To make working around this a little easier we use this class, which will use the native + file dialog and manually return focus to the parent widget. + + It will also save the directory of the previous file selected in a file dialog, and if + no 'dir' argument is specified it will open new dialogs at that directory. + +*/ + +class FileDialog : public QFileDialog +{ +public: + FileDialog(QWidget *parent, Qt::WindowFlags flags) : QFileDialog(parent, flags) {}; + FileDialog(QWidget *parent = nullptr, + const QString &caption = QString(), + const QString &directory = QString(), + const QString &filter = QString()) : QFileDialog(parent, caption, directory, filter) {}; + + static void setDirectory(const QString &dir) { FileDialog::prevDirectory = dir; } + static QString getDirectory() { return FileDialog::prevDirectory; } + + static QString getOpenFileName(QWidget *parent = nullptr, + const QString &caption = QString(), + const QString &dir = QString(), + const QString &filter = QString(), + QString *selectedFilter = nullptr, + QFileDialog::Options options = Options()); + + static QStringList getOpenFileNames(QWidget *parent = nullptr, + const QString &caption = QString(), + const QString &dir = QString(), + const QString &filter = QString(), + QString *selectedFilter = nullptr, + QFileDialog::Options options = Options()); + + static QString getExistingDirectory(QWidget *parent = nullptr, + const QString &caption = QString(), + const QString &dir = QString(), + QFileDialog::Options options = ShowDirsOnly); + + static QString getSaveFileName(QWidget *parent = nullptr, + const QString &caption = QString(), + const QString &dir = QString(), + const QString &filter = QString(), + QString *selectedFilter = nullptr, + QFileDialog::Options options = Options()); + +private: + static QString prevDirectory; + static QString getDirectoryFromInput(const QString &dir); + static void setDirectoryFromFile(const QString &fileName); + static void restoreFocus(QWidget *parent); +}; + +#endif // FILEDIALOG_H diff --git a/include/core/map.h b/include/core/map.h index 2333c862a..acc52d908 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -25,7 +25,7 @@ // porymap will reflect changes to it, but the value is hard-coded in the projects at the moment #define BORDER_DISTANCE 7 -class MapPixmapItem; +class LayoutPixmapItem; class CollisionPixmapItem; class BorderMetatilesPixmapItem; @@ -39,6 +39,7 @@ class Map : public QObject public: QString name; QString constantName; + QString song; QString layoutId; QString location; @@ -51,20 +52,22 @@ class Map : public QObject bool allowEscaping; int floorNumber = 0; QString battle_scene; + QString sharedEventsMap = ""; QString sharedScriptsMap = ""; + QStringList scriptsFileLabels; QMap customHeaders; - MapLayout *layout; + + Layout *layout = nullptr; + void setLayout(Layout *layout); + bool isPersistedToFile = true; bool hasUnsavedDataChanges = false; + bool needsLayoutDir = true; bool needsHealLocation = false; bool scriptsLoaded = false; - QImage collision_image; - QPixmap collision_pixmap; - QImage image; - QPixmap pixmap; QMap> events; QList ownedEvents; // for memory management @@ -74,64 +77,34 @@ class Map : public QObject void setName(QString mapName); static QString mapConstantFromName(QString mapName, bool includePrefix = true); + int getWidth(); int getHeight(); int getBorderWidth(); int getBorderHeight(); - QPixmap render(bool ignoreCache = false, MapLayout *fromLayout = nullptr, QRect bounds = QRect(0, 0, -1, -1)); - QPixmap renderCollision(bool ignoreCache); - bool mapBlockChanged(int i, const Blockdata &cache); - bool borderBlockChanged(int i, const Blockdata &cache); - void cacheBlockdata(); - void cacheCollision(); - bool getBlock(int x, int y, Block *out); - void setBlock(int x, int y, Block block, bool enableScriptCallback = false); - void setBlockdata(Blockdata blockdata, bool enableScriptCallback = false); - uint16_t getBorderMetatileId(int x, int y); - void setBorderMetatileId(int x, int y, uint16_t metatileId, bool enableScriptCallback = false); - void setBorderBlockData(Blockdata blockdata, bool enableScriptCallback = false); - void floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); - void _floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); - void magicFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); + QList getAllEvents() const; QStringList getScriptLabels(Event::Group group = Event::Group::None); + QString getScriptsFilePath() const; + void openScript(QString label); void removeEvent(Event *); void addEvent(Event *); + void deleteConnections(); QList 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); - void clearBorderCache(); - void cacheBorder(); - bool hasUnsavedChanges(); - bool isWithinBounds(int x, int y); - bool isWithinBorderBounds(int x, int y); - void openScript(QString label); - QString getScriptsFilePath() const; - - MapPixmapItem *mapItem = nullptr; - void setMapItem(MapPixmapItem *item) { mapItem = item; } - - CollisionPixmapItem *collisionItem = nullptr; - void setCollisionItem(CollisionPixmapItem *item) { collisionItem = item; } - - BorderMetatilesPixmapItem *borderItem = nullptr; - void setBorderItem(BorderMetatilesPixmapItem *item) { borderItem = item; } + QRect getConnectionRect(const QString &direction, Layout *fromLayout = nullptr); + QPixmap renderConnection(const QString &direction, Layout *fromLayout = nullptr); QUndoStack editHistory; void modify(); void clean(); + bool hasUnsavedChanges() const; 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. @@ -141,7 +114,6 @@ class Map : public QObject signals: void modified(); void mapDimensionsChanged(const QSize &size); - void mapNeedsRedrawing(); void openScriptRequested(QString label); void connectionAdded(MapConnection*); void connectionRemoved(MapConnection*); diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 13215fff0..b617002fa 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -7,41 +7,135 @@ #include #include #include +#include -class MapLayout { +class Map; +class LayoutPixmapItem; +class CollisionPixmapItem; +class BorderMetatilesPixmapItem; + +class Layout : public QObject { + Q_OBJECT public: - MapLayout() {} + Layout() {} + static QString layoutConstantFromName(QString mapName); + + bool loaded = false; + QString id; QString name; + int width; int height; int border_width; int border_height; + QString border_path; QString blockdata_path; + QString tileset_primary_label; QString tileset_secondary_label; + Tileset *tileset_primary = nullptr; Tileset *tileset_secondary = nullptr; + Blockdata blockdata; + + QImage image; + QPixmap pixmap; QImage border_image; QPixmap border_pixmap; + QImage collision_image; + QPixmap collision_pixmap; + Blockdata border; Blockdata cached_blockdata; Blockdata cached_collision; Blockdata cached_border; struct { Blockdata blocks; - QSize mapDimensions; + QSize layoutDimensions; Blockdata border; QSize borderDimensions; } lastCommitBlocks; // to track map changes + QList metatileLayerOrder; + QList metatileLayerOpacity; + + LayoutPixmapItem *layoutItem = nullptr; + CollisionPixmapItem *collisionItem = nullptr; + BorderMetatilesPixmapItem *borderItem = nullptr; + + QUndoStack editHistory; + + // to simplify new layout settings transfer between functions + struct SimpleSettings { + QString id; + QString name; + int width; + int height; + QString tileset_primary_label; + QString tileset_secondary_label; + QString from_id = QString(); + }; + +public: + Layout *copy(); + void copyFrom(Layout *other); + int getWidth(); int getHeight(); int getBorderWidth(); int getBorderHeight(); + + bool isWithinBounds(int x, int y); + bool isWithinBorderBounds(int x, int y); + + bool getBlock(int x, int y, Block *out); + void setBlock(int x, int y, Block block, bool enableScriptCallback = false); + void setBlockdata(Blockdata blockdata, bool enableScriptCallback = 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); + + void cacheBlockdata(); + void cacheCollision(); + void clearBorderCache(); + void cacheBorder(); + + bool hasUnsavedChanges() const; + + bool layoutBlockChanged(int i, const Blockdata &cache); + + uint16_t getBorderMetatileId(int x, int y); + void setBorderMetatileId(int x, int y, uint16_t metatileId, bool enableScriptCallback = false); + void setBorderBlockData(Blockdata blockdata, bool enableScriptCallback = false); + + void floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); + void _floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); + void magicFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); + + QPixmap render(bool ignoreCache = false, Layout *fromLayout = nullptr, QRect bounds = QRect(0, 0, -1, -1)); + QPixmap renderCollision(bool ignoreCache); + // QPixmap renderConnection(MapConnection, Layout *); + QPixmap renderBorder(bool ignoreCache = false); + + QPixmap getLayoutItemPixmap(); + + void setLayoutItem(LayoutPixmapItem *item) { layoutItem = item; } + void setCollisionItem(CollisionPixmapItem *item) { collisionItem = item; } + void setBorderItem(BorderMetatilesPixmapItem *item) { borderItem = item; } + +private: + void setNewDimensionsBlockdata(int newWidth, int newHeight); + void setNewBorderDimensionsBlockdata(int newWidth, int newHeight); + +signals: + void layoutChanged(Layout *layout); + //void modified(); + void layoutDimensionsChanged(const QSize &size); + void needsRedrawing(); }; #endif // MAPLAYOUT_H diff --git a/include/core/mapparser.h b/include/core/mapparser.h index 21e3074e4..4032154a5 100644 --- a/include/core/mapparser.h +++ b/include/core/mapparser.h @@ -10,7 +10,7 @@ class MapParser { public: MapParser(); - MapLayout *parse(QString filepath, bool *error, Project *project); + Layout *parse(QString filepath, bool *error, Project *project); }; #endif // MAPPARSER_H diff --git a/include/core/parseutil.h b/include/core/parseutil.h index bc167a303..e3df8375b 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -104,7 +104,8 @@ class ParseUtil }; ParsedDefines readCDefines(const QString &filename, const QStringList &filterList, bool useRegex); QMap evaluateCDefines(const QString &filename, const QStringList &filterList, bool useRegex); - bool defineNameMatchesFilter(const QString &name, const QStringList &filterList, bool useRegex); + bool defineNameMatchesFilter(const QString &name, const QStringList &filterList) const; + bool defineNameMatchesFilter(const QString &name, const QList &filterList) const; static const QRegularExpression re_incScriptLabel; static const QRegularExpression re_globalIncScriptLabel; diff --git a/include/core/regionmap.h b/include/core/regionmap.h index bc05a84f7..822df79b3 100644 --- a/include/core/regionmap.h +++ b/include/core/regionmap.h @@ -57,8 +57,8 @@ class RegionMap : public QObject bool loadLayout(poryjson::Json); bool loadEntries(); - void setEntries(tsl::ordered_map *entries) { this->region_map_entries = entries; } - void setEntries(tsl::ordered_map entries) { *(this->region_map_entries) = entries; } + void setEntries(QMap *entries) { this->region_map_entries = entries; } + void setEntries(const QMap &entries) { *(this->region_map_entries) = entries; } void clearEntries() { this->region_map_entries->clear(); } MapSectionEntry getEntry(QString section); void setEntry(QString section, MapSectionEntry entry); @@ -114,8 +114,6 @@ class RegionMap : public QObject void setLayer(QString layer) { this->current_layer = layer; } QString getLayer() { return this->current_layer; } - QString fixCase(QString); - int padLeft() { return this->offset_left; } int padTop() { return this->offset_top; } int padRight() { return this->tilemap_width - this->layout_width - this->offset_left; } @@ -149,14 +147,12 @@ class RegionMap : public QObject const QString section_prefix; const QString default_map_section; - const QString count_map_section; signals: void mapNeedsDisplaying(); private: - // TODO: defaults needed? - tsl::ordered_map *region_map_entries = nullptr; + QMap *region_map_entries = nullptr; QString alias = ""; diff --git a/include/core/regionmapeditcommands.h b/include/core/regionmapeditcommands.h index 69bea2510..05b12bc3b 100644 --- a/include/core/regionmapeditcommands.h +++ b/include/core/regionmapeditcommands.h @@ -64,9 +64,9 @@ class EditLayout : public QUndoCommand { /// Edit Layout Dimensions -class ResizeLayout : public QUndoCommand { +class ResizeRMLayout : public QUndoCommand { public: - ResizeLayout(RegionMap *map, int oldWidth, int oldHeight, int newWidth, int newHeight, + ResizeRMLayout(RegionMap *map, int oldWidth, int oldHeight, int newWidth, int newHeight, QMap> oldLayouts, QMap> newLayouts, QUndoCommand *parent = nullptr); void undo() override; @@ -153,7 +153,7 @@ class ResizeTilemap : public EditTilemap { /// ClearEntries class ClearEntries : public QUndoCommand { public: - ClearEntries(RegionMap *map, tsl::ordered_map, QUndoCommand *parent = nullptr); + ClearEntries(RegionMap *map, QMap, QUndoCommand *parent = nullptr); void undo() override; void redo() override; @@ -163,7 +163,7 @@ class ClearEntries : public QUndoCommand { private: RegionMap *map; - tsl::ordered_map entries; + QMap entries; }; #endif // REGIONMAPEDITCOMMANDS_H diff --git a/include/core/tileset.h b/include/core/tileset.h index b120b2c5a..bb7631d75 100644 --- a/include/core/tileset.h +++ b/include/core/tileset.h @@ -18,6 +18,7 @@ class Tileset Tileset() = default; Tileset(const Tileset &other); Tileset &operator=(const Tileset &other); + ~Tileset(); public: QString name; @@ -33,7 +34,6 @@ class Tileset QStringList palettePaths; QList tiles; - QList metatiles; QHash metatileLabels; QList> palettes; QList> palettePreviews; @@ -59,6 +59,19 @@ class Tileset bool appendToHeaders(QString root, QString friendlyName, bool usingAsm); bool appendToGraphics(QString root, QString friendlyName, bool usingAsm); bool appendToMetatiles(QString root, QString friendlyName, bool usingAsm); + + void setMetatiles(const QList &metatiles); + void addMetatile(Metatile* metatile); + + QList metatiles() const { return m_metatiles; } + Metatile* metatileAt(unsigned int i) const { return m_metatiles.at(i); } + + void clearMetatiles(); + void resizeMetatiles(unsigned int newNumMetatiles); + int numMetatiles() const { return m_metatiles.length(); } + +private: + QList m_metatiles; }; #endif // TILESET_H diff --git a/include/core/wildmoninfo.h b/include/core/wildmoninfo.h index 6c93d0d20..d0aa29dce 100644 --- a/include/core/wildmoninfo.h +++ b/include/core/wildmoninfo.h @@ -8,7 +8,7 @@ struct WildPokemon { int minLevel = 5; int maxLevel = 5; - QString species = "SPECIES_NONE"; + QString species = "SPECIES_NONE"; // TODO: Use define_species_prefix }; struct WildMonInfo { diff --git a/include/editor.h b/include/editor.h index 7ef5ba10d..f5e94d288 100644 --- a/include/editor.h +++ b/include/editor.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "mapconnection.h" #include "metatileselector.h" @@ -21,8 +22,9 @@ #include "divingmappixmapitem.h" #include "currentselectedmetatilespixmapitem.h" #include "collisionpixmapitem.h" -#include "mappixmapitem.h" +#include "layoutpixmapitem.h" #include "settings.h" +#include "gridsettings.h" #include "movablerect.h" #include "cursortilerect.h" #include "mapruler.h" @@ -45,17 +47,33 @@ class Editor : public QObject public: Ui::MainWindow* ui; QObject *parent = nullptr; + QPointer project = nullptr; - Map *map = nullptr; + QPointer map = nullptr; + QPointer layout = nullptr; + + QUndoGroup editGroup; // Manages the undo history for each map + Settings *settings; + GridSettings gridSettings; + void setProject(Project * project); - void saveProject(); void save(); - void closeProject(); - bool setMap(QString map_name); + void saveProject(); void saveUiFields(); void saveEncounterTabData(); + + void closeProject(); + + bool setMap(QString map_name); + bool setLayout(QString layoutName); + void unsetMap(); + + Tileset *getCurrentMapPrimaryTileset(); + bool displayMap(); + bool displayLayout(); + void displayMetatileSelector(); void displayMapMetatiles(); void displayMapMovementPermissions(); @@ -67,61 +85,60 @@ class Editor : public QObject void displayMapConnections(); void displayMapBorder(); void displayMapGrid(); + void updateMapGrid(); void displayWildMonTables(); void updateMapBorder(); void updateMapConnections(); - void setEditingMap(); - void setEditingCollision(); - void setEditingObjects(); - void setEditingConnections(); - void setMapEditingButtonsEnabled(bool enabled); void setConnectionsVisibility(bool visible); void updateDivingMapsVisibility(); void renderDivingConnections(); void addConnection(MapConnection* connection); void removeConnection(MapConnection* connection); - void removeSelectedConnection(); void addNewWildMonGroup(QWidget *window); void deleteWildMonGroup(); + void configureEncounterJSON(QWidget *); EncounterTableModel* getCurrentWildMonTable(); void updateDiveMap(QString mapName); void updateEmergeMap(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); void updateCustomMapHeaderValues(QTableWidget *); - void configureEncounterJSON(QWidget *); - Tileset *getCurrentMapPrimaryTileset(); DraggablePixmapItem *addMapEvent(Event *event); + bool eventLimitReached(Map *, Event::Type); void selectMapEvent(DraggablePixmapItem *object, bool toggle = false); DraggablePixmapItem *addNewEvent(Event::Type type); void updateSelectedEvents(); void duplicateSelectedEvents(); void redrawObject(DraggablePixmapItem *item); QList getObjects(); + void updateCursorRectPos(int x, int y); void setCursorRectVisible(bool visible); + void updateWarpEventWarning(Event *event); void updateWarpEventWarnings(); - bool eventLimitReached(Map *, Event::Type); QPointer scene = nullptr; QGraphicsPixmapItem *current_view = nullptr; - QPointer map_item = nullptr; + QPointer map_item = nullptr; QList> connection_items; QMap> diving_map_items; QGraphicsPathItem *connection_mask = nullptr; QPointer collision_item = nullptr; QGraphicsItemGroup *events_group = nullptr; + QList borderItems; - QList gridLines; + QGraphicsItemGroup *mapGrid = nullptr; + MapRuler *map_ruler = nullptr; + MovableRect *playerViewRect = nullptr; CursorTileRect *cursorMapTileRect = nullptr; - MapRuler *map_ruler = nullptr; QPointer scene_metatiles = nullptr; QPointer scene_current_metatile_selection = nullptr; @@ -137,8 +154,27 @@ class Editor : public QObject QPointer selected_connection_item = nullptr; QPointer connection_to_select = nullptr; - QString map_edit_mode = "paint"; - QString obj_edit_mode = "select"; + enum class EditAction { None, Paint, Select, Fill, Shift, Pick, Move }; + EditAction mapEditAction = EditAction::Paint; + EditAction objectEditAction = EditAction::Select; + + enum class EditMode { None, Disabled, Metatiles, Collision, Header, Events, Connections, Encounters }; + EditMode editMode = EditMode::None; + void setEditMode(EditMode mode) { this->editMode = mode; } + EditMode getEditMode() { return this->editMode; } + + bool getEditingLayout(); + + void setEditorView(); + + void setEditingMetatiles(); + void setEditingCollision(); + void setEditingHeader(); + void setEditingObjects(); + void setEditingConnections(); + void setEditingEncounters(); + + void setMapEditingButtonsEnabled(bool enabled); int scaleIndex = 2; qreal collisionOpacity = 0.5; @@ -148,10 +184,9 @@ class Editor : public QObject int getBorderDrawDistance(int dimension); - QUndoGroup editGroup; // Manages the undo history for each map - bool selectingEvent = false; + void deleteSelectedEvents(); void shouldReselectEvents(); void scaleMapView(int); static void openInTextEditor(const QString &path, int lineNum = 0); @@ -165,6 +200,7 @@ public slots: void maskNonVisibleConnectionTiles(); void onBorderMetatilesChanged(); void selectedEventIndexChanged(int index, Event::Group eventGroup); + void toggleGrid(bool); private: const QImage defaultCollisionImgSheet = QImage(":/images/collisions.png"); @@ -202,11 +238,11 @@ public slots: qint64 *pid = nullptr); private slots: - void onMapStartPaint(QGraphicsSceneMouseEvent *event, MapPixmapItem *item); - void onMapEndPaint(QGraphicsSceneMouseEvent *event, MapPixmapItem *item); + void onMapStartPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item); + void onMapEndPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item); void setSmartPathCursorMode(QGraphicsSceneMouseEvent *event); void setStraightPathCursorMode(QGraphicsSceneMouseEvent *event); - void mouseEvent_map(QGraphicsSceneMouseEvent *event, MapPixmapItem *item); + void mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item); void mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixmapItem *item); void setSelectedConnectionItem(ConnectionPixmapItem *connectionItem); void onHoveredMovementPermissionChanged(uint16_t, uint16_t); @@ -219,7 +255,6 @@ private slots: void onHoveredMapMovementPermissionCleared(); void onSelectedMetatilesChanged(); void onWheelZoom(int); - void onToggleGridClicked(bool); signals: void objectsChanged(); @@ -231,6 +266,7 @@ private slots: void currentMetatilesSelectionChanged(); void mapRulerStatusChanged(const QString &); void tilesetUpdated(QString); + void gridToggled(bool); }; #endif // EDITOR_H diff --git a/include/lib/fex/lexer.h b/include/lib/fex/lexer.h index 9b22976d8..d4d652719 100644 --- a/include/lib/fex/lexer.h +++ b/include/lib/fex/lexer.h @@ -4,6 +4,7 @@ #include #include #include +#include namespace fex { @@ -89,9 +90,7 @@ namespace fex Lexer() = default; ~Lexer() = default; - std::vector LexFile(const std::string &path); - std::vector LexString(const std::string &data); - void LexFileDumpTokens(const std::string &path, const std::string &out); + std::vector LexFile(const QString &path); private: std::vector Lex(); diff --git a/include/lib/fex/parser.h b/include/lib/fex/parser.h index 6a6b9e43a..b73dd81e9 100644 --- a/include/lib/fex/parser.h +++ b/include/lib/fex/parser.h @@ -21,7 +21,7 @@ namespace fex std::vector ParseTopLevelArrays(std::vector tokens); std::map ParseTopLevelObjects(std::vector tokens); - std::map ReadDefines(const std::string &filename, std::vector matching); + std::map ReadDefines(const QString &filename, std::vector matching); private: int EvaluateExpression(std::vector tokens); diff --git a/include/lib/fex/parser_util.h b/include/lib/fex/parser_util.h deleted file mode 100644 index 58ff89fcb..000000000 --- a/include/lib/fex/parser_util.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef PARSER_UTIL_H -#define PARSER_UTIL_H - -#include -#include - -class ParserUtil -{ -public: - ParserUtil(QString root); - QStringList ReadDefines(QString filename, QString prefix); - QStringList ReadDefinesValueSort(QString filename, QString prefix); - -private: - QString root_; -}; - - -#endif // PARSER_UTIL_H diff --git a/include/mainwindow.h b/include/mainwindow.h index c2726efc2..397a1d722 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -21,11 +21,13 @@ #include "regionmapeditor.h" #include "mapimageexporter.h" #include "filterchildrenproxymodel.h" +#include "maplistmodels.h" #include "newmappopup.h" #include "newtilesetdialog.h" #include "shortcutseditor.h" #include "preferenceeditor.h" #include "projectsettingseditor.h" +#include "gridsettings.h" #include "customscriptseditor.h" #include "wildmonchart.h" #include "updatepromoter.h" @@ -165,13 +167,14 @@ public slots: void on_mainTabBar_tabBarClicked(int index); void on_mapViewTab_tabBarClicked(int index); void onWarpBehaviorWarningClicked(); + void clearOverlay(); private slots: void on_action_Open_Project_triggered(); void on_action_Reload_Project_triggered(); void on_action_Close_Project_triggered(); - void on_mapList_activated(const QModelIndex &index); void on_action_Save_Project_triggered(); + void openWarpMap(QString map_name, int event_id, Event::Group event_group); void duplicate(); @@ -180,16 +183,17 @@ private slots: void copy(); void paste(); + void onLayoutChanged(Layout *layout); void onOpenConnectedMap(MapConnection*); - void onMapNeedsRedrawing(); void onTilesetsSaved(QString, QString); void openNewMapPopupWindow(); void onNewMapCreated(); - void onMapCacheCleared(); void onMapLoaded(Map *map); void importMapFromAdvanceMap1_92(); void onMapRulerStatusChanged(const QString &); void applyUserShortcuts(); + void markMapEdited(); + void markSpecificMapEdited(Map*); void on_action_NewMap_triggered(); void on_actionNew_Tileset_triggered(); @@ -200,6 +204,7 @@ private slots: void on_comboBox_Weather_currentTextChanged(const QString &arg1); void on_comboBox_Type_currentTextChanged(const QString &arg1); void on_comboBox_BattleScene_currentTextChanged(const QString &arg1); + void on_comboBox_LayoutSelector_currentTextChanged(const QString &arg1); void on_checkBox_ShowLocation_stateChanged(int selected); void on_checkBox_AllowRunning_stateChanged(int selected); void on_checkBox_AllowBiking_stateChanged(int selected); @@ -219,9 +224,6 @@ private slots: void on_actionMove_triggered(); void on_actionMap_Shift_triggered(); - void onDeleteKeyPressed(); - void on_toolButton_deleteObject_clicked(); - void addNewEvent(Event::Type type); void tryAddEventTab(QWidget * tab); void displayEventTabs(); @@ -236,9 +238,6 @@ private slots: void on_toolButton_Shift_clicked(); void onOpenMapListContextMenu(const QPoint &point); - void onAddNewMapToGroupClick(QAction* triggeredAction); - void onAddNewMapToAreaClick(QAction* triggeredAction); - void onAddNewMapToLayoutClick(QAction* triggeredAction); void currentMetatilesSelectionChanged(); void on_action_Export_Map_Image_triggered(); @@ -262,10 +261,6 @@ private slots: void on_actionTileset_Editor_triggered(); - void mapSortOrder_changed(QAction *action); - - void on_lineEdit_filterBox_textChanged(const QString &arg1); - void moveEvent(QMoveEvent *event); void closeEvent(QCloseEvent *); @@ -278,8 +273,11 @@ private slots: 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(); + + void mapListShortcut_ToggleEmptyFolders(); + void mapListShortcut_ExpandAll(); + void mapListShortcut_CollapseAll(); + void on_actionAbout_Porymap_triggered(); void on_actionOpen_Log_File_triggered(); void on_actionOpen_Config_Folder_triggered(); @@ -302,6 +300,8 @@ private slots: void on_actionProject_Settings_triggered(); void on_actionCustom_Scripts_triggered(); void reloadScriptEngine(); + void on_actionShow_Grid_triggered(); + void on_actionGrid_Settings_triggered(); public: Ui::MainWindow *ui; @@ -316,16 +316,20 @@ private slots: QPointer newMapPrompt = nullptr; QPointer preferenceEditor = nullptr; QPointer projectSettingsEditor = nullptr; + QPointer gridSettingsDialog = nullptr; QPointer customScriptsEditor = nullptr; + + QPointer groupListProxyModel = nullptr; + QPointer mapGroupModel = nullptr; + QPointer areaListProxyModel = nullptr; + QPointer mapAreaModel = nullptr; + QPointer layoutListProxyModel = nullptr; + QPointer layoutTreeModel = nullptr; + QPointer updatePromoter = nullptr; QPointer networkAccessManager = nullptr; QPointer aboutWindow = nullptr; QPointer wildMonChart = nullptr; - FilterChildrenProxyModel *mapListProxyModel; - QStandardItemModel *mapListModel; - QList *mapGroupItemsList; - QMap mapListIndexes; - QIcon mapIcon; QAction *undoAction = nullptr; QAction *redoAction = nullptr; @@ -337,48 +341,61 @@ private slots: bool isProgrammaticEventTabChange; bool newMapDefaultsSet = false; + bool tilesetNeedsRedraw = false; - bool userSetMap(QString, bool scrollTreeView = false); - bool setMap(QString, bool scrollTreeView = false); + bool setLayout(QString layoutId); + bool setMap(QString); + void unsetMap(); + bool userSetLayout(QString layoutId); + bool userSetMap(QString); void redrawMapScene(); void refreshMapScene(); + void setLayoutOnlyMode(bool layoutOnly); + bool checkProjectSanity(); bool loadProjectData(); bool setProjectUI(); void clearProjectUI(); - void sortMapList(); + void openSubWindow(QWidget * window); + void scrollMapList(MapTree *list, QString itemName); + void scrollMapListToCurrentMap(MapTree *list); + void scrollMapListToCurrentLayout(MapTree *list); + void resetMapListFilters(); + void showFileWatcherWarning(QString filepath); QString getExistingDirectory(QString); bool openProject(QString dir, bool initial = false); bool closeProject(); void showProjectOpenFailure(); - void saveGlobalConfigs(); + bool setInitialMap(); - QStandardItem* createMapItem(QString mapName, int groupNum, int inGroupNum); + void saveGlobalConfigs(); + void refreshRecentProjectsMenu(); - void updateMapListIcon(const QString &mapName); void updateMapList(); + void mapListAddGroup(); + void mapListAddLayout(); + void mapListAddArea(); + void openMapListItem(const QModelIndex &index); + void saveMapListTab(int index); void displayMapProperties(); void checkToolButtons(); - void clickToolButtonFromEditMode(QString editMode); + void clickToolButtonFromEditAction(Editor::EditAction editAction); - void markMapEdited(); - void markMapEdited(Map*); - void showWindowTitle(); + void updateWindowTitle(); void initWindow(); void initCustomUI(); void initExtraSignals(); void initEditor(); void initMiscHeapObjects(); - void initMapSortOrder(); + void initMapList(); void initShortcuts(); void initExtraShortcuts(); void loadUserSettings(); - void applyMapListFilter(QString filterText); void restoreWindowState(); void setTheme(QString); void updateTilesetEditor(); @@ -398,6 +415,9 @@ private slots: double getMetatilesZoomScale(); void redrawMetatileSelection(); void scrollMetatileSelectorToSelection(); + MapListToolBar* getCurrentMapListToolBar(); + MapTree* getCurrentMapList(); + void refreshLocationsComboBox(); QObjectList shortcutableObjects() const; void addCustomHeaderValue(QString key, QJsonValue value, bool isNew = false); @@ -407,12 +427,6 @@ private slots: void setDivingMapsVisible(bool visible); }; -enum MapListUserRoles { - GroupRole = Qt::UserRole + 1, // Used to hold the map group number. - TypeRole, // Used to differentiate between the different layers of the map list tree view. - TypeRole2, // Used for various extra data needed. -}; - // These are namespaced in a struct to avoid colliding with e.g. class Map. struct MainTab { enum { @@ -432,4 +446,10 @@ struct MapViewTab { }; }; +struct MapListTab { + enum { + Groups = 0, Areas, Layouts + }; +}; + #endif // MAINWINDOW_H diff --git a/include/project.h b/include/project.h index c3fc1677f..efb7db7c6 100644 --- a/include/project.h +++ b/include/project.h @@ -25,7 +25,7 @@ class Project : public QObject { Q_OBJECT public: - Project(QWidget *parent = nullptr); + Project(QObject *parent = nullptr); ~Project(); Project(const Project &) = delete; @@ -46,11 +46,9 @@ class Project : public QObject QStringList mapLayoutsTable; QStringList mapLayoutsTableMaster; QString layoutsLabel; - QMap mapLayouts; - QMap mapLayoutsMaster; - QMap mapSecToMapHoverName; - QMap mapSectionNameToValue; - QMap mapSectionValueToName; + QMap layoutIdsToNames; + QMap mapLayouts; + QMap mapLayoutsMaster; QMap eventGraphicsMap; QMap gfxDefines; QString defaultSong; @@ -67,6 +65,8 @@ class Project : public QObject QStringList bgEventFacingDirections; QStringList trainerTypes; QStringList globalScriptLabels; + QStringList mapSectionIdNames; + QMap regionMapEntries; QMap> metatileLabelsMap; QMap unusedMetatileLabels; QMap metatileBehaviorMap; @@ -76,19 +76,19 @@ class Project : public QObject QFileSystemWatcher fileWatcher; QMap modifiedFileTimestamps; bool usingAsmTilesets; - QString importExportPath; QSet disabledSettingsNames; int pokemonMinLevel; int pokemonMaxLevel; int maxEncounterRate; bool wildEncountersLoaded; + bool saveEmptyMapsec; void set_root(QString); - void initSignals(); - void clearMapCache(); void clearTilesetCache(); + void clearMapLayouts(); + void clearEventGraphics(); struct DataQualifiers { @@ -114,8 +114,8 @@ class Project : public QObject QStringList tilesetLabelsOrdered; Blockdata readBlockdata(QString); - bool loadBlockdata(MapLayout*); - bool loadLayoutBorder(MapLayout*); + bool loadBlockdata(Layout *); + bool loadLayoutBorder(Layout *); void saveTextFile(QString path, QString text); void appendTextFile(QString path, QString text); @@ -139,12 +139,20 @@ class Project : public QObject bool readSpeciesIconPaths(); QMap speciesToIconPath; + void addNewMapsec(const QString &name); + void removeMapsec(const QString &name); + + bool hasUnsavedChanges(); + bool hasUnsavedDataChanges = false; + QSet getTopLevelMapFields(); bool loadMapData(Map*); bool readMapLayouts(); - bool loadLayout(MapLayout *); + Layout *loadLayout(QString layoutId); + Layout *createNewLayout(Layout::SimpleSettings &layoutSettings); + bool loadLayout(Layout *); bool loadMapLayout(Map*); - bool loadLayoutTilesets(MapLayout*); + bool loadLayoutTilesets(Layout *); void loadTilesetAssets(Tileset*); void loadTilesetTiles(Tileset*, QImage); void loadTilesetMetatiles(Tileset*); @@ -152,15 +160,17 @@ class Project : public QObject void loadTilesetPalettes(Tileset*); void readTilesetPaths(Tileset* tileset); - void saveLayoutBlockdata(Map*); - void saveLayoutBorder(Map*); + void saveLayout(Layout *); + void saveLayoutBlockdata(Layout *); + void saveLayoutBorder(Layout *); void writeBlockdata(QString, const Blockdata &); void saveAllMaps(); - void saveMap(Map*); + void saveMap(Map *); void saveAllDataStructures(); void saveConfig(); void saveMapLayouts(); void saveMapGroups(); + void saveRegionMapSections(); void saveWildMonData(); void saveMapConstantsHeader(); void saveHealLocations(Map*); @@ -214,7 +224,6 @@ class Project : public QObject QString buildMetatileLabelsText(const QMap defines); QString findMetatileLabelsTileset(QString label); - void setImportExportPath(QString filename); static QString getExistingFilepath(QString filepath); void applyParsedLimits(); @@ -232,12 +241,13 @@ class Project : public QObject static bool mapDimensionsValid(int width, int height); bool calculateDefaultMapSize(); static int getMaxObjectEvents(); + static QString getEmptyMapsecName(); private: - void updateMapLayout(Map*); + void updateLayout(Layout *); - void setNewMapBlockdata(Map* map); - void setNewMapBorder(Map *map); + void setNewLayoutBlockdata(Layout *layout); + void setNewLayoutBorder(Layout *layout); void setNewMapEvents(Map *map); void setNewMapConnections(Map *map); @@ -256,9 +266,8 @@ class Project : public QObject static int max_object_events; signals: - void reloadProject(); - void uncheckMonitorFilesAction(); - void mapCacheCleared(); + void fileChanged(QString filepath); + void mapSectionIdNamesChanged(); void mapLoaded(Map *map); }; diff --git a/include/ui/bordermetatilespixmapitem.h b/include/ui/bordermetatilespixmapitem.h index f4af8bfe6..cf2ba0d42 100644 --- a/include/ui/bordermetatilespixmapitem.h +++ b/include/ui/bordermetatilespixmapitem.h @@ -1,21 +1,21 @@ #ifndef BORDERMETATILESPIXMAPITEM_H #define BORDERMETATILESPIXMAPITEM_H -#include "map.h" +#include "maplayout.h" #include "metatileselector.h" #include class BorderMetatilesPixmapItem : public QObject, public QGraphicsPixmapItem { Q_OBJECT public: - BorderMetatilesPixmapItem(Map *map_, MetatileSelector *metatileSelector) { - this->map = map_; - this->map->setBorderItem(this); + BorderMetatilesPixmapItem(Layout *layout, MetatileSelector *metatileSelector) { + this->layout = layout; + this->layout->setBorderItem(this); this->metatileSelector = metatileSelector; setAcceptHoverEvents(true); } MetatileSelector *metatileSelector; - Map *map; + Layout *layout; void draw(); signals: void hoveredBorderMetatileSelectionChanged(uint16_t); diff --git a/include/ui/collisionpixmapitem.h b/include/ui/collisionpixmapitem.h index c489ddd71..1419f2e4b 100644 --- a/include/ui/collisionpixmapitem.h +++ b/include/ui/collisionpixmapitem.h @@ -4,19 +4,20 @@ #include #include "metatileselector.h" -#include "mappixmapitem.h" +#include "movementpermissionsselector.h" +#include "layoutpixmapitem.h" #include "map.h" #include "settings.h" -class CollisionPixmapItem : public MapPixmapItem { +class CollisionPixmapItem : public LayoutPixmapItem { Q_OBJECT public: - CollisionPixmapItem(Map *map, QSpinBox * selectedCollision, QSpinBox * selectedElevation, MetatileSelector *metatileSelector, Settings *settings, qreal *opacity) - : MapPixmapItem(map, metatileSelector, settings){ + CollisionPixmapItem(Layout *layout, QSpinBox * selectedCollision, QSpinBox * selectedElevation, MetatileSelector *metatileSelector, Settings *settings, qreal *opacity) + : LayoutPixmapItem(layout, metatileSelector, settings){ this->selectedCollision = selectedCollision; this->selectedElevation = selectedElevation; this->opacity = opacity; - map->setCollisionItem(this); + layout->setCollisionItem(this); } QSpinBox * selectedCollision; QSpinBox * selectedElevation; diff --git a/include/ui/colorinputwidget.h b/include/ui/colorinputwidget.h new file mode 100644 index 000000000..cd871e0bf --- /dev/null +++ b/include/ui/colorinputwidget.h @@ -0,0 +1,46 @@ +#ifndef COLORINPUTWIDGET_H +#define COLORINPUTWIDGET_H + +#include +#include + +namespace Ui { +class ColorInputWidget; +} + + +class ColorInputWidget : public QGroupBox { + Q_OBJECT +public: + explicit ColorInputWidget(QWidget *parent = nullptr); + explicit ColorInputWidget(const QString &title, QWidget *parent = nullptr); + ~ColorInputWidget(); + + void setColor(QRgb color); + QRgb color() const { return m_color; } + + bool setBitDepth(int bits); + int bitDepth() const { return m_bitDepth; } + +signals: + void colorChanged(QRgb color); + void bitDepthChanged(int bits); + void editingFinished(); + +private: + Ui::ColorInputWidget *ui; + + QRgb m_color = 0; + int m_bitDepth = 0; + + void init(); + void updateColorUi(); + void pickColor(); + void blockEditSignals(bool block); + + void setRgbFromSliders(); + void setRgbFromSpinners(); + void setRgbFromHexString(const QString &); +}; + +#endif // COLORINPUTWIDGET_H diff --git a/include/ui/connectionpixmapitem.h b/include/ui/connectionpixmapitem.h index 62eda6fe4..183f2d795 100644 --- a/include/ui/connectionpixmapitem.h +++ b/include/ui/connectionpixmapitem.h @@ -5,6 +5,7 @@ #include #include #include +#include class ConnectionPixmapItem : public QObject, public QGraphicsPixmapItem { Q_OBJECT @@ -36,14 +37,17 @@ class ConnectionPixmapItem : public QObject, public QGraphicsPixmapItem { static const int mHeight = 16; protected: - QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; - void mousePressEvent(QGraphicsSceneMouseEvent*) override; - void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; - void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override; + virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; + virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override; + virtual void keyPressEvent(QKeyEvent*) override; + virtual void focusInEvent(QFocusEvent*) override; signals: void connectionItemDoubleClicked(MapConnection*); void selectionChanged(bool selected); + void deleteRequested(MapConnection*); }; #endif // CONNECTIONPIXMAPITEM_H diff --git a/include/ui/connectionslistitem.h b/include/ui/connectionslistitem.h index bbe0f2d31..7ba6a9d8e 100644 --- a/include/ui/connectionslistitem.h +++ b/include/ui/connectionslistitem.h @@ -34,11 +34,12 @@ class ConnectionsListItem : public QFrame unsigned actionId = 0; protected: - void mousePressEvent(QMouseEvent*) override; + virtual void mousePressEvent(QMouseEvent*) override; + virtual void focusInEvent(QFocusEvent*) override; + virtual void keyPressEvent(QKeyEvent*) override; signals: void selected(); - void removed(MapConnection*); void openMapClicked(MapConnection*); private slots: diff --git a/include/ui/currentselectedmetatilespixmapitem.h b/include/ui/currentselectedmetatilespixmapitem.h index 5e4bd2756..109f52c56 100644 --- a/include/ui/currentselectedmetatilespixmapitem.h +++ b/include/ui/currentselectedmetatilespixmapitem.h @@ -1,23 +1,24 @@ #ifndef CURRENTSELECTEDMETATILESPIXMAPITEM_H #define CURRENTSELECTEDMETATILESPIXMAPITEM_H -#include "map.h" #include "metatileselector.h" #include +class Layout; + class CurrentSelectedMetatilesPixmapItem : public QGraphicsPixmapItem { public: - CurrentSelectedMetatilesPixmapItem(Map *map, MetatileSelector *metatileSelector) { - this->map = map; + CurrentSelectedMetatilesPixmapItem(Layout *layout, MetatileSelector *metatileSelector) { + this->layout = layout; this->metatileSelector = metatileSelector; } - Map* map = nullptr; + Layout *layout = nullptr; MetatileSelector *metatileSelector; void draw(); - void setMap(Map *map) { this->map = map; } + void setLayout(Layout *layout) { this->layout = layout; } }; -QPixmap drawMetatileSelection(MetatileSelection selection, Map *map); +QPixmap drawMetatileSelection(MetatileSelection selection, Layout *layout); #endif // CURRENTSELECTEDMETATILESPIXMAPITEM_H diff --git a/include/ui/customscriptseditor.h b/include/ui/customscriptseditor.h index 0e5d34898..8d261570e 100644 --- a/include/ui/customscriptseditor.h +++ b/include/ui/customscriptseditor.h @@ -31,7 +31,6 @@ public slots: Ui::CustomScriptsEditor *ui; bool hasUnsavedChanges = false; - QString fileDialogDir; const QString baseDir; void displayScript(const QString &filepath, bool enabled); @@ -50,6 +49,7 @@ public slots: void restoreWindowState(); void initShortcuts(); QObjectList shortcutableObjects() const; + void openManual(); private slots: void dialogButtonClicked(QAbstractButton *button); diff --git a/include/ui/eventfilters.h b/include/ui/eventfilters.h new file mode 100644 index 000000000..851c344bd --- /dev/null +++ b/include/ui/eventfilters.h @@ -0,0 +1,28 @@ +#include +#include + + + +/// Prevent wheel scroll +class WheelFilter : public QObject { + Q_OBJECT +public: + WheelFilter(QObject *parent) : QObject(parent) {} + virtual ~WheelFilter() {} + bool eventFilter(QObject *obj, QEvent *event) override; +}; + + + +/// Ctrl+Wheel = zoom +class MapSceneEventFilter : public QObject { + Q_OBJECT +protected: + bool eventFilter(QObject *obj, QEvent *event) override; +public: + explicit MapSceneEventFilter(QObject *parent = nullptr) : QObject(parent) {} + +signals: + void wheelZoom(int delta); +public slots: +}; diff --git a/include/ui/filterchildrenproxymodel.h b/include/ui/filterchildrenproxymodel.h index b73cbd623..507693b33 100644 --- a/include/ui/filterchildrenproxymodel.h +++ b/include/ui/filterchildrenproxymodel.h @@ -9,9 +9,11 @@ class FilterChildrenProxyModel : public QSortFilterProxyModel public: explicit FilterChildrenProxyModel(QObject *parent = nullptr); + void setHideEmpty(bool hidden) { this->hideEmpty = hidden; } protected: bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const; - +private: + bool hideEmpty = false; }; #endif // FILTERCHILDRENPROXYMODEL_H diff --git a/include/ui/graphicsview.h b/include/ui/graphicsview.h index c0d1592cf..92771cf7a 100644 --- a/include/ui/graphicsview.h +++ b/include/ui/graphicsview.h @@ -34,6 +34,7 @@ class ClickableGraphicsView : public NoScrollGraphicsView class Editor; +// TODO: This should just be MapView. It makes map-based assumptions, and no other class inherits GraphicsView. class GraphicsView : public QGraphicsView { public: @@ -44,10 +45,10 @@ class GraphicsView : public QGraphicsView // GraphicsView_Object object; Editor *editor; protected: - void mousePressEvent(QMouseEvent *event); - void mouseMoveEvent(QMouseEvent *event); - void mouseReleaseEvent(QMouseEvent *event); - void moveEvent(QMoveEvent *event); + virtual void mousePressEvent(QMouseEvent *event) override; + virtual void mouseMoveEvent(QMouseEvent *event) override; + virtual void mouseReleaseEvent(QMouseEvent *event) override; + virtual void moveEvent(QMoveEvent *event) override; }; //Q_DECLARE_METATYPE(GraphicsView) diff --git a/include/ui/gridsettings.h b/include/ui/gridsettings.h new file mode 100644 index 000000000..807a0685c --- /dev/null +++ b/include/ui/gridsettings.h @@ -0,0 +1,99 @@ +#ifndef GRIDSETTINGS_H +#define GRIDSETTINGS_H + +#include +#include + +class GridSettings { +public: + explicit GridSettings() {}; + ~GridSettings() {}; + + enum Style { + Solid, + LargeDashes, + SmallDashes, + Crosshairs, + Dots, + }; + + uint width = 16; + uint height = 16; + int offsetX = 0; + int offsetY = 0; + Style style = Style::Solid; + QColor color = Qt::black; + QVector getHorizontalDashPattern() const { return this->getDashPattern(this->width); } + QVector getVerticalDashPattern() const { return this->getDashPattern(this->height); } + + static QString getStyleName(Style style); + static GridSettings::Style getStyleFromName(const QString &name); +private: + static const QMap styleToName; + + QVector getCenteredDashPattern(uint length, qreal dashLength, qreal gapLength) const; + QVector getDashPattern(uint length) const; +}; + +inline bool operator==(const GridSettings &a, const GridSettings &b) { + return a.width == b.width + && a.height == b.height + && a.offsetX == b.offsetX + && a.offsetY == b.offsetY + && a.style == b.style + && a.color == b.color; +} + +inline bool operator!=(const GridSettings &a, const GridSettings &b) { + return !(operator==(a, b)); +} + + + +namespace Ui { +class GridSettingsDialog; +} + +class GridSettingsDialog : public QDialog { + Q_OBJECT +public: + explicit GridSettingsDialog(QWidget *parent = nullptr); + explicit GridSettingsDialog(GridSettings *settings, QWidget *parent = nullptr); + ~GridSettingsDialog(); + + void setSettings(const GridSettings &settings); + GridSettings settings() const { return *m_settings; } + + void setDefaultSettings(const GridSettings &settings); + GridSettings defaultSettings() const { return m_defaultSettings; } + +signals: + void changedGridSettings(); + +private: + Ui::GridSettingsDialog *ui; + GridSettings *const m_settings; + const GridSettings m_originalSettings; + GridSettings m_defaultSettings; + bool m_dimensionsLinked = true; + bool m_offsetsLinked = true; + bool m_ownedSettings = false; + + void init(); + void updateInput(); + void setWidth(int value); + void setHeight(int value); + void setOffsetX(int value); + void setOffsetY(int value); + +private slots: + void dialogButtonClicked(QAbstractButton *button); + void on_spinBox_Width_valueChanged(int value); + void on_spinBox_Height_valueChanged(int value); + void on_spinBox_X_valueChanged(int value); + void on_spinBox_Y_valueChanged(int value); + void on_comboBox_Style_currentTextChanged(const QString &text); + void onColorChanged(QRgb color); +}; + +#endif // GRIDSETTINGS_H diff --git a/include/ui/mappixmapitem.h b/include/ui/layoutpixmapitem.h similarity index 80% rename from include/ui/mappixmapitem.h rename to include/ui/layoutpixmapitem.h index cd2d335cd..08496c54c 100644 --- a/include/ui/mappixmapitem.h +++ b/include/ui/layoutpixmapitem.h @@ -1,54 +1,58 @@ #ifndef MAPPIXMAPITEM_H #define MAPPIXMAPITEM_H -#include "map.h" #include "settings.h" #include "metatileselector.h" #include -class MapPixmapItem : public QObject, public QGraphicsPixmapItem { +class Layout; + +class LayoutPixmapItem : public QObject, public QGraphicsPixmapItem { Q_OBJECT private: using QGraphicsPixmapItem::paint; public: - enum class PaintMode { - Disabled, - Metatiles, - EventObjects - }; - MapPixmapItem(Map *map_, MetatileSelector *metatileSelector, Settings *settings) { - this->map = map_; - this->map->setMapItem(this); + LayoutPixmapItem(Layout *layout, MetatileSelector *metatileSelector, Settings *settings) { + this->layout = layout; + // this->map->setMapItem(this); this->metatileSelector = metatileSelector; this->settings = settings; - this->paintingMode = PaintMode::Metatiles; - this->lockedAxis = MapPixmapItem::Axis::None; + this->lockedAxis = LayoutPixmapItem::Axis::None; this->prevStraightPathState = false; setAcceptHoverEvents(true); } - MapPixmapItem::PaintMode paintingMode; - Map *map; + + Layout *layout; + MetatileSelector *metatileSelector; + Settings *settings; + bool active; bool has_mouse = false; bool right_click; + int paint_tile_initial_x; int paint_tile_initial_y; bool prevStraightPathState; int straight_path_initial_x; int straight_path_initial_y; + QPoint metatilePos; + enum Axis { None = 0, X, Y }; - MapPixmapItem::Axis lockedAxis; + + LayoutPixmapItem::Axis lockedAxis; + QPoint selection_origin; QList selection; + virtual void paint(QGraphicsSceneMouseEvent*); virtual void floodFill(QGraphicsSceneMouseEvent*); virtual void magicFill(QGraphicsSceneMouseEvent*); @@ -70,26 +74,33 @@ class MapPixmapItem : public QObject, public QGraphicsPixmapItem { QList selectedCollisions, bool fromScriptCall = false); void floodFillSmartPath(int initialX, int initialY, bool fromScriptCall = false); + virtual void pick(QGraphicsSceneMouseEvent*); virtual void select(QGraphicsSceneMouseEvent*); virtual void shift(QGraphicsSceneMouseEvent*); void shift(int xDelta, int yDelta, bool fromScriptCall = false); virtual void draw(bool ignoreCache = false); + void updateMetatileSelection(QGraphicsSceneMouseEvent *event); void paintNormal(int x, int y, bool fromScriptCall = false); void lockNondominantAxis(QGraphicsSceneMouseEvent *event); QPoint adjustCoords(QPoint pos); + void setEditsEnabled(bool enabled) { this->editsEnabled = enabled; } + bool getEditsEnabled() { return this->editsEnabled; } + private: void paintSmartPath(int x, int y, bool fromScriptCall = false); static QList smartPathTable; unsigned actionId_ = 0; + bool editsEnabled = true; + signals: - void startPaint(QGraphicsSceneMouseEvent *, MapPixmapItem *); - void endPaint(QGraphicsSceneMouseEvent *, MapPixmapItem *); - void mouseEvent(QGraphicsSceneMouseEvent *, MapPixmapItem *); + void startPaint(QGraphicsSceneMouseEvent *, LayoutPixmapItem *); + void endPaint(QGraphicsSceneMouseEvent *, LayoutPixmapItem *); + void mouseEvent(QGraphicsSceneMouseEvent *, LayoutPixmapItem *); void hoveredMapMetatileChanged(const QPoint &pos); void hoveredMapMetatileCleared(); diff --git a/include/ui/mapimageexporter.h b/include/ui/mapimageexporter.h index b9cc20bda..37df3038c 100644 --- a/include/ui/mapimageexporter.h +++ b/include/ui/mapimageexporter.h @@ -16,6 +16,24 @@ enum ImageExporterMode { Timelapse, }; +struct ImageExporterSettings { + bool showObjects = false; + bool showWarps = false; + bool showBGs = false; + bool showTriggers = false; + bool showHealLocations = false; + bool showUpConnections = false; + bool showDownConnections = false; + bool showLeftConnections = false; + bool showRightConnections = false; + bool showGrid = false; + bool showBorder = false; + bool showCollision = false; + bool previewActualSize = false; + int timelapseSkipAmount = 1; + int timelapseDelayMs = 200; +}; + class MapImageExporter : public QDialog { Q_OBJECT @@ -27,56 +45,51 @@ class MapImageExporter : public QDialog private: Ui::MapImageExporter *ui; + Layout *layout = nullptr; Map *map = nullptr; Editor *editor = nullptr; QGraphicsScene *scene = nullptr; QPixmap preview; - bool showObjects = false; - bool showWarps = false; - bool showBGs = false; - bool showTriggers = false; - bool showHealSpots = false; - bool showUpConnections = false; - bool showDownConnections = false; - bool showLeftConnections = false; - bool showRightConnections = false; - bool showGrid = false; - bool showBorder = false; - bool showCollision = false; - int timelapseSkipAmount = 1; - int timelapseDelayMs = 200; + ImageExporterSettings settings; ImageExporterMode mode = ImageExporterMode::Normal; void updatePreview(); + void scalePreview(); void updateShowBorderState(); void saveImage(); QPixmap getStitchedImage(QProgressDialog *progress, bool includeBorder); QPixmap getFormattedMapPixmap(Map *map, bool ignoreBorder = false); bool historyItemAppliesToFrame(const QUndoCommand *command); +protected: + virtual void showEvent(QShowEvent *) override; + virtual void resizeEvent(QResizeEvent *) override; + private slots: void on_checkBox_Objects_stateChanged(int state); void on_checkBox_Warps_stateChanged(int state); void on_checkBox_BGs_stateChanged(int state); void on_checkBox_Triggers_stateChanged(int state); - void on_checkBox_HealSpots_stateChanged(int state); + void on_checkBox_HealLocations_stateChanged(int state); + void on_checkBox_AllEvents_stateChanged(int state); void on_checkBox_ConnectionUp_stateChanged(int state); void on_checkBox_ConnectionDown_stateChanged(int state); void on_checkBox_ConnectionLeft_stateChanged(int state); void on_checkBox_ConnectionRight_stateChanged(int state); + void on_checkBox_AllConnections_stateChanged(int state); void on_checkBox_Elevation_stateChanged(int state); void on_checkBox_Grid_stateChanged(int state); void on_checkBox_Border_stateChanged(int state); - void on_pushButton_Save_pressed(); void on_pushButton_Reset_pressed(); - void on_pushButton_Cancel_pressed(); void on_spinBox_TimelapseDelay_valueChanged(int delayMs); void on_spinBox_FrameSkip_valueChanged(int skip); + + void on_checkBox_ActualSize_stateChanged(int state); }; #endif // MAPIMAGEEXPORTER_H diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h new file mode 100644 index 000000000..17810c7ed --- /dev/null +++ b/include/ui/maplistmodels.h @@ -0,0 +1,208 @@ +#pragma once +#ifndef MAPLISTMODELS_H +#define MAPLISTMODELS_H + +#include +#include +#include +#include +#include + + + +class Project; + +enum MapListUserRoles { + GroupRole = Qt::UserRole + 1, // Used to hold the map group number. + TypeRole, // Used to differentiate between the different layers of the map list tree view. + TypeRole2, // Used for various extra data needed. +}; + + + +class MapTree : public QTreeView { + Q_OBJECT +public: + MapTree(QWidget *parent) : QTreeView(parent) { + this->setDropIndicatorShown(true); + this->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + this->setFocusPolicy(Qt::StrongFocus); + this->setContextMenuPolicy(Qt::CustomContextMenu); + } + +protected: + virtual void keyPressEvent(QKeyEvent *event) override; + +public slots: + void removeSelected(); +}; + + + +class GroupNameDelegate : public QStyledItemDelegate { + Q_OBJECT + +public: + GroupNameDelegate(Project *project, QObject *parent = nullptr) : QStyledItemDelegate(parent), project(project) {} + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + void setEditorData(QWidget *editor, const QModelIndex &index) const override; + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + +private: + Project *project = nullptr; +}; + + + +class QRegularExpressionValidator; + +class MapListModel : public QStandardItemModel { + Q_OBJECT + +public: + MapListModel(QObject *parent = nullptr) : QStandardItemModel(parent) {}; + ~MapListModel() { } + + virtual QModelIndex indexOf(QString id) const = 0; + virtual void removeItemAt(const QModelIndex &index); + virtual QStandardItem *getItem(const QModelIndex &index) const = 0; + +protected: + virtual void removeItem(QStandardItem *item) = 0; +}; + +class MapGroupModel : public MapListModel { + Q_OBJECT + +public: + MapGroupModel(Project *project, QObject *parent = nullptr); + ~MapGroupModel() { } + + QVariant data(const QModelIndex &index, int role) const override; + + Qt::DropActions supportedDropActions() const override; + QStringList mimeTypes() const override; + virtual QMimeData *mimeData(const QModelIndexList &indexes) const override; + virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; + + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + +public: + void setMap(QString mapName) { this->openMap = mapName; } + + QStandardItem *createGroupItem(QString groupName, int groupIndex, QStandardItem *fromItem = nullptr); + QStandardItem *createMapItem(QString mapName, QStandardItem *fromItem = nullptr); + + QStandardItem *insertGroupItem(QString groupName); + QStandardItem *insertMapItem(QString mapName, QString groupName); + + virtual QStandardItem *getItem(const QModelIndex &index) const override; + virtual QModelIndex indexOf(QString mapName) const override; + + void initialize(); + +protected: + virtual void removeItem(QStandardItem *item) override; + +private: + friend class MapTree; + void updateProject(); + +private: + Project *project; + QStandardItem *root = nullptr; + + QMap groupItems; + QMap mapItems; + + QString openMap; + +signals: + void dragMoveCompleted(); +}; + + + +class MapAreaModel : public MapListModel { + Q_OBJECT + +public: + MapAreaModel(Project *project, QObject *parent = nullptr); + ~MapAreaModel() {} + + QVariant data(const QModelIndex &index, int role) const override; + +public: + void setMap(QString mapName) { this->openMap = mapName; } + + QStandardItem *createAreaItem(QString areaName); + QStandardItem *createMapItem(QString mapName, int areaIndex, int mapIndex); + + QStandardItem *insertAreaItem(QString areaName); + QStandardItem *insertMapItem(QString mapName, QString areaName, int groupIndex); + + virtual QStandardItem *getItem(const QModelIndex &index) const override; + virtual QModelIndex indexOf(QString mapName) const override; + + void initialize(); + +protected: + virtual void removeItem(QStandardItem *item) override; + +private: + Project *project; + QStandardItem *root = nullptr; + + QMap areaItems; + QMap mapItems; + + QString openMap; + +signals: + void edited(); +}; + + + +class LayoutTreeModel : public MapListModel { + Q_OBJECT + +public: + LayoutTreeModel(Project *project, QObject *parent = nullptr); + ~LayoutTreeModel() {} + + QVariant data(const QModelIndex &index, int role) const override; + +public: + void setLayout(QString layoutId) { this->openLayout = layoutId; } + + QStandardItem *createLayoutItem(QString layoutId); + QStandardItem *createMapItem(QString mapName); + + QStandardItem *insertLayoutItem(QString layoutId); + QStandardItem *insertMapItem(QString mapName, QString layoutId); + + virtual QStandardItem *getItem(const QModelIndex &index) const override; + virtual QModelIndex indexOf(QString layoutName) const override; + + void initialize(); + +protected: + virtual void removeItem(QStandardItem *item) override; + +private: + Project *project; + QStandardItem *root = nullptr; + + QMap layoutItems; + QMap mapItems; + + QString openLayout; + +signals: + void edited(); +}; + +#endif // MAPLISTMODELS_H diff --git a/include/ui/maplisttoolbar.h b/include/ui/maplisttoolbar.h new file mode 100644 index 000000000..bf749a3e1 --- /dev/null +++ b/include/ui/maplisttoolbar.h @@ -0,0 +1,52 @@ +#ifndef MAPLISTTOOLBAR_H +#define MAPLISTTOOLBAR_H + +#include "maplistmodels.h" +#include "filterchildrenproxymodel.h" + +#include +#include + +namespace Ui { +class MapListToolBar; +} + +class MapListToolBar : public QFrame +{ + Q_OBJECT + +public: + explicit MapListToolBar(QWidget *parent = nullptr); + ~MapListToolBar(); + + MapTree* list() const { return m_list; } + void setList(MapTree *list); + + void setEditsAllowedButtonVisible(bool visible); + void setEditsAllowed(bool allowed); + void toggleEditsAllowed(); + + void setEmptyFoldersVisible(bool visible); + void toggleEmptyFolders(); + + void expandList(); + void collapseList(); + + void applyFilter(const QString &filterText); + void clearFilter(); + void setFilterLocked(bool locked) { m_filterLocked = locked; } + bool isFilterLocked() const { return m_filterLocked; } + +signals: + void filterCleared(MapTree*); + void addFolderClicked(); + +private: + Ui::MapListToolBar *ui; + QPointer m_list; + bool m_filterLocked = false; + bool m_editsAllowed = false; + bool m_emptyFoldersVisible = true; +}; + +#endif // MAPLISTTOOLBAR_H diff --git a/include/ui/mapsceneeventfilter.h b/include/ui/mapsceneeventfilter.h deleted file mode 100644 index 7de427e31..000000000 --- a/include/ui/mapsceneeventfilter.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef MAPSCENEEVENTFILTER_H -#define MAPSCENEEVENTFILTER_H - -#include - -class MapSceneEventFilter : public QObject -{ - Q_OBJECT -protected: - bool eventFilter(QObject *obj, QEvent *event) override; -public: - explicit MapSceneEventFilter(QObject *parent = nullptr); - -signals: - void wheelZoom(int delta); -public slots: -}; - -#endif // MAPSCENEEVENTFILTER_H diff --git a/include/ui/mapview.h b/include/ui/mapview.h index 9176c825a..7355da9d0 100644 --- a/include/ui/mapview.h +++ b/include/ui/mapview.h @@ -73,7 +73,8 @@ class MapView : public GraphicsView private: QMap overlayMap; protected: - void drawForeground(QPainter *painter, const QRectF &rect); + virtual void drawForeground(QPainter *painter, const QRectF &rect) override; + virtual void keyPressEvent(QKeyEvent*) override; }; #endif // GRAPHICSVIEW_H diff --git a/include/ui/metatileselector.h b/include/ui/metatileselector.h index ec2c49f41..ae11c58e0 100644 --- a/include/ui/metatileselector.h +++ b/include/ui/metatileselector.h @@ -31,13 +31,13 @@ struct MetatileSelection class MetatileSelector: public SelectablePixmapItem { Q_OBJECT public: - MetatileSelector(int numMetatilesWide, Map *map): SelectablePixmapItem(16, 16) { + MetatileSelector(int numMetatilesWide, Layout *layout): SelectablePixmapItem(16, 16) { this->externalSelection = false; this->prefabSelection = false; this->numMetatilesWide = numMetatilesWide; - this->map = map; - this->primaryTileset = map->layout->tileset_primary; - this->secondaryTileset = map->layout->tileset_secondary; + this->layout = layout; + this->primaryTileset = layout->tileset_primary; + this->secondaryTileset = layout->tileset_secondary; this->selection = MetatileSelection{}; this->cellPos = QPoint(-1, -1); setAcceptHoverEvents(true); @@ -51,7 +51,7 @@ class MetatileSelector: public SelectablePixmapItem { void setPrefabSelection(MetatileSelection selection); void setExternalSelection(int, int, QList, QList>); QPoint getMetatileIdCoordsOnWidget(uint16_t); - void setMap(Map*); + void setLayout(Layout *layout); bool isInternalSelection() const { return (!this->externalSelection && !this->prefabSelection); } Tileset *primaryTileset; Tileset *secondaryTileset; @@ -65,7 +65,7 @@ class MetatileSelector: public SelectablePixmapItem { bool externalSelection; bool prefabSelection; int numMetatilesWide; - Map *map; + Layout *layout; int externalSelectionWidth; int externalSelectionHeight; QList externalSelectedMetatiles; diff --git a/include/ui/montabwidget.h b/include/ui/montabwidget.h index 4b66969c3..6d9160689 100644 --- a/include/ui/montabwidget.h +++ b/include/ui/montabwidget.h @@ -32,8 +32,6 @@ public slots: void deactivateTab(int tabIndex); private: - bool eventFilter(QObject *object, QEvent *event); - void actionCopyTab(int index); void actionAddDeleteTab(int index); diff --git a/include/ui/newmappopup.h b/include/ui/newmappopup.h index 826e1609f..f160876a0 100644 --- a/include/ui/newmappopup.h +++ b/include/ui/newmappopup.h @@ -23,8 +23,9 @@ class NewMapPopup : public QMainWindow bool importedMap; QString layoutId; void init(); - void init(MapSortOrder type, QVariant data); - void init(MapLayout *); + void initUi(); + void init(int tabIndex, QString data); + void init(Layout *); static void setDefaultSettings(Project *project); signals: @@ -37,7 +38,7 @@ class NewMapPopup : public QMainWindow bool checkNewMapGroup(); void saveSettings(); void useLayout(QString layoutId); - void useLayoutSettings(MapLayout *mapLayout); + void useLayoutSettings(Layout *mapLayout); struct Settings { QString group; @@ -60,6 +61,8 @@ class NewMapPopup : public QMainWindow static struct Settings settings; private slots: + void on_checkBox_UseExistingLayout_stateChanged(int state); + void on_comboBox_Layout_currentTextChanged(const QString &text); void on_pushButton_NewMap_Accept_clicked(); void on_lineEdit_NewMap_Name_textChanged(const QString &); }; diff --git a/include/ui/overlay.h b/include/ui/overlay.h index 8f64d058d..86fe09637 100644 --- a/include/ui/overlay.h +++ b/include/ui/overlay.h @@ -50,19 +50,19 @@ class OverlayPath : public OverlayItem { QColor fillColor; }; -class OverlayImage : public OverlayItem { +class OverlayPixmap : public OverlayItem { public: - OverlayImage(int x, int y, QImage image) { + OverlayPixmap(int x, int y, QPixmap pixmap) { this->x = x; this->y = y; - this->image = image; + this->pixmap = pixmap; } - ~OverlayImage() {} + ~OverlayPixmap() {} virtual void render(QPainter *painter); private: int x; int y; - QImage image; + QPixmap pixmap; }; class Overlay diff --git a/include/ui/paletteeditor.h b/include/ui/paletteeditor.h index 9b846d600..63581eb1b 100644 --- a/include/ui/paletteeditor.h +++ b/include/ui/paletteeditor.h @@ -2,10 +2,8 @@ #define PALETTEEDITOR_H #include -#include -#include -#include -#include + +#include "colorinputwidget.h" #include "project.h" #include "history.h" @@ -32,43 +30,27 @@ class PaletteEditor : public QMainWindow { private: Ui::PaletteEditor *ui; Project *project = nullptr; - - QList> sliders; - QList> spinners; - QList frames; - QList pickButtons; - QList hexEdits; + QList colorInputs; Tileset *primaryTileset; Tileset *secondaryTileset; QList> palettesHistory; - void refreshColorUis(); - void updateColorUi(int index, QRgb color); - void commitEditHistory(int paletteid); + Tileset* getTileset(int paletteId); + void refreshColorInputs(); + void commitEditHistory(); + void commitEditHistory(int paletteId); void restoreWindowState(); - void setSignalsEnabled(bool enabled); - void setColorsFromHistory(PaletteHistoryItem*, int); void closeEvent(QCloseEvent*); - void pickColor(int i); void setRgb(int index, QRgb rgb); - void setRgbFromSliders(int colorIndex); - void setRgbFromHexEdit(int colorIndex); - void setRgbFromSpinners(int colorIndex); + void setPalette(int paletteId, const QList &palette); void setBitDepth(int bits); int bitDepth = 24; - class HexCodeValidator : public QValidator { - virtual QValidator::State validate(QString &input, int &) const override { - input = input.toUpper(); - return QValidator::Acceptable; - } - }; - - HexCodeValidator *hexValidator = nullptr; + static const int numColors = 16; signals: void closed(); diff --git a/include/ui/prefab.h b/include/ui/prefab.h index 7bd9e0b24..2ac8fa042 100644 --- a/include/ui/prefab.h +++ b/include/ui/prefab.h @@ -20,9 +20,9 @@ struct PrefabItem class Prefab { public: - void initPrefabUI(MetatileSelector *selector, QWidget *prefabWidget, QLabel *emptyPrefabLabel, Map *map); - void addPrefab(MetatileSelection selection, Map *map, QString name); - void updatePrefabUi(Map *map); + void initPrefabUI(MetatileSelector *selector, QWidget *prefabWidget, QLabel *emptyPrefabLabel, Layout *layout); + void addPrefab(MetatileSelection selection, Layout *layout, QString name); + void updatePrefabUi(Layout *layout); bool tryImportDefaultPrefabs(QWidget * parent, BaseGameVersion version, QString filepath = ""); private: diff --git a/include/ui/prefabcreationdialog.h b/include/ui/prefabcreationdialog.h index 5748f35af..0821f7512 100644 --- a/include/ui/prefabcreationdialog.h +++ b/include/ui/prefabcreationdialog.h @@ -2,10 +2,11 @@ #define PREFABCREATIONDIALOG_H #include "metatileselector.h" -#include "map.h" #include +class Layout; + namespace Ui { class PrefabCreationDialog; } @@ -15,12 +16,12 @@ class PrefabCreationDialog : public QDialog Q_OBJECT public: - explicit PrefabCreationDialog(QWidget *parent, MetatileSelector *metatileSelector, Map *map); + explicit PrefabCreationDialog(QWidget *parent, MetatileSelector *metatileSelector, Layout *layout); ~PrefabCreationDialog(); void savePrefab(); private: - Map *map; + Layout *layout = nullptr; Ui::PrefabCreationDialog *ui; MetatileSelection selection; }; diff --git a/include/ui/projectsettingseditor.h b/include/ui/projectsettingseditor.h index 41affa8b0..a0a269823 100644 --- a/include/ui/projectsettingseditor.h +++ b/include/ui/projectsettingseditor.h @@ -65,6 +65,8 @@ class ProjectSettingsEditor : public QMainWindow void updateMaskOverlapWarning(QLabel * warning, QList masks); QStringList getWarpBehaviorsList(); void setWarpBehaviorsList(QStringList list); + void openFilesHelp(); + void openIdentifiersHelp(); private slots: void dialogButtonClicked(QAbstractButton *button); diff --git a/include/ui/regionmapeditor.h b/include/ui/regionmapeditor.h index 3d889f881..c19416516 100644 --- a/include/ui/regionmapeditor.h +++ b/include/ui/regionmapeditor.h @@ -57,7 +57,6 @@ public slots: tsl::ordered_map region_maps; QString configFilepath; - QString mapSectionFilepath; poryjson::Json rmConfigJson; @@ -96,7 +95,7 @@ public slots: void saveConfig(); bool loadRegionMapEntries(); bool saveRegionMapEntries(); - tsl::ordered_map region_map_entries; + QMap region_map_entries; bool buildConfigDialog(); poryjson::Json configRegionMapDialog(); diff --git a/include/ui/regionmappropertiesdialog.h b/include/ui/regionmappropertiesdialog.h index 6097d842c..3abacc64f 100644 --- a/include/ui/regionmappropertiesdialog.h +++ b/include/ui/regionmappropertiesdialog.h @@ -4,7 +4,6 @@ #include "orderedjson.h" #include -#include class Project; @@ -33,7 +32,7 @@ class RegionMapPropertiesDialog : public QDialog void hideMessages(); - QString browse(QString filter, QFileDialog::FileMode mode); + QString browse(QString filter); private slots: void on_browse_tilesetImagePath_clicked(); diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index 9ce9777dd..32bdc97f7 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -8,7 +8,8 @@ #include "tileseteditormetatileselector.h" #include "tileseteditortileselector.h" #include "metatilelayersitem.h" -#include "map.h" + +class Layout; namespace Ui { class TilesetEditor; @@ -39,10 +40,10 @@ class TilesetEditor : public QMainWindow Q_OBJECT public: - explicit TilesetEditor(Project*, Map*, QWidget *parent = nullptr); + explicit TilesetEditor(Project *project, Layout *layout, QWidget *parent = nullptr); ~TilesetEditor(); - void update(Map *map, QString primaryTilsetLabel, QString secondaryTilesetLabel); - void updateMap(Map *map); + void update(Layout *layout, QString primaryTilsetLabel, QString secondaryTilesetLabel); + void updateLayout(Layout *layout); void updateTilesets(QString primaryTilsetLabel, QString secondaryTilesetLabel); bool selectMetatile(uint16_t metatileId); uint16_t getSelectedMetatileId(); @@ -155,7 +156,7 @@ private slots: MetatileLayersItem *metatileLayersItem = nullptr; PaletteEditor *paletteEditor = nullptr; Project *project = nullptr; - Map *map = nullptr; + Layout *layout = nullptr; Metatile *metatile = nullptr; Metatile *copiedMetatile = nullptr; QString copiedMetatileLabel; diff --git a/include/ui/tileseteditormetatileselector.h b/include/ui/tileseteditormetatileselector.h index f8b6011da..6b2c9a6f2 100644 --- a/include/ui/tileseteditormetatileselector.h +++ b/include/ui/tileseteditormetatileselector.h @@ -3,13 +3,14 @@ #include "selectablepixmapitem.h" #include "tileset.h" -#include "map.h" + +class Layout; class TilesetEditorMetatileSelector: public SelectablePixmapItem { Q_OBJECT public: - TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Map *map); - Map *map = nullptr; + TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Layout *layout); + Layout *layout = nullptr; void draw(); bool select(uint16_t metatileId); void setTilesets(Tileset*, Tileset*, bool draw = true); diff --git a/include/ui/tileseteditortileselector.h b/include/ui/tileseteditortileselector.h index bfbc49465..7e34d52a2 100644 --- a/include/ui/tileseteditortileselector.h +++ b/include/ui/tileseteditortileselector.h @@ -61,6 +61,7 @@ class TilesetEditorTileSelector: public SelectablePixmapItem { QPoint getTileCoords(uint16_t); QList getCurPaletteTable(); QList buildSelectedTiles(int, int, QList); + QImage buildImage(int tileIdStart, int numTiles); void drawUnused(); diff --git a/include/ui/updatepromoter.h b/include/ui/updatepromoter.h index 8b67c69e7..de73bcdd3 100644 --- a/include/ui/updatepromoter.h +++ b/include/ui/updatepromoter.h @@ -17,7 +17,7 @@ class UpdatePromoter : public QDialog public: explicit UpdatePromoter(QWidget *parent, NetworkAccessManager *manager); - ~UpdatePromoter() {}; + ~UpdatePromoter(); void checkForUpdates(); void updatePreferences(); diff --git a/include/ui/wildmonchart.h b/include/ui/wildmonchart.h index d4e3d83f0..3be7e4686 100644 --- a/include/ui/wildmonchart.h +++ b/include/ui/wildmonchart.h @@ -69,7 +69,7 @@ public slots: void applySpeciesColors(const QList &); QChart::ChartTheme currentTheme() const; void updateTheme(); - void limitChartAnimation(QChart*); + void limitChartAnimation(); void showHelpDialog(); }; diff --git a/porymap.pro b/porymap.pro index 691862888..611b61792 100644 --- a/porymap.pro +++ b/porymap.pro @@ -33,6 +33,7 @@ SOURCES += src/core/block.cpp \ src/core/bitpacker.cpp \ src/core/blockdata.cpp \ src/core/events.cpp \ + src/core/filedialog.cpp \ src/core/heallocation.cpp \ src/core/imageexport.cpp \ src/core/map.cpp \ @@ -51,7 +52,6 @@ SOURCES += src/core/block.cpp \ src/core/editcommands.cpp \ src/lib/fex/lexer.cpp \ src/lib/fex/parser.cpp \ - src/lib/fex/parser_util.cpp \ src/lib/orderedjson.cpp \ src/core/regionmapeditcommands.cpp \ src/scriptapi/apimap.cpp \ @@ -59,6 +59,7 @@ SOURCES += src/core/block.cpp \ src/scriptapi/apiutility.cpp \ src/scriptapi/scripting.cpp \ src/ui/aboutporymap.cpp \ + src/ui/colorinputwidget.cpp \ src/ui/connectionslistitem.cpp \ src/ui/customscriptseditor.cpp \ src/ui/customscriptslistitem.cpp \ @@ -68,6 +69,7 @@ SOURCES += src/core/block.cpp \ src/ui/collisionpixmapitem.cpp \ src/ui/connectionpixmapitem.cpp \ src/ui/currentselectedmetatilespixmapitem.cpp \ + src/ui/gridsettings.cpp \ src/ui/newmapconnectiondialog.cpp \ src/ui/overlay.cpp \ src/ui/prefab.cpp \ @@ -77,14 +79,16 @@ SOURCES += src/core/block.cpp \ src/ui/cursortilerect.cpp \ src/ui/customattributestable.cpp \ src/ui/eventframes.cpp \ + src/ui/eventfilters.cpp \ src/ui/filterchildrenproxymodel.cpp \ + src/ui/maplistmodels.cpp \ + src/ui/maplisttoolbar.cpp \ src/ui/graphicsview.cpp \ src/ui/imageproviders.cpp \ - src/ui/mappixmapitem.cpp \ + src/ui/layoutpixmapitem.cpp \ src/ui/prefabcreationdialog.cpp \ src/ui/regionmappixmapitem.cpp \ src/ui/citymappixmapitem.cpp \ - src/ui/mapsceneeventfilter.cpp \ src/ui/metatilelayersitem.cpp \ src/ui/metatileselector.cpp \ src/ui/movablerect.cpp \ @@ -129,6 +133,7 @@ HEADERS += include/core/block.h \ include/core/bitpacker.h \ include/core/blockdata.h \ include/core/events.h \ + include/core/filedialog.h \ include/core/heallocation.h \ include/core/history.h \ include/core/imageexport.h \ @@ -152,7 +157,6 @@ HEADERS += include/core/block.h \ include/lib/fex/define_statement.h \ include/lib/fex/lexer.h \ include/lib/fex/parser.h \ - include/lib/fex/parser_util.h \ include/lib/orderedmap.h \ include/lib/orderedjson.h \ include/ui/aboutporymap.h \ @@ -165,6 +169,7 @@ HEADERS += include/core/block.h \ include/ui/collisionpixmapitem.h \ include/ui/connectionpixmapitem.h \ include/ui/currentselectedmetatilespixmapitem.h \ + include/ui/gridsettings.h \ include/ui/newmapconnectiondialog.h \ include/ui/prefabframe.h \ include/ui/projectsettingseditor.h \ @@ -173,15 +178,18 @@ HEADERS += include/core/block.h \ include/ui/cursortilerect.h \ include/ui/customattributestable.h \ include/ui/eventframes.h \ + include/ui/eventfilters.h \ include/ui/filterchildrenproxymodel.h \ + include/ui/maplistmodels.h \ + include/ui/maplisttoolbar.h \ include/ui/graphicsview.h \ include/ui/imageproviders.h \ - include/ui/mappixmapitem.h \ + include/ui/layoutpixmapitem.h \ include/ui/mapview.h \ include/ui/prefabcreationdialog.h \ include/ui/regionmappixmapitem.h \ include/ui/citymappixmapitem.h \ - include/ui/mapsceneeventfilter.h \ + include/ui/colorinputwidget.h \ include/ui/metatilelayersitem.h \ include/ui/metatileselector.h \ include/ui/movablerect.h \ @@ -226,7 +234,10 @@ HEADERS += include/core/block.h \ include/ui/wildmonchart.h FORMS += forms/mainwindow.ui \ + forms/colorinputwidget.ui \ forms/connectionslistitem.ui \ + forms/gridsettingsdialog.ui \ + forms/maplisttoolbar.ui \ forms/newmapconnectiondialog.ui \ forms/prefabcreationdialog.ui \ forms/prefabframe.ui \ diff --git a/resources/icons/application_form_edit.ico b/resources/icons/application_form_edit.ico index 7bb403eab..5d9cc7daf 100644 Binary files a/resources/icons/application_form_edit.ico and b/resources/icons/application_form_edit.ico differ diff --git a/resources/icons/chart_bar.ico b/resources/icons/chart_bar.ico new file mode 100755 index 000000000..a66163a43 Binary files /dev/null and b/resources/icons/chart_bar.ico differ diff --git a/resources/icons/collapse_all.ico b/resources/icons/collapse_all.ico index f6c7f3158..806f2435f 100644 Binary files a/resources/icons/collapse_all.ico and b/resources/icons/collapse_all.ico differ diff --git a/resources/icons/connections.ico b/resources/icons/connections.ico new file mode 100644 index 000000000..effb20c98 Binary files /dev/null and b/resources/icons/connections.ico differ diff --git a/resources/icons/expand_all.ico b/resources/icons/expand_all.ico index 0707936c5..ca913a132 100644 Binary files a/resources/icons/expand_all.ico and b/resources/icons/expand_all.ico differ diff --git a/resources/icons/folder_add.ico b/resources/icons/folder_add.ico new file mode 100755 index 000000000..d881adf8e Binary files /dev/null and b/resources/icons/folder_add.ico differ diff --git a/resources/icons/folder_closed_map.ico b/resources/icons/folder_closed_map.ico index 27f9810b5..d299acb70 100644 Binary files a/resources/icons/folder_closed_map.ico and b/resources/icons/folder_closed_map.ico differ diff --git a/resources/icons/folder_eye_closed.ico b/resources/icons/folder_eye_closed.ico new file mode 100644 index 000000000..354abadb4 Binary files /dev/null and b/resources/icons/folder_eye_closed.ico differ diff --git a/resources/icons/folder_eye_open.ico b/resources/icons/folder_eye_open.ico new file mode 100644 index 000000000..15e7175f3 Binary files /dev/null and b/resources/icons/folder_eye_open.ico differ diff --git a/resources/icons/link.ico b/resources/icons/link.ico new file mode 100755 index 000000000..b0c35b59a Binary files /dev/null and b/resources/icons/link.ico differ diff --git a/resources/icons/link_broken.ico b/resources/icons/link_broken.ico new file mode 100755 index 000000000..fd7774ed5 Binary files /dev/null and b/resources/icons/link_broken.ico differ diff --git a/resources/icons/lock_edit.ico b/resources/icons/lock_edit.ico new file mode 100644 index 000000000..f30f42491 Binary files /dev/null and b/resources/icons/lock_edit.ico differ diff --git a/resources/icons/map_grayed.ico b/resources/icons/map_grayed.ico new file mode 100644 index 000000000..86c3f5fb7 Binary files /dev/null and b/resources/icons/map_grayed.ico differ diff --git a/resources/icons/minimap.ico b/resources/icons/minimap.ico new file mode 100644 index 000000000..548c7c8fc Binary files /dev/null and b/resources/icons/minimap.ico differ diff --git a/resources/icons/unlock_edit.ico b/resources/icons/unlock_edit.ico new file mode 100644 index 000000000..85a99e980 Binary files /dev/null and b/resources/icons/unlock_edit.ico differ diff --git a/resources/images.qrc b/resources/images.qrc index 73ed06203..888c5d9db 100644 --- a/resources/images.qrc +++ b/resources/images.qrc @@ -1,6 +1,7 @@ icons/add.ico + icons/chart_bar.ico icons/collapse_all.ico icons/cursor.ico icons/delete.ico @@ -10,16 +11,24 @@ icons/file_put.ico icons/fill_color_cursor.ico icons/fill_color.ico + icons/folder_add.ico icons/folder_closed_map.ico icons/folder_closed.ico + icons/folder_eye_closed.ico + icons/folder_eye_open.ico icons/folder_map_edited.ico icons/folder_map_opened.ico icons/folder_map.ico icons/folder.ico + icons/lock_edit.ico + icons/unlock_edit.ico icons/help.ico + icons/link_broken.ico + icons/link.ico icons/map_edited.ico icons/map_opened.ico icons/map.ico + icons/map_grayed.ico icons/move.ico icons/pencil_cursor.ico icons/pencil.ico @@ -35,7 +44,10 @@ icons/sort_map.ico icons/sort_number.ico icons/tall_grass.ico + icons/minimap.ico icons/viewsprites.ico + icons/application_form_edit.ico + icons/connections.ico icons/ui/dark_checkbox_checked_disabled.png icons/ui/dark_checkbox_checked_disabled@2x.png icons/ui/dark_checkbox_checked.png diff --git a/src/config.cpp b/src/config.cpp index ef18f3605..3d604da60 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -110,7 +110,6 @@ const QMap> ProjectConfig::defaultIde {ProjectIdentifier::define_map_empty, {"define_map_empty", "UNDEFINED"}}, {ProjectIdentifier::define_map_section_prefix, {"define_map_section_prefix", "MAPSEC_"}}, {ProjectIdentifier::define_map_section_empty, {"define_map_section_empty", "NONE"}}, - {ProjectIdentifier::define_map_section_count, {"define_map_section_count", "COUNT"}}, {ProjectIdentifier::define_species_prefix, {"define_species_prefix", "SPECIES_"}}, // Regex {ProjectIdentifier::regex_behaviors, {"regex_behaviors", "\\bMB_"}}, @@ -167,7 +166,6 @@ const QMap> ProjectConfig::defaultPaths {ProjectFilePath::constants_obj_event_movement, { "constants_obj_event_movement", "include/constants/event_object_movement.h"}}, {ProjectFilePath::constants_obj_events, { "constants_obj_events", "include/constants/event_objects.h"}}, {ProjectFilePath::constants_event_bg, { "constants_event_bg", "include/constants/event_bg.h"}}, - {ProjectFilePath::constants_region_map_sections, { "constants_region_map_sections", "include/constants/region_map_sections.h"}}, {ProjectFilePath::constants_metatile_labels, { "constants_metatile_labels", "include/constants/metatile_labels.h"}}, {ProjectFilePath::constants_metatile_behaviors, { "constants_metatile_behaviors", "include/constants/metatile_behaviors.h"}}, {ProjectFilePath::constants_species, { "constants_species", "include/constants/species.h"}}, @@ -208,7 +206,6 @@ void KeyValueConfigBase::load() { } QTextStream in(&file); - QList configLines; static const QRegularExpression re("^(?[^=]+)=(?.*)$"); while (!in.atEnd()) { QString line = in.readLine().trimmed(); @@ -279,18 +276,6 @@ uint32_t KeyValueConfigBase::getConfigUint32(QString key, QString value, uint32_ return qMin(max, qMax(min, result)); } -const QMap mapSortOrderMap = { - {MapSortOrder::Group, "group"}, - {MapSortOrder::Layout, "layout"}, - {MapSortOrder::Area, "area"}, -}; - -const QMap mapSortOrderReverseMap = { - {"group", MapSortOrder::Group}, - {"layout", MapSortOrder::Layout}, - {"area", MapSortOrder::Area}, -}; - PorymapConfig porymapConfig; QString PorymapConfig::getConfigFilepath() { @@ -315,14 +300,8 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { this->reopenOnLaunch = getConfigBool(key, value); } else if (key == "pretty_cursors") { this->prettyCursors = getConfigBool(key, value); - } else if (key == "map_sort_order") { - QString sortOrder = value.toLower(); - if (mapSortOrderReverseMap.contains(sortOrder)) { - this->mapSortOrder = mapSortOrderReverseMap.value(sortOrder); - } else { - this->mapSortOrder = MapSortOrder::Group; - logWarn(QString("Invalid config value for map_sort_order: '%1'. Must be 'group', 'area', or 'layout'.").arg(value)); - } + } else if (key == "map_list_tab") { + this->mapListTab = getConfigInteger(key, value, 0, 2, 0); } else if (key == "main_window_geometry") { this->mainWindowGeometry = bytesFromString(value); } else if (key == "main_window_state") { @@ -439,7 +418,7 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("project_manually_closed", this->projectManuallyClosed ? "1" : "0"); map.insert("reopen_on_launch", this->reopenOnLaunch ? "1" : "0"); map.insert("pretty_cursors", this->prettyCursors ? "1" : "0"); - map.insert("map_sort_order", mapSortOrderMap.value(this->mapSortOrder)); + map.insert("map_list_tab", QString::number(this->mapListTab)); map.insert("main_window_geometry", stringFromByteArray(this->mainWindowGeometry)); map.insert("main_window_state", stringFromByteArray(this->mainWindowState)); map.insert("map_splitter_state", stringFromByteArray(this->mapSplitterState)); @@ -741,8 +720,8 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { } else if (key == "enable_map_allow_flags") { this->mapAllowFlagsEnabled = getConfigBool(key, value); #ifdef CONFIG_BACKWARDS_COMPATABILITY - } else if (key == "recent_map") { - userConfig.recentMap = value; + } else if (key == "recent_map_or_layout") { + userConfig.recentMapOrLayout = value; } else if (key == "use_encounter_json") { userConfig.useEncounterJson = getConfigBool(key, value); } else if (key == "custom_scripts") { @@ -1036,8 +1015,8 @@ QString UserConfig::getConfigFilepath() { } void UserConfig::parseConfigKeyValue(QString key, QString value) { - if (key == "recent_map") { - this->recentMap = value; + if (key == "recent_map_or_layout") { + this->recentMapOrLayout = value; } else if (key == "use_encounter_json") { this->useEncounterJson = getConfigBool(key, value); } else if (key == "custom_scripts") { @@ -1053,14 +1032,13 @@ void UserConfig::setUnreadKeys() { QMap UserConfig::getKeyValueMap() { QMap map; - map.insert("recent_map", this->recentMap); + map.insert("recent_map_or_layout", this->recentMapOrLayout); map.insert("use_encounter_json", QString::number(this->useEncounterJson)); map.insert("custom_scripts", this->outputCustomScripts()); return map; } void UserConfig::init() { - QString dirName = QDir(this->projectDir).dirName().toLower(); this->useEncounterJson = true; this->customScripts.clear(); } diff --git a/src/core/editcommands.cpp b/src/core/editcommands.cpp index f557ddddb..c500c8c0d 100644 --- a/src/core/editcommands.cpp +++ b/src/core/editcommands.cpp @@ -1,5 +1,4 @@ #include "editcommands.h" -#include "mappixmapitem.h" #include "draggablepixmapitem.h" #include "bordermetatilespixmapitem.h" #include "editor.h" @@ -25,17 +24,17 @@ int getEventTypeMask(QList events) { return eventTypeMask; } -void renderMapBlocks(Map *map, bool ignoreCache = false) { - map->mapItem->draw(ignoreCache); - map->collisionItem->draw(ignoreCache); +void renderBlocks(Layout *layout, bool ignoreCache = false) { + layout->layoutItem->draw(ignoreCache); + layout->collisionItem->draw(ignoreCache); } -PaintMetatile::PaintMetatile(Map *map, +PaintMetatile::PaintMetatile(Layout *layout, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, unsigned actionId, QUndoCommand *parent) : QUndoCommand(parent) { setText("Paint Metatiles"); - this->map = map; + this->layout = layout; this->oldMetatiles = oldMetatiles; this->newMetatiles = newMetatiles; @@ -45,23 +44,23 @@ PaintMetatile::PaintMetatile(Map *map, void PaintMetatile::redo() { QUndoCommand::redo(); - if (!map) return; + if (!layout) return; - map->setBlockdata(newMetatiles, true); + layout->setBlockdata(newMetatiles, true); - map->layout->lastCommitBlocks.blocks = map->layout->blockdata; + layout->lastCommitBlocks.blocks = layout->blockdata; - renderMapBlocks(map); + renderBlocks(layout); } void PaintMetatile::undo() { - if (!map) return; + if (!layout) return; - map->setBlockdata(oldMetatiles, true); + layout->setBlockdata(oldMetatiles, true); - map->layout->lastCommitBlocks.blocks = map->layout->blockdata; + layout->lastCommitBlocks.blocks = layout->blockdata; - renderMapBlocks(map); + renderBlocks(layout); QUndoCommand::undo(); } @@ -69,7 +68,7 @@ void PaintMetatile::undo() { bool PaintMetatile::mergeWith(const QUndoCommand *command) { const PaintMetatile *other = static_cast(command); - if (map != other->map) + if (layout != other->layout) return false; if (actionId != other->actionId) @@ -84,12 +83,12 @@ bool PaintMetatile::mergeWith(const QUndoCommand *command) { ************************************************************************ ******************************************************************************/ -PaintBorder::PaintBorder(Map *map, +PaintBorder::PaintBorder(Layout *layout, const Blockdata &oldBorder, const Blockdata &newBorder, unsigned actionId, QUndoCommand *parent) : QUndoCommand(parent) { setText("Paint Border"); - this->map = map; + this->layout = layout; this->oldBorder = oldBorder; this->newBorder = newBorder; @@ -99,23 +98,23 @@ PaintBorder::PaintBorder(Map *map, void PaintBorder::redo() { QUndoCommand::redo(); - if (!map) return; + if (!layout) return; - map->setBorderBlockData(newBorder, true); + layout->setBorderBlockData(newBorder, true); - map->layout->lastCommitBlocks.border = map->layout->border; + layout->lastCommitBlocks.border = layout->border; - map->borderItem->draw(); + layout->borderItem->draw(); } void PaintBorder::undo() { - if (!map) return; + if (!layout) return; - map->setBorderBlockData(oldBorder, true); + layout->setBorderBlockData(oldBorder, true); - map->layout->lastCommitBlocks.border = map->layout->border; + layout->lastCommitBlocks.border = layout->border; - map->borderItem->draw(); + layout->borderItem->draw(); QUndoCommand::undo(); } @@ -124,12 +123,12 @@ void PaintBorder::undo() { ************************************************************************ ******************************************************************************/ -ShiftMetatiles::ShiftMetatiles(Map *map, +ShiftMetatiles::ShiftMetatiles(Layout *layout, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, unsigned actionId, QUndoCommand *parent) : QUndoCommand(parent) { setText("Shift Metatiles"); - this->map = map; + this->layout = layout; this->oldMetatiles = oldMetatiles; this->newMetatiles = newMetatiles; @@ -139,23 +138,23 @@ ShiftMetatiles::ShiftMetatiles(Map *map, void ShiftMetatiles::redo() { QUndoCommand::redo(); - if (!map) return; + if (!layout) return; - map->setBlockdata(newMetatiles, true); + layout->setBlockdata(newMetatiles, true); - map->layout->lastCommitBlocks.blocks = map->layout->blockdata; + layout->lastCommitBlocks.blocks = layout->blockdata; - renderMapBlocks(map, true); + renderBlocks(layout, true); } void ShiftMetatiles::undo() { - if (!map) return; + if (!layout) return; - map->setBlockdata(oldMetatiles, true); + layout->setBlockdata(oldMetatiles, true); - map->layout->lastCommitBlocks.blocks = map->layout->blockdata; + layout->lastCommitBlocks.blocks = layout->blockdata; - renderMapBlocks(map, true); + renderBlocks(layout, true); QUndoCommand::undo(); } @@ -163,7 +162,7 @@ void ShiftMetatiles::undo() { bool ShiftMetatiles::mergeWith(const QUndoCommand *command) { const ShiftMetatiles *other = static_cast(command); - if (this->map != other->map) + if (this->layout != other->layout) return false; if (actionId != other->actionId) @@ -178,20 +177,20 @@ bool ShiftMetatiles::mergeWith(const QUndoCommand *command) { ************************************************************************ ******************************************************************************/ -ResizeMap::ResizeMap(Map *map, QSize oldMapDimensions, QSize newMapDimensions, +ResizeLayout::ResizeLayout(Layout *layout, QSize oldLayoutDimensions, QSize newLayoutDimensions, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, QSize oldBorderDimensions, QSize newBorderDimensions, const Blockdata &oldBorder, const Blockdata &newBorder, QUndoCommand *parent) : QUndoCommand(parent) { setText("Resize Map"); - this->map = map; + this->layout = layout; - this->oldMapWidth = oldMapDimensions.width(); - this->oldMapHeight = oldMapDimensions.height(); + this->oldLayoutWidth = oldLayoutDimensions.width(); + this->oldLayoutHeight = oldLayoutDimensions.height(); - this->newMapWidth = newMapDimensions.width(); - this->newMapHeight = newMapDimensions.height(); + this->newLayoutWidth = newLayoutDimensions.width(); + this->newLayoutHeight = newLayoutDimensions.height(); this->oldMetatiles = oldMetatiles; this->newMetatiles = newMetatiles; @@ -206,36 +205,36 @@ ResizeMap::ResizeMap(Map *map, QSize oldMapDimensions, QSize newMapDimensions, this->newBorder = newBorder; } -void ResizeMap::redo() { +void ResizeLayout::redo() { QUndoCommand::redo(); - if (!map) return; + if (!layout) return; - map->layout->blockdata = newMetatiles; - map->setDimensions(newMapWidth, newMapHeight, false, true); + layout->blockdata = newMetatiles; + layout->setDimensions(newLayoutWidth, newLayoutHeight, false, true); - map->layout->border = newBorder; - map->setBorderDimensions(newBorderWidth, newBorderHeight, false, true); + layout->border = newBorder; + layout->setBorderDimensions(newBorderWidth, newBorderHeight, false, true); - map->layout->lastCommitBlocks.mapDimensions = QSize(map->getWidth(), map->getHeight()); - map->layout->lastCommitBlocks.borderDimensions = QSize(map->getBorderWidth(), map->getBorderHeight()); + layout->lastCommitBlocks.layoutDimensions = QSize(layout->getWidth(), layout->getHeight()); + layout->lastCommitBlocks.borderDimensions = QSize(layout->getBorderWidth(), layout->getBorderHeight()); - map->mapNeedsRedrawing(); + layout->needsRedrawing(); } -void ResizeMap::undo() { - if (!map) return; +void ResizeLayout::undo() { + if (!layout) return; - map->layout->blockdata = oldMetatiles; - map->setDimensions(oldMapWidth, oldMapHeight, false, true); + layout->blockdata = oldMetatiles; + layout->setDimensions(oldLayoutWidth, oldLayoutHeight, false, true); - map->layout->border = oldBorder; - map->setBorderDimensions(oldBorderWidth, oldBorderHeight, false, true); + layout->border = oldBorder; + layout->setBorderDimensions(oldBorderWidth, oldBorderHeight, false, true); - map->layout->lastCommitBlocks.mapDimensions = QSize(map->getWidth(), map->getHeight()); - map->layout->lastCommitBlocks.borderDimensions = QSize(map->getBorderWidth(), map->getBorderHeight()); + layout->lastCommitBlocks.layoutDimensions = QSize(layout->getWidth(), layout->getHeight()); + layout->lastCommitBlocks.borderDimensions = QSize(layout->getBorderWidth(), layout->getBorderHeight()); - map->mapNeedsRedrawing(); + layout->needsRedrawing(); QUndoCommand::undo(); } @@ -487,23 +486,23 @@ int EventPaste::id() const { ************************************************************************ ******************************************************************************/ -ScriptEditMap::ScriptEditMap(Map *map, - QSize oldMapDimensions, QSize newMapDimensions, +ScriptEditLayout::ScriptEditLayout(Layout *layout, + QSize oldLayoutDimensions, QSize newLayoutDimensions, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, QSize oldBorderDimensions, QSize newBorderDimensions, const Blockdata &oldBorder, const Blockdata &newBorder, QUndoCommand *parent) : QUndoCommand(parent) { - setText("Script Edit Map"); + setText("Script Edit Layout"); - this->map = map; + this->layout = layout; this->newMetatiles = newMetatiles; this->oldMetatiles = oldMetatiles; - this->oldMapWidth = oldMapDimensions.width(); - this->oldMapHeight = oldMapDimensions.height(); - this->newMapWidth = newMapDimensions.width(); - this->newMapHeight = newMapDimensions.height(); + this->oldLayoutWidth = oldLayoutDimensions.width(); + this->oldLayoutHeight = oldLayoutDimensions.height(); + this->newLayoutWidth = newLayoutDimensions.width(); + this->newLayoutHeight = newLayoutDimensions.height(); this->oldBorder = oldBorder; this->newBorder = newBorder; @@ -514,58 +513,58 @@ ScriptEditMap::ScriptEditMap(Map *map, this->newBorderHeight = newBorderDimensions.height(); } -void ScriptEditMap::redo() { +void ScriptEditLayout::redo() { QUndoCommand::redo(); - if (!map) return; + if (!layout) return; - if (newMapWidth != map->getWidth() || newMapHeight != map->getHeight()) { - map->layout->blockdata = newMetatiles; - map->setDimensions(newMapWidth, newMapHeight, false); + if (newLayoutWidth != layout->getWidth() || newLayoutHeight != layout->getHeight()) { + layout->blockdata = newMetatiles; + layout->setDimensions(newLayoutWidth, newLayoutHeight, false); } else { - map->setBlockdata(newMetatiles); + layout->setBlockdata(newMetatiles); } - if (newBorderWidth != map->getBorderWidth() || newBorderHeight != map->getBorderHeight()) { - map->layout->border = newBorder; - map->setBorderDimensions(newBorderWidth, newBorderHeight, false); + if (newBorderWidth != layout->getBorderWidth() || newBorderHeight != layout->getBorderHeight()) { + layout->border = newBorder; + layout->setBorderDimensions(newBorderWidth, newBorderHeight, false); } else { - map->setBorderBlockData(newBorder); + layout->setBorderBlockData(newBorder); } - map->layout->lastCommitBlocks.blocks = newMetatiles; - map->layout->lastCommitBlocks.mapDimensions = QSize(newMapWidth, newMapHeight); - map->layout->lastCommitBlocks.border = newBorder; - map->layout->lastCommitBlocks.borderDimensions = QSize(newBorderWidth, newBorderHeight); + layout->lastCommitBlocks.blocks = newMetatiles; + layout->lastCommitBlocks.layoutDimensions = QSize(newLayoutWidth, newLayoutHeight); + layout->lastCommitBlocks.border = newBorder; + layout->lastCommitBlocks.borderDimensions = QSize(newBorderWidth, newBorderHeight); - renderMapBlocks(map); - map->borderItem->draw(); + renderBlocks(layout, true); + layout->borderItem->draw(); } -void ScriptEditMap::undo() { - if (!map) return; +void ScriptEditLayout::undo() { + if (!layout) return; - if (oldMapWidth != map->getWidth() || oldMapHeight != map->getHeight()) { - map->layout->blockdata = oldMetatiles; - map->setDimensions(oldMapWidth, oldMapHeight, false); + if (oldLayoutWidth != layout->getWidth() || oldLayoutHeight != layout->getHeight()) { + layout->blockdata = oldMetatiles; + layout->setDimensions(oldLayoutWidth, oldLayoutHeight, false); } else { - map->setBlockdata(oldMetatiles); + layout->setBlockdata(oldMetatiles); } - if (oldBorderWidth != map->getBorderWidth() || oldBorderHeight != map->getBorderHeight()) { - map->layout->border = oldBorder; - map->setBorderDimensions(oldBorderWidth, oldBorderHeight, false); + if (oldBorderWidth != layout->getBorderWidth() || oldBorderHeight != layout->getBorderHeight()) { + layout->border = oldBorder; + layout->setBorderDimensions(oldBorderWidth, oldBorderHeight, false); } else { - map->setBorderBlockData(oldBorder); + layout->setBorderBlockData(oldBorder); } - map->layout->lastCommitBlocks.blocks = oldMetatiles; - map->layout->lastCommitBlocks.mapDimensions = QSize(oldMapWidth, oldMapHeight); - map->layout->lastCommitBlocks.border = oldBorder; - map->layout->lastCommitBlocks.borderDimensions = QSize(oldBorderWidth, oldBorderHeight); + layout->lastCommitBlocks.blocks = oldMetatiles; + layout->lastCommitBlocks.layoutDimensions = QSize(oldLayoutWidth, oldLayoutHeight); + layout->lastCommitBlocks.border = oldBorder; + layout->lastCommitBlocks.borderDimensions = QSize(oldBorderWidth, oldBorderHeight); - renderMapBlocks(map); - map->borderItem->draw(); + renderBlocks(layout, true); + layout->borderItem->draw(); QUndoCommand::undo(); } diff --git a/src/core/events.cpp b/src/core/events.cpp index 13be4153a..89416eaef 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -146,10 +146,13 @@ void Event::loadPixmap(Project *) { this->pixmap = pixmap ? *pixmap : QPixmap(); } -void Event::setIcons() { +void Event::clearIcons() { qDeleteAll(icons); icons.clear(); +} +void Event::setIcons() { + clearIcons(); const int w = 16; const int h = 16; static const QPixmap defaultIcons = QPixmap(":/images/Entities_16x16.png"); diff --git a/src/core/filedialog.cpp b/src/core/filedialog.cpp new file mode 100644 index 000000000..94826090d --- /dev/null +++ b/src/core/filedialog.cpp @@ -0,0 +1,51 @@ +#include "filedialog.h" + +QString FileDialog::prevDirectory; + + QString FileDialog::getDirectoryFromInput(const QString &dir) { + if (dir.isEmpty()) + return FileDialog::prevDirectory; + return dir; + } + +void FileDialog::setDirectoryFromFile(const QString &fileName) { + if (!fileName.isEmpty()) + FileDialog::prevDirectory = QFileInfo(fileName).absolutePath(); +} + +void FileDialog::restoreFocus(QWidget *parent) { + if (parent) { + parent->raise(); + parent->activateWindow(); + } +} + +QString FileDialog::getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) { + const QString fileName = QFileDialog::getOpenFileName(parent, caption, getDirectoryFromInput(dir), filter, selectedFilter, options); + setDirectoryFromFile(fileName); + restoreFocus(parent); + return fileName; +} + +QStringList FileDialog::getOpenFileNames(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) { + const QStringList fileNames = QFileDialog::getOpenFileNames(parent, caption, getDirectoryFromInput(dir), filter, selectedFilter, options); + if (!fileNames.isEmpty()) + setDirectoryFromFile(fileNames.last()); + restoreFocus(parent); + return fileNames; +} + +QString FileDialog::getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) { + const QString fileName = QFileDialog::getSaveFileName(parent, caption, getDirectoryFromInput(dir), filter, selectedFilter, options); + setDirectoryFromFile(fileName); + restoreFocus(parent); + return fileName; +} + +QString FileDialog::getExistingDirectory(QWidget *parent, const QString &caption, const QString &dir, QFileDialog::Options options) { + const QString existingDir = QFileDialog::getExistingDirectory(parent, caption, getDirectoryFromInput(dir), options); + if (!existingDir.isEmpty()) + setDirectory(existingDir); + restoreFocus(parent); + return existingDir; +} diff --git a/src/core/map.cpp b/src/core/map.cpp index 86df7947c..2a7d96dcc 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -28,6 +28,13 @@ void Map::setName(QString mapName) { scriptsLoaded = false; } +void Map::setLayout(Layout *layout) { + this->layout = layout; + if (layout) { + this->layoutId = layout->id; + } +} + QString Map::mapConstantFromName(QString mapName, bool includePrefix) { // Transform map names of the form 'GraniteCave_B1F` into map constants like 'MAP_GRANITE_CAVE_B1F'. static const QRegularExpression caseChange("([a-z])([A-Z])"); @@ -60,165 +67,10 @@ int Map::getBorderHeight() { return layout->getBorderHeight(); } -bool Map::mapBlockChanged(int i, const Blockdata &cache) { - if (cache.length() <= i) - return true; - if (layout->blockdata.length() <= i) - return true; - - return layout->blockdata.at(i) != cache.at(i); -} - -bool Map::borderBlockChanged(int i, const Blockdata &cache) { - if (cache.length() <= i) - return true; - if (layout->border.length() <= i) - return true; - - return layout->border.at(i) != cache.at(i); -} - -void Map::clearBorderCache() { - layout->cached_border.clear(); -} - -void Map::cacheBorder() { - layout->cached_border.clear(); - for (const auto &block : layout->border) - layout->cached_border.append(block); -} - -void Map::cacheBlockdata() { - layout->cached_blockdata.clear(); - for (const auto &block : layout->blockdata) - layout->cached_blockdata.append(block); -} - -void Map::cacheCollision() { - layout->cached_collision.clear(); - for (const auto &block : layout->blockdata) - layout->cached_collision.append(block); -} - -QPixmap Map::renderCollision(bool ignoreCache) { - bool changed_any = false; - int width_ = getWidth(); - int height_ = getHeight(); - if (collision_image.isNull() || collision_image.width() != width_ * 16 || collision_image.height() != height_ * 16) { - collision_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); - changed_any = true; - } - if (layout->blockdata.isEmpty() || !width_ || !height_) { - collision_pixmap = collision_pixmap.fromImage(collision_image); - return collision_pixmap; - } - QPainter painter(&collision_image); - for (int i = 0; i < layout->blockdata.length(); i++) { - if (!ignoreCache && !mapBlockChanged(i, layout->cached_collision)) { - continue; - } - changed_any = true; - Block block = layout->blockdata.at(i); - QImage collision_metatile_image = getCollisionMetatileImage(block); - int map_y = width_ ? i / width_ : 0; - int map_x = width_ ? i % width_ : 0; - QPoint metatile_origin = QPoint(map_x * 16, map_y * 16); - painter.drawImage(metatile_origin, collision_metatile_image); - } - painter.end(); - cacheCollision(); - if (changed_any) { - collision_pixmap = collision_pixmap.fromImage(collision_image); - } - return collision_pixmap; -} - -QPixmap Map::render(bool ignoreCache, MapLayout * fromLayout, QRect bounds) { - bool changed_any = false; - int width_ = getWidth(); - int height_ = getHeight(); - if (image.isNull() || image.width() != width_ * 16 || image.height() != height_ * 16) { - image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); - changed_any = true; - } - if (layout->blockdata.isEmpty() || !width_ || !height_) { - pixmap = pixmap.fromImage(image); - return pixmap; - } - - QPainter painter(&image); - for (int i = 0; i < layout->blockdata.length(); i++) { - if (!ignoreCache && !mapBlockChanged(i, layout->cached_blockdata)) { - continue; - } - changed_any = true; - int map_y = width_ ? i / width_ : 0; - int map_x = width_ ? i % width_ : 0; - if (bounds.isValid() && !bounds.contains(map_x, map_y)) { - continue; - } - QPoint metatile_origin = QPoint(map_x * 16, map_y * 16); - Block block = layout->blockdata.at(i); - QImage metatile_image = getMetatileImage( - block.metatileId(), - fromLayout ? fromLayout->tileset_primary : layout->tileset_primary, - fromLayout ? fromLayout->tileset_secondary : layout->tileset_secondary, - metatileLayerOrder, - metatileLayerOpacity - ); - painter.drawImage(metatile_origin, metatile_image); - } - painter.end(); - if (changed_any) { - cacheBlockdata(); - pixmap = pixmap.fromImage(image); - } - - return pixmap; -} - -QPixmap Map::renderBorder(bool ignoreCache) { - bool changed_any = false, border_resized = false; - int width_ = getBorderWidth(); - int height_ = getBorderHeight(); - if (layout->border_image.isNull()) { - layout->border_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); - changed_any = true; - } - if (layout->border_image.width() != width_ * 16 || layout->border_image.height() != height_ * 16) { - layout->border_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); - border_resized = true; - } - if (layout->border.isEmpty()) { - layout->border_pixmap = layout->border_pixmap.fromImage(layout->border_image); - return layout->border_pixmap; - } - QPainter painter(&layout->border_image); - for (int i = 0; i < layout->border.length(); i++) { - if (!ignoreCache && (!border_resized && !borderBlockChanged(i, layout->cached_border))) { - continue; - } - - changed_any = true; - Block block = layout->border.at(i); - uint16_t metatileId = block.metatileId(); - QImage metatile_image = getMetatileImage(metatileId, layout->tileset_primary, layout->tileset_secondary, metatileLayerOrder, metatileLayerOpacity); - int map_y = width_ ? i / width_ : 0; - int map_x = width_ ? i % width_ : 0; - painter.drawImage(QPoint(map_x * 16, map_y * 16), metatile_image); - } - painter.end(); - if (changed_any) { - cacheBorder(); - layout->border_pixmap = layout->border_pixmap.fromImage(layout->border_image); - } - return layout->border_pixmap; -} - // 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) { +QRect Map::getConnectionRect(const QString &direction, Layout * fromLayout) { int x = 0, y = 0; int w = getWidth(), h = getHeight(); @@ -244,7 +96,7 @@ QRect Map::getConnectionRect(const QString &direction, MapLayout * fromLayout) { return QRect(x, y, w, h); } -QPixmap Map::renderConnection(const QString &direction, MapLayout * fromLayout) { +QPixmap Map::renderConnection(const QString &direction, Layout * fromLayout) { QRect bounds = getConnectionRect(direction, fromLayout); if (!bounds.isValid()) return QPixmap(); @@ -254,214 +106,14 @@ QPixmap Map::renderConnection(const QString &direction, MapLayout * fromLayout) 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); -} - -void Map::setNewDimensionsBlockdata(int newWidth, int newHeight) { - int oldWidth = getWidth(); - int oldHeight = getHeight(); - - Blockdata newBlockdata; - - for (int y = 0; y < newHeight; y++) - for (int x = 0; x < newWidth; x++) { - if (x < oldWidth && y < oldHeight) { - int index = y * oldWidth + x; - newBlockdata.append(layout->blockdata.value(index)); - } else { - newBlockdata.append(0); - } - } - - layout->blockdata = newBlockdata; -} - -void Map::setNewBorderDimensionsBlockdata(int newWidth, int newHeight) { - int oldWidth = getBorderWidth(); - int oldHeight = getBorderHeight(); - - Blockdata newBlockdata; - - for (int y = 0; y < newHeight; y++) - for (int x = 0; x < newWidth; x++) { - if (x < oldWidth && y < oldHeight) { - int index = y * oldWidth + x; - newBlockdata.append(layout->border.value(index)); - } else { - newBlockdata.append(0); - } - } - - layout->border = newBlockdata; -} - -void Map::setDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) { - if (setNewBlockdata) { - setNewDimensionsBlockdata(newWidth, newHeight); - } - - int oldWidth = layout->width; - int oldHeight = layout->height; - layout->width = newWidth; - layout->height = newHeight; - - if (enableScriptCallback && (oldWidth != newWidth || oldHeight != newHeight)) { - Scripting::cb_MapResized(oldWidth, oldHeight, newWidth, newHeight); - } - - emit mapDimensionsChanged(QSize(getWidth(), getHeight())); - modify(); -} - -void Map::setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) { - if (setNewBlockdata) { - setNewBorderDimensionsBlockdata(newWidth, newHeight); - } - - int oldWidth = layout->border_width; - int oldHeight = layout->border_height; - layout->border_width = newWidth; - layout->border_height = newHeight; - - if (enableScriptCallback && (oldWidth != newWidth || oldHeight != newHeight)) { - Scripting::cb_BorderResized(oldWidth, oldHeight, newWidth, newHeight); - } - - modify(); + QPixmap connectionPixmap = this->layout->render(true, fromLayout, bounds); + return connectionPixmap.copy(bounds.x() * 16, bounds.y() * 16, bounds.width() * 16, bounds.height() * 16); } void Map::openScript(QString label) { emit openScriptRequested(label); } -bool Map::getBlock(int x, int y, Block *out) { - if (isWithinBounds(x, y)) { - int i = y * getWidth() + x; - *out = layout->blockdata.value(i); - return true; - } - return false; -} - -void Map::setBlock(int x, int y, Block block, bool enableScriptCallback) { - if (!isWithinBounds(x, y)) return; - int i = y * getWidth() + x; - if (i < layout->blockdata.size()) { - Block prevBlock = layout->blockdata.at(i); - layout->blockdata.replace(i, block); - if (enableScriptCallback) { - Scripting::cb_MetatileChanged(x, y, prevBlock, block); - } - } -} - -void Map::setBlockdata(Blockdata blockdata, bool enableScriptCallback) { - int width = getWidth(); - int size = qMin(blockdata.size(), layout->blockdata.size()); - for (int i = 0; i < size; i++) { - Block prevBlock = layout->blockdata.at(i); - Block newBlock = blockdata.at(i); - if (prevBlock != newBlock) { - layout->blockdata.replace(i, newBlock); - if (enableScriptCallback) - Scripting::cb_MetatileChanged(i % width, i / width, prevBlock, newBlock); - } - } -} - -uint16_t Map::getBorderMetatileId(int x, int y) { - int i = y * getBorderWidth() + x; - return layout->border[i].metatileId(); -} - -void Map::setBorderMetatileId(int x, int y, uint16_t metatileId, bool enableScriptCallback) { - int i = y * getBorderWidth() + x; - if (i < layout->border.size()) { - uint16_t prevMetatileId = layout->border[i].metatileId(); - layout->border[i].setMetatileId(metatileId); - if (prevMetatileId != metatileId && enableScriptCallback) { - Scripting::cb_BorderMetatileChanged(x, y, prevMetatileId, metatileId); - } - } -} - -void Map::setBorderBlockData(Blockdata blockdata, bool enableScriptCallback) { - int width = getBorderWidth(); - int size = qMin(blockdata.size(), layout->border.size()); - for (int i = 0; i < size; i++) { - Block prevBlock = layout->border.at(i); - Block newBlock = blockdata.at(i); - if (prevBlock != newBlock) { - layout->border.replace(i, newBlock); - if (enableScriptCallback) - Scripting::cb_BorderMetatileChanged(i % width, i / width, prevBlock.metatileId(), newBlock.metatileId()); - } - } -} - -void Map::_floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation) { - QList todo; - todo.append(QPoint(x, y)); - while (todo.length()) { - QPoint point = todo.takeAt(0); - x = point.x(); - y = point.y(); - Block block; - if (!getBlock(x, y, &block)) { - continue; - } - - uint old_coll = block.collision(); - uint old_elev = block.elevation(); - if (old_coll == collision && old_elev == elevation) { - continue; - } - - block.setCollision(collision); - block.setElevation(elevation); - setBlock(x, y, block, true); - if (getBlock(x + 1, y, &block) && block.collision() == old_coll && block.elevation() == old_elev) { - todo.append(QPoint(x + 1, y)); - } - if (getBlock(x - 1, y, &block) && block.collision() == old_coll && block.elevation() == old_elev) { - todo.append(QPoint(x - 1, y)); - } - if (getBlock(x, y + 1, &block) && block.collision() == old_coll && block.elevation() == old_elev) { - todo.append(QPoint(x, y + 1)); - } - if (getBlock(x, y - 1, &block) && block.collision() == old_coll && block.elevation() == old_elev) { - todo.append(QPoint(x, y - 1)); - } - } -} - -void Map::floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation) { - Block block; - if (getBlock(x, y, &block) && (block.collision() != collision || block.elevation() != elevation)) { - _floodFillCollisionElevation(x, y, collision, elevation); - } -} - -void Map::magicFillCollisionElevation(int initialX, int initialY, uint16_t collision, uint16_t elevation) { - Block block; - if (getBlock(initialX, initialY, &block) && (block.collision() != collision || block.elevation() != elevation)) { - uint old_coll = block.collision(); - uint old_elev = block.elevation(); - - for (int y = 0; y < getHeight(); y++) { - for (int x = 0; x < getWidth(); x++) { - if (getBlock(x, y, &block) && block.collision() == old_coll && block.elevation() == old_elev) { - block.setCollision(collision); - block.setElevation(elevation); - setBlock(x, y, block, true); - } - } - } - } -} - QList Map::getAllEvents() const { QList all_events; for (const auto &event_list : events) { @@ -604,8 +256,8 @@ void Map::clean() { this->hasUnsavedDataChanges = false; } -bool Map::hasUnsavedChanges() { - return !editHistory.isClean() || hasUnsavedDataChanges || !isPersistedToFile; +bool Map::hasUnsavedChanges() const { + return !editHistory.isClean() || this->layout->hasUnsavedChanges() || hasUnsavedDataChanges || !isPersistedToFile; } void Map::pruneEditHistory() { @@ -628,11 +280,3 @@ void Map::pruneEditHistory() { command->setObsolete(true); } } - -bool Map::isWithinBounds(int x, int y) { - return (x >= 0 && x < this->getWidth() && y >= 0 && y < this->getHeight()); -} - -bool Map::isWithinBorderBounds(int x, int y) { - return (x >= 0 && x < this->getBorderWidth() && y >= 0 && y < this->getBorderHeight()); -} diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 0f5deecd5..34033ac55 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -2,7 +2,35 @@ #include -QString MapLayout::layoutConstantFromName(QString mapName) { +#include "scripting.h" +#include "imageproviders.h" + + + +Layout *Layout::copy() { + Layout *layout = new Layout; + layout->copyFrom(this); + return layout; +} + +void Layout::copyFrom(Layout *other) { + this->id = other->id; + this->name = other->name; + this->width = other->width; + this->height = other->height; + this->border_width = other->border_width; + this->border_height = other->border_height; + this->border_path = other->border_path; + this->blockdata_path = other->blockdata_path; + this->tileset_primary_label = other->tileset_primary_label; + this->tileset_secondary_label = other->tileset_secondary_label; + this->tileset_primary = other->tileset_primary; + this->tileset_secondary = other->tileset_secondary; + this->blockdata = other->blockdata; + this->border = other->border; +} + +QString Layout::layoutConstantFromName(QString mapName) { // Transform map names of the form 'GraniteCave_B1F` into layout constants like 'LAYOUT_GRANITE_CAVE_B1F'. static const QRegularExpression caseChange("([a-z])([A-Z])"); QString nameWithUnderscores = mapName.replace(caseChange, "\\1_\\2"); @@ -17,18 +45,379 @@ QString MapLayout::layoutConstantFromName(QString mapName) { return constantName; } -int MapLayout::getWidth() { +int Layout::getWidth() { return width; } -int MapLayout::getHeight() { +int Layout::getHeight() { return height; } -int MapLayout::getBorderWidth() { +int Layout::getBorderWidth() { return border_width; } -int MapLayout::getBorderHeight() { +int Layout::getBorderHeight() { return border_height; } + +bool Layout::isWithinBounds(int x, int y) { + return (x >= 0 && x < this->getWidth() && y >= 0 && y < this->getHeight()); +} + +bool Layout::isWithinBorderBounds(int x, int y) { + return (x >= 0 && x < this->getBorderWidth() && y >= 0 && y < this->getBorderHeight()); +} + +bool Layout::getBlock(int x, int y, Block *out) { + if (isWithinBounds(x, y)) { + int i = y * getWidth() + x; + *out = this->blockdata.value(i); + return true; + } + return false; +} + +void Layout::setBlock(int x, int y, Block block, bool enableScriptCallback) { + if (!isWithinBounds(x, y)) return; + int i = y * getWidth() + x; + if (i < this->blockdata.size()) { + Block prevBlock = this->blockdata.at(i); + this->blockdata.replace(i, block); + if (enableScriptCallback) { + Scripting::cb_MetatileChanged(x, y, prevBlock, block); + } + } +} + +void Layout::setBlockdata(Blockdata newBlockdata, bool enableScriptCallback) { + int width = getWidth(); + int size = qMin(newBlockdata.size(), this->blockdata.size()); + for (int i = 0; i < size; i++) { + Block prevBlock = this->blockdata.at(i); + Block newBlock = newBlockdata.at(i); + if (prevBlock != newBlock) { + this->blockdata.replace(i, newBlock); + if (enableScriptCallback) + Scripting::cb_MetatileChanged(i % width, i / width, prevBlock, newBlock); + } + } +} + +void Layout::clearBorderCache() { + this->cached_border.clear(); +} + +void Layout::cacheBorder() { + this->cached_border.clear(); + for (const auto &block : this->border) + this->cached_border.append(block); +} + +void Layout::cacheBlockdata() { + this->cached_blockdata.clear(); + for (const auto &block : this->blockdata) + this->cached_blockdata.append(block); +} + +void Layout::cacheCollision() { + this->cached_collision.clear(); + for (const auto &block : this->blockdata) + this->cached_collision.append(block); +} + +bool Layout::layoutBlockChanged(int i, const Blockdata &cache) { + if (cache.length() <= i) + return true; + if (this->blockdata.length() <= i) + return true; + + return this->blockdata.at(i) != cache.at(i); +} + +uint16_t Layout::getBorderMetatileId(int x, int y) { + int i = y * getBorderWidth() + x; + return this->border[i].metatileId(); +} + +void Layout::setBorderMetatileId(int x, int y, uint16_t metatileId, bool enableScriptCallback) { + int i = y * getBorderWidth() + x; + if (i < this->border.size()) { + uint16_t prevMetatileId = this->border[i].metatileId(); + this->border[i].setMetatileId(metatileId); + if (prevMetatileId != metatileId && enableScriptCallback) { + Scripting::cb_BorderMetatileChanged(x, y, prevMetatileId, metatileId); + } + } +} + +void Layout::setBorderBlockData(Blockdata newBlockdata, bool enableScriptCallback) { + int width = getBorderWidth(); + int size = qMin(newBlockdata.size(), this->border.size()); + for (int i = 0; i < size; i++) { + Block prevBlock = this->border.at(i); + Block newBlock = newBlockdata.at(i); + if (prevBlock != newBlock) { + this->border.replace(i, newBlock); + if (enableScriptCallback) + Scripting::cb_BorderMetatileChanged(i % width, i / width, prevBlock.metatileId(), newBlock.metatileId()); + } + } +} + +void Layout::setDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) { + if (setNewBlockdata) { + setNewDimensionsBlockdata(newWidth, newHeight); + } + + int oldWidth = this->width; + int oldHeight = this->height; + this->width = newWidth; + this->height = newHeight; + + if (enableScriptCallback && (oldWidth != newWidth || oldHeight != newHeight)) { + Scripting::cb_MapResized(oldWidth, oldHeight, newWidth, newHeight); + } + + emit layoutChanged(this); + emit layoutDimensionsChanged(QSize(getWidth(), getHeight())); +} + +void Layout::setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) { + if (setNewBlockdata) { + setNewBorderDimensionsBlockdata(newWidth, newHeight); + } + + int oldWidth = this->border_width; + int oldHeight = this->border_height; + this->border_width = newWidth; + this->border_height = newHeight; + + if (enableScriptCallback && (oldWidth != newWidth || oldHeight != newHeight)) { + Scripting::cb_BorderResized(oldWidth, oldHeight, newWidth, newHeight); + } + + emit layoutChanged(this); +} + +void Layout::setNewDimensionsBlockdata(int newWidth, int newHeight) { + int oldWidth = getWidth(); + int oldHeight = getHeight(); + + Blockdata newBlockdata; + + for (int y = 0; y < newHeight; y++) + for (int x = 0; x < newWidth; x++) { + if (x < oldWidth && y < oldHeight) { + int index = y * oldWidth + x; + newBlockdata.append(this->blockdata.value(index)); + } else { + newBlockdata.append(0); + } + } + + this->blockdata = newBlockdata; +} + +void Layout::setNewBorderDimensionsBlockdata(int newWidth, int newHeight) { + int oldWidth = getBorderWidth(); + int oldHeight = getBorderHeight(); + + Blockdata newBlockdata; + + for (int y = 0; y < newHeight; y++) + for (int x = 0; x < newWidth; x++) { + if (x < oldWidth && y < oldHeight) { + int index = y * oldWidth + x; + newBlockdata.append(this->border.value(index)); + } else { + newBlockdata.append(0); + } + } + + this->border = newBlockdata; +} + +void Layout::_floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation) { + QList todo; + todo.append(QPoint(x, y)); + while (todo.length()) { + QPoint point = todo.takeAt(0); + x = point.x(); + y = point.y(); + Block block; + if (!getBlock(x, y, &block)) { + continue; + } + + uint old_coll = block.collision(); + uint old_elev = block.elevation(); + if (old_coll == collision && old_elev == elevation) { + continue; + } + + block.setCollision(collision); + block.setElevation(elevation); + setBlock(x, y, block, true); + if (getBlock(x + 1, y, &block) && block.collision() == old_coll && block.elevation() == old_elev) { + todo.append(QPoint(x + 1, y)); + } + if (getBlock(x - 1, y, &block) && block.collision() == old_coll && block.elevation()== old_elev) { + todo.append(QPoint(x - 1, y)); + } + if (getBlock(x, y + 1, &block) && block.collision() == old_coll && block.elevation() == old_elev) { + todo.append(QPoint(x, y + 1)); + } + if (getBlock(x, y - 1, &block) && block.collision() == old_coll && block.elevation() == old_elev) { + todo.append(QPoint(x, y - 1)); + } + } +} + +void Layout::floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation) { + Block block; + if (getBlock(x, y, &block) && (block.collision() != collision || block.elevation() != elevation)) { + _floodFillCollisionElevation(x, y, collision, elevation); + } +} + +void Layout::magicFillCollisionElevation(int initialX, int initialY, uint16_t collision, uint16_t elevation) { + Block block; + if (getBlock(initialX, initialY, &block) && (block.collision() != collision || block.elevation() != elevation)) { + uint old_coll = block.collision(); + uint old_elev = block.elevation(); + + for (int y = 0; y < getHeight(); y++) { + for (int x = 0; x < getWidth(); x++) { + if (getBlock(x, y, &block) && block.collision() == old_coll && block.elevation() == old_elev) { + block.setCollision(collision); + block.setElevation(elevation); + setBlock(x, y, block, true); + } + } + } + } +} + +QPixmap Layout::render(bool ignoreCache, Layout *fromLayout, QRect bounds) { + bool changed_any = false; + int width_ = getWidth(); + int height_ = getHeight(); + if (image.isNull() || image.width() != width_ * 16 || image.height() != height_ * 16) { + image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); + changed_any = true; + } + if (this->blockdata.isEmpty() || !width_ || !height_) { + pixmap = pixmap.fromImage(image); + return pixmap; + } + + QPainter painter(&image); + for (int i = 0; i < this->blockdata.length(); i++) { + if (!ignoreCache && !layoutBlockChanged(i, this->cached_blockdata)) { + continue; + } + changed_any = true; + int map_y = width_ ? i / width_ : 0; + int map_x = width_ ? i % width_ : 0; + if (bounds.isValid() && !bounds.contains(map_x, map_y)) { + continue; + } + QPoint metatile_origin = QPoint(map_x * 16, map_y * 16); + Block block = this->blockdata.at(i); + QImage metatile_image = getMetatileImage( + block.metatileId(), + fromLayout ? fromLayout->tileset_primary : this->tileset_primary, + fromLayout ? fromLayout->tileset_secondary : this->tileset_secondary, + metatileLayerOrder, + metatileLayerOpacity + ); + painter.drawImage(metatile_origin, metatile_image); + } + painter.end(); + if (changed_any) { + cacheBlockdata(); + pixmap = pixmap.fromImage(image); + } + + return pixmap; +} + +QPixmap Layout::renderCollision(bool ignoreCache) { + bool changed_any = false; + int width_ = getWidth(); + int height_ = getHeight(); + if (collision_image.isNull() || collision_image.width() != width_ * 16 || collision_image.height() != height_ * 16) { + collision_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); + changed_any = true; + } + if (this->blockdata.isEmpty() || !width_ || !height_) { + collision_pixmap = collision_pixmap.fromImage(collision_image); + return collision_pixmap; + } + QPainter painter(&collision_image); + for (int i = 0; i < this->blockdata.length(); i++) { + if (!ignoreCache && !layoutBlockChanged(i, this->cached_collision)) { + continue; + } + changed_any = true; + Block block = this->blockdata.at(i); + QImage collision_metatile_image = getCollisionMetatileImage(block); + int map_y = width_ ? i / width_ : 0; + int map_x = width_ ? i % width_ : 0; + QPoint metatile_origin = QPoint(map_x * 16, map_y * 16); + painter.drawImage(metatile_origin, collision_metatile_image); + } + painter.end(); + cacheCollision(); + if (changed_any) { + collision_pixmap = collision_pixmap.fromImage(collision_image); + } + return collision_pixmap; +} + +QPixmap Layout::renderBorder(bool ignoreCache) { + bool changed_any = false, border_resized = false; + int width_ = getBorderWidth(); + int height_ = getBorderHeight(); + if (this->border_image.isNull()) { + this->border_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); + changed_any = true; + } + if (this->border_image.width() != width_ * 16 || this->border_image.height() != height_ * 16) { + this->border_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888); + border_resized = true; + } + if (this->border.isEmpty()) { + this->border_pixmap = this->border_pixmap.fromImage(this->border_image); + return this->border_pixmap; + } + QPainter painter(&this->border_image); + for (int i = 0; i < this->border.length(); i++) { + if (!ignoreCache && (!border_resized && !layoutBlockChanged(i, this->cached_border))) { + continue; + } + + changed_any = true; + Block block = this->border.at(i); + uint16_t metatileId = block.metatileId(); + QImage metatile_image = getMetatileImage(metatileId, this->tileset_primary, this->tileset_secondary, metatileLayerOrder, metatileLayerOpacity); + int map_y = width_ ? i / width_ : 0; + int map_x = width_ ? i % width_ : 0; + painter.drawImage(QPoint(map_x * 16, map_y * 16), metatile_image); + } + painter.end(); + if (changed_any) { + cacheBorder(); + this->border_pixmap = this->border_pixmap.fromImage(this->border_image); + } + return this->border_pixmap; +} + +QPixmap Layout::getLayoutItemPixmap() { + return this->layoutItem ? this->layoutItem->pixmap() : QPixmap(); +} + +bool Layout::hasUnsavedChanges() const { + return !this->editHistory.isClean(); +} diff --git a/src/core/mapparser.cpp b/src/core/mapparser.cpp index 986c2c242..3d4258bd8 100644 --- a/src/core/mapparser.cpp +++ b/src/core/mapparser.cpp @@ -7,7 +7,7 @@ MapParser::MapParser() { } -MapLayout *MapParser::parse(QString filepath, bool *error, Project *project) +Layout *MapParser::parse(QString filepath, bool *error, Project *project) { QFile file(filepath); if (!file.open(QIODevice::ReadOnly)) { @@ -69,7 +69,7 @@ MapLayout *MapParser::parse(QString filepath, bool *error, Project *project) } } - MapLayout *mapLayout = new MapLayout(); + Layout *mapLayout = new Layout(); mapLayout->width = mapWidth; mapLayout->height = mapHeight; mapLayout->border_width = (borderWidth == 0) ? DEFAULT_BORDER_WIDTH : borderWidth; diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 04cdbcc7f..2c357776a 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -49,10 +49,10 @@ void ParseUtil::recordErrors(const QStringList &errors) { } void ParseUtil::logRecordedErrors() { - QStringList errors = this->errorMap.value(this->curDefine); + const QStringList errors = this->errorMap.value(this->curDefine); if (errors.isEmpty()) return; QString message = QString("Failed to parse '%1':").arg(this->curDefine); - for (const auto error : errors) + for (const auto &error : errors) message.append(QString("\n%1").arg(error)); logError(message); } @@ -370,13 +370,14 @@ QStringList ParseUtil::readCIncbinArray(const QString &filename, const QString & return paths; } -bool ParseUtil::defineNameMatchesFilter(const QString &name, const QStringList &filterList, bool useRegex) { +bool ParseUtil::defineNameMatchesFilter(const QString &name, const QStringList &filterList) const { + return filterList.contains(name); +} + +bool ParseUtil::defineNameMatchesFilter(const QString &name, const QList &filterList) const { for (auto filter : filterList) { - if (useRegex) { - // TODO: These QRegularExpression should probably be constructed beforehand, - // otherwise we recreate them for every define we check. - if (QRegularExpression(filter).match(name).hasMatch()) return true; - } else if (name == filter) return true; + if (filter.match(name).hasMatch()) + return true; } return false; } @@ -405,6 +406,21 @@ ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const if (this->text.isEmpty()) return result; + // If necessary, construct regular expressions from filter list + QList filterList_Regex; + if (useRegex) { + for (auto filter : filterList) { + filterList_Regex.append(QRegularExpression(filter)); + } + } + + // Create lambda function to match the define name to the filter, depending on the filter type + auto matchesFilter = [this, &filterList, &filterList_Regex, useRegex](const QString &name) { + if (useRegex) + return defineNameMatchesFilter(name, filterList_Regex); + return defineNameMatchesFilter(name, filterList); + }; + // Capture either the name and value of a #define, or everything between the braces of 'enum { }' static const QRegularExpression re("#define\\s+(?\\w+)[\\s\\n][^\\S\\n]*(?.+)?" "|\\benum\\b[^{]*{(?[^}]*)}"); @@ -437,14 +453,14 @@ ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const baseNum = 1; } result.expressions.insert(name, expression); - if (defineNameMatchesFilter(name, filterList, useRegex)) + if (matchesFilter(name)) result.filteredNames.append(name); } } else { // Encountered a #define const QString name = match.captured("defineName"); result.expressions.insert(name, match.captured("defineValue")); - if (defineNameMatchesFilter(name, filterList, useRegex)) + if (matchesFilter(name)) result.filteredNames.append(name); } } @@ -580,7 +596,7 @@ bool ParseUtil::gameStringToBool(QString gameString, bool * ok) { QMap> ParseUtil::readCStructs(const QString &filename, const QString &label, const QHash memberMap) { QString filePath = this->root + "/" + filename; auto cParser = fex::Parser(); - auto tokens = fex::Lexer().LexFile(filePath.toStdString()); + auto tokens = fex::Lexer().LexFile(filePath); auto structs = cParser.ParseTopLevelObjects(tokens); QMap> structMaps; for (auto it = structs.begin(); it != structs.end(); it++) { diff --git a/src/core/regionmap.cpp b/src/core/regionmap.cpp index 7dbd4c5f1..94650ccd9 100644 --- a/src/core/regionmap.cpp +++ b/src/core/regionmap.cpp @@ -19,8 +19,7 @@ using std::make_shared; RegionMap::RegionMap(Project *project) : section_prefix(projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix)), - default_map_section(section_prefix + projectConfig.getIdentifier(ProjectIdentifier::define_map_section_empty)), - count_map_section(section_prefix + projectConfig.getIdentifier(ProjectIdentifier::define_map_section_count)) + default_map_section(project->getEmptyMapsecName()) { this->project = project; } @@ -157,7 +156,7 @@ bool RegionMap::loadLayout(poryjson::Json layoutJson) { for (int x = 0; x < this->layout_width; x++) { int bin_index = x + y * this->layout_width; uint8_t square_section_id = mapBinData.at(bin_index); - QString square_section_name = project->mapSectionValueToName.value(square_section_id); + QString square_section_name = project->mapSectionIdNames.value(square_section_id, this->default_map_section); LayoutSquare square; square.map_section = square_section_name; @@ -401,7 +400,7 @@ void RegionMap::saveLayout() { for (int m = 0; m < this->layout_height; m++) { for (int n = 0; n < this->layout_width; n++) { int i = n + this->layout_width * m; - data.append(this->project->mapSectionNameToValue.value(this->layouts["main"][i].map_section)); + data.append(this->project->mapSectionIdNames.indexOf(this->layouts["main"][i].map_section)); } } QFile bfile(fullPath(this->layout_path)); @@ -760,18 +759,15 @@ bool RegionMap::squareInLayout(int x, int y) { } MapSectionEntry RegionMap::getEntry(QString section) { - if (this->region_map_entries->contains(section)) - return this->region_map_entries->operator[](section); - else - return MapSectionEntry(); + return this->region_map_entries->value(section, MapSectionEntry()); } void RegionMap::setEntry(QString section, MapSectionEntry entry) { - this->region_map_entries->operator[](section) = entry; + this->region_map_entries->insert(section, entry); } void RegionMap::removeEntry(QString section) { - this->region_map_entries->erase(section); + this->region_map_entries->remove(section); } QString RegionMap::palPath() { @@ -788,27 +784,6 @@ int RegionMap::getMapSquareIndex(int x, int y) { return ((index < tilemap.length()) && (index >= 0)) ? index : 0; } -// For turning a MAPSEC_NAME into a unique identifier sMapName-style variable. -// CAPS_WITH_UNDERSCORE to CamelCase -QString RegionMap::fixCase(QString caps) { - bool big = true; - QString camel; - - static const QRegularExpression re_braced("({.*})"); - for (auto ch : caps.remove(re_braced).remove(this->section_prefix)) { - if (ch == '_' || ch == ' ') { - big = true; - continue; - } - if (big) { - camel += ch.toUpper(); - big = false; - } - else camel += ch.toLower(); - } - return camel; -} - QString RegionMap::fullPath(QString local) { return this->project->root + "/" + local; } diff --git a/src/core/regionmapeditcommands.cpp b/src/core/regionmapeditcommands.cpp index e718d596e..7a12cbc6c 100644 --- a/src/core/regionmapeditcommands.cpp +++ b/src/core/regionmapeditcommands.cpp @@ -90,7 +90,7 @@ bool EditLayout::mergeWith(const QUndoCommand *command) { /// -ResizeLayout::ResizeLayout(RegionMap *map, int oldWidth, int oldHeight, int newWidth, int newHeight, +ResizeRMLayout::ResizeRMLayout(RegionMap *map, int oldWidth, int oldHeight, int newWidth, int newHeight, QMap> oldLayouts, QMap> newLayouts, QUndoCommand *parent) : QUndoCommand(parent) { setText("Change Layout Dimensions"); @@ -104,7 +104,7 @@ ResizeLayout::ResizeLayout(RegionMap *map, int oldWidth, int oldHeight, int newW this->newLayouts = newLayouts; } -void ResizeLayout::redo() { +void ResizeRMLayout::redo() { QUndoCommand::redo(); if (!map) return; @@ -113,7 +113,7 @@ void ResizeLayout::redo() { map->setAllLayouts(this->newLayouts); } -void ResizeLayout::undo() { +void ResizeRMLayout::undo() { if (!map) return; map->setLayoutDimensions(oldWidth, oldHeight, false); @@ -122,8 +122,8 @@ void ResizeLayout::undo() { QUndoCommand::undo(); } -bool ResizeLayout::mergeWith(const QUndoCommand *command) { - const ResizeLayout *other = static_cast(command); +bool ResizeRMLayout::mergeWith(const QUndoCommand *command) { + const ResizeRMLayout *other = static_cast(command); if (this->map != other->map) return false; @@ -260,7 +260,7 @@ void ResizeTilemap::undo() { /// -ClearEntries::ClearEntries(RegionMap *map, tsl::ordered_map entries, QUndoCommand *parent) +ClearEntries::ClearEntries(RegionMap *map, QMap entries, QUndoCommand *parent) : QUndoCommand(parent) { setText("Clear Entries"); diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index 7e5729eea..be3a04d41 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -29,8 +29,8 @@ Tileset::Tileset(const Tileset &other) tiles.append(tile.copy()); } - for (auto *metatile : other.metatiles) { - metatiles.append(new Metatile(*metatile)); + for (auto *metatile : other.m_metatiles) { + m_metatiles.append(new Metatile(*metatile)); } } @@ -55,14 +55,42 @@ Tileset &Tileset::operator=(const Tileset &other) { tiles.append(tile.copy()); } - metatiles.clear(); - for (auto *metatile : other.metatiles) { - metatiles.append(new Metatile(*metatile)); + clearMetatiles(); + for (auto *metatile : other.m_metatiles) { + m_metatiles.append(new Metatile(*metatile)); } return *this; } +Tileset::~Tileset() { + clearMetatiles(); +} + +void Tileset::clearMetatiles() { + qDeleteAll(m_metatiles); + m_metatiles.clear(); +} + +void Tileset::setMetatiles(const QList &metatiles) { + clearMetatiles(); + m_metatiles = metatiles; +} + +void Tileset::addMetatile(Metatile* metatile) { + m_metatiles.append(metatile); +} + +void Tileset::resizeMetatiles(unsigned int newNumMetatiles) { + while (m_metatiles.length() > newNumMetatiles) { + delete m_metatiles.takeLast(); + } + const int numTiles = projectConfig.getNumTilesInMetatile(); + while (m_metatiles.length() < newNumMetatiles) { + m_metatiles.append(new Metatile(numTiles)); + } +} + Tileset* Tileset::getTileTileset(int tileId, Tileset *primaryTileset, Tileset *secondaryTileset) { if (tileId < Project::getNumTilesPrimary()) { return primaryTileset; @@ -89,7 +117,7 @@ Metatile* Tileset::getMetatile(int metatileId, Tileset *primaryTileset, Tileset return nullptr; } int index = Metatile::getIndexInTileset(metatileId); - return tileset->metatiles.value(index, nullptr); + return tileset->m_metatiles.value(index, nullptr); } // Metatile labels are stored per-tileset. When looking for a metatile label, first search in the tileset @@ -178,10 +206,10 @@ bool Tileset::metatileIsValid(uint16_t metatileId, Tileset *primaryTileset, Tile if (metatileId >= Project::getNumMetatilesTotal()) return false; - if (metatileId < Project::getNumMetatilesPrimary() && metatileId >= primaryTileset->metatiles.length()) + if (metatileId < Project::getNumMetatilesPrimary() && metatileId >= primaryTileset->numMetatiles()) return false; - if (metatileId >= Project::getNumMetatilesPrimary() + secondaryTileset->metatiles.length()) + if (metatileId >= Project::getNumMetatilesPrimary() + secondaryTileset->numMetatiles()) return false; return true; diff --git a/src/editor.cpp b/src/editor.cpp index 69b2ffb9c..86cedd5b7 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -4,7 +4,7 @@ #include "log.h" #include "connectionslistitem.h" #include "currentselectedmetatilespixmapitem.h" -#include "mapsceneeventfilter.h" +#include "eventfilters.h" #include "metatile.h" #include "montabwidget.h" #include "editcommands.h" @@ -47,6 +47,10 @@ Editor::Editor(Ui::MainWindow* ui) connect(ui->stackedWidget_WildMons, &QStackedWidget::currentChanged, [this] { emit wildMonTableOpened(getCurrentWildMonTable()); }); + + connect(ui->toolButton_Open_Scripts, &QToolButton::pressed, this, &Editor::openMapScripts); + connect(ui->actionOpen_Project_in_Text_Editor, &QAction::triggered, this, &Editor::openProjectInTextEditor); + connect(ui->checkBox_ToggleGrid, &QCheckBox::toggled, this, &Editor::toggleGrid); } Editor::~Editor() @@ -71,10 +75,14 @@ void Editor::saveProject() { } void Editor::save() { - if (project && map) { + if (this->project && this->map) { saveUiFields(); - project->saveMap(map); - project->saveAllDataStructures(); + this->project->saveMap(this->map); + this->project->saveAllDataStructures(); + } + else if (this->project && this->layout) { + this->project->saveLayout(this->layout); + this->project->saveAllDataStructures(); } } @@ -98,66 +106,103 @@ void Editor::closeProject() { delete this->project; } -void Editor::setEditingMap() { - current_view = map_item; - if (map_item) { - map_item->paintingMode = MapPixmapItem::PaintMode::Metatiles; - map_item->draw(); - map_item->setVisible(true); - } - if (collision_item) { - collision_item->setVisible(false); - } - if (events_group) { - events_group->setVisible(false); +bool Editor::getEditingLayout() { + return this->editMode == EditMode::Metatiles || this->editMode == EditMode::Collision; +} + +void Editor::setEditorView() { + // based on editMode + if (!map_item || !collision_item) return; + if (!this->layout) return; + + map_item->setVisible(true); // is map item ever not visible + collision_item->setVisible(false); + + switch (this->editMode) { + case EditMode::Metatiles: + case EditMode::Connections: + case EditMode::Events: + current_view = map_item; + break; + case EditMode::Collision: + current_view = collision_item; + break; + default: + current_view = nullptr; + return; } + + map_item->draw(); + collision_item->draw(); + + current_view->setVisible(true); + updateBorderVisibility(); - this->cursorMapTileRect->stopSingleTileMode(); + this->cursorMapTileRect->setSingleTileMode(); this->cursorMapTileRect->setActive(true); - setMapEditingButtonsEnabled(true); + switch (this->editMode) { + case EditMode::Metatiles: + case EditMode::Collision: + map_item->setEditsEnabled(true); + this->editGroup.setActiveStack(&this->layout->editHistory); + break; + case EditMode::Connections: + this->cursorMapTileRect->setActive(false); + map_item->setEditsEnabled(false); + case EditMode::Events: + if (this->map) { + this->editGroup.setActiveStack(&this->map->editHistory); + } + break; + case EditMode::Header: + case EditMode::Encounters: + default: + this->editGroup.setActiveStack(nullptr); + break; + } + + if (this->events_group) { + this->events_group->setVisible(this->editMode == EditMode::Events); + } + setMapEditingButtonsEnabled(this->editMode != EditMode::Events); +} + +void Editor::setEditingMetatiles() { + this->editMode = EditMode::Metatiles; + + setEditorView(); } void Editor::setEditingCollision() { - current_view = collision_item; - if (collision_item) { - collision_item->draw(); - collision_item->setVisible(true); - } - if (map_item) { - map_item->paintingMode = MapPixmapItem::PaintMode::Metatiles; - map_item->draw(); - map_item->setVisible(true); - } - if (events_group) { - events_group->setVisible(false); - } - updateBorderVisibility(); - this->cursorMapTileRect->setSingleTileMode(); - this->cursorMapTileRect->setActive(true); + this->editMode = EditMode::Collision; - setMapEditingButtonsEnabled(true); + setEditorView(); +} + +void Editor::setEditingHeader() { + this->editMode = EditMode::Header; + + setEditorView(); } void Editor::setEditingObjects() { - current_view = map_item; - if (events_group) { - events_group->setVisible(true); - } - if (map_item) { - map_item->paintingMode = MapPixmapItem::PaintMode::EventObjects; - map_item->draw(); - map_item->setVisible(true); - } - if (collision_item) { - collision_item->setVisible(false); - } - updateBorderVisibility(); - this->cursorMapTileRect->setSingleTileMode(); - this->cursorMapTileRect->setActive(false); + this->editMode = EditMode::Events; + + setEditorView(); updateWarpEventWarnings(); +} + +void Editor::setEditingConnections() { + this->editMode = EditMode::Connections; - setMapEditingButtonsEnabled(false); + setEditorView(); +} + +void Editor::setEditingEncounters() { + this->editMode = EditMode::Encounters; + + setEditorView(); } void Editor::setMapEditingButtonsEnabled(bool enabled) { @@ -166,7 +211,7 @@ void Editor::setMapEditingButtonsEnabled(bool enabled) { this->ui->pushButton_ChangeDimensions->setEnabled(enabled); // If the fill button is pressed, unpress it and select the pointer. if (!enabled && (this->ui->toolButton_Fill->isChecked() || this->ui->toolButton_Dropper->isChecked())) { - this->map_edit_mode = "select"; + this->mapEditAction = EditAction::Select; this->settings->mapCursor = QCursor(); this->cursorMapTileRect->setSingleTileMode(); this->ui->toolButton_Fill->setChecked(false); @@ -176,24 +221,6 @@ void Editor::setMapEditingButtonsEnabled(bool enabled) { this->ui->checkBox_smartPaths->setEnabled(enabled); } -void Editor::setEditingConnections() { - current_view = map_item; - if (map_item) { - map_item->paintingMode = MapPixmapItem::PaintMode::Disabled; - map_item->draw(); - map_item->setVisible(true); - } - if (collision_item) { - collision_item->setVisible(false); - } - if (events_group) { - events_group->setVisible(false); - } - updateBorderVisibility(); - this->cursorMapTileRect->setSingleTileMode(); - this->cursorMapTileRect->setActive(false); -} - void Editor::clearWildMonTables() { QStackedWidget *stack = ui->stackedWidget_WildMons; const QSignalBlocker blocker(stack); @@ -784,6 +811,9 @@ void Editor::displayConnection(MapConnection *connection) { connect(listItem, &ConnectionsListItem::openMapClicked, this, &Editor::openConnectedMap); connect(pixmapItem, &ConnectionPixmapItem::connectionItemDoubleClicked, this, &Editor::openConnectedMap); + // Pressing the delete key on a selected connection's pixmap deletes it + connect(pixmapItem, &ConnectionPixmapItem::deleteRequested, this, &Editor::removeConnection); + // Sync the selection highlight between the list UI and the pixmap connect(pixmapItem, &ConnectionPixmapItem::selectionChanged, [=](bool selected) { listItem->setSelected(selected); @@ -842,11 +872,6 @@ void Editor::removeConnection(MapConnection *connection) { 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; @@ -1055,8 +1080,8 @@ void Editor::onHoveredMovementPermissionCleared() { } QString Editor::getMetatileDisplayMessage(uint16_t metatileId) { - Metatile *metatile = Tileset::getMetatile(metatileId, map->layout->tileset_primary, map->layout->tileset_secondary); - QString label = Tileset::getMetatileLabel(metatileId, map->layout->tileset_primary, map->layout->tileset_secondary); + Metatile *metatile = Tileset::getMetatile(metatileId, this->layout->tileset_primary, this->layout->tileset_secondary); + QString label = Tileset::getMetatileLabel(metatileId, this->layout->tileset_primary, this->layout->tileset_secondary); QString message = QString("Metatile: %1").arg(Metatile::getMetatileIdString(metatileId)); if (label.size()) message += QString(" \"%1\"").arg(label); @@ -1142,46 +1167,46 @@ void Editor::setCursorRectVisible(bool visible) { void Editor::onHoveredMapMetatileChanged(const QPoint &pos) { int x = pos.x(); int y = pos.y(); - if (!map->isWithinBounds(x, y)) + if (!layout->isWithinBounds(x, y)) return; this->updateCursorRectPos(x, y); - if (map_item->paintingMode == MapPixmapItem::PaintMode::Metatiles) { - int blockIndex = y * map->getWidth() + x; - int metatileId = map->layout->blockdata.at(blockIndex).metatileId(); + if (this->getEditingLayout()) { + int blockIndex = y * layout->getWidth() + x; + int metatileId = layout->blockdata.at(blockIndex).metatileId(); this->ui->statusBar->showMessage(QString("X: %1, Y: %2, %3, Scale = %4x") .arg(x) .arg(y) .arg(getMetatileDisplayMessage(metatileId)) .arg(QString::number(zoomLevels[this->scaleIndex], 'g', 2))); } - else if (map_item->paintingMode == MapPixmapItem::PaintMode::EventObjects) { + else if (this->editMode == EditMode::Events) { this->ui->statusBar->showMessage(QString("X: %1, Y: %2, Scale = %3x") .arg(x) .arg(y) .arg(QString::number(zoomLevels[this->scaleIndex], 'g', 2))); } + Scripting::cb_BlockHoverChanged(x, y); } void Editor::onHoveredMapMetatileCleared() { this->setCursorRectVisible(false); - if (map_item->paintingMode == MapPixmapItem::PaintMode::Metatiles - || map_item->paintingMode == MapPixmapItem::PaintMode::EventObjects) { + if (!map_item->getEditsEnabled()) { this->ui->statusBar->clearMessage(); } Scripting::cb_BlockHoverCleared(); } void Editor::onHoveredMapMovementPermissionChanged(int x, int y) { - if (!map->isWithinBounds(x, y)) + if (!layout->isWithinBounds(x, y)) return; this->updateCursorRectPos(x, y); - if (map_item->paintingMode == MapPixmapItem::PaintMode::Metatiles) { - int blockIndex = y * map->getWidth() + x; - uint16_t collision = map->layout->blockdata.at(blockIndex).collision(); - uint16_t elevation = map->layout->blockdata.at(blockIndex).elevation(); + if (this->getEditingLayout()) { + int blockIndex = y * layout->getWidth() + x; + uint16_t collision = layout->blockdata.at(blockIndex).collision(); + uint16_t elevation = layout->blockdata.at(blockIndex).elevation(); QString message = QString("X: %1, Y: %2, %3") .arg(x) .arg(y) @@ -1193,7 +1218,7 @@ void Editor::onHoveredMapMovementPermissionChanged(int x, int y) { void Editor::onHoveredMapMovementPermissionCleared() { this->setCursorRectVisible(false); - if (map_item->paintingMode == MapPixmapItem::PaintMode::Metatiles) { + if (this->getEditingLayout()) { this->ui->statusBar->clearMessage(); } Scripting::cb_BlockHoverCleared(); @@ -1215,11 +1240,7 @@ QString Editor::getMovementPermissionText(uint16_t collision, uint16_t elevation return message; } -bool Editor::setMap(QString map_name) { - if (map_name.isEmpty()) { - return false; - } - +void Editor::unsetMap() { // disconnect previous map's signals so they are not firing // multiple times if set again in the future if (map) { @@ -1228,47 +1249,90 @@ bool Editor::setMap(QString map_name) { for (auto connection : map->getConnections()) disconnectMapConnection(connection); } + clearMapConnections(); - if (project) { - Map *loadedMap = project->loadMap(map_name); - if (!loadedMap) { - return false; - } + this->map = nullptr; +} - map = loadedMap; +bool Editor::setMap(QString map_name) { + if (!project || map_name.isEmpty()) { + return false; + } - editGroup.addStack(&map->editHistory); - editGroup.setActiveStack(&map->editHistory); - selected_events->clear(); - if (!displayMap()) { - return false; - } - 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(); + unsetMap(); + + Map *loadedMap = project->loadMap(map_name); + if (!loadedMap) { + return false; + } + + this->map = loadedMap; + + setLayout(map->layout->id); + + editGroup.addStack(&map->editHistory); + editGroup.setActiveStack(&map->editHistory); + + selected_events->clear(); + if (!displayMap()) { + return false; + } + displayWildMonTables(); + + connect(map, &Map::openScriptRequested, this, &Editor::openScript); + connect(map, &Map::connectionAdded, this, &Editor::displayConnection); + connect(map, &Map::connectionRemoved, this, &Editor::removeConnectionPixmap); + updateSelectedEvents(); + + return true; +} + +bool Editor::setLayout(QString layoutId) { + if (!project || layoutId.isEmpty()) { + return false; } + this->layout = this->project->loadLayout(layoutId); + + if (!displayLayout()) { + return false; + } + + editGroup.addStack(&layout->editHistory); + + map_ruler->setMapDimensions(QSize(this->layout->getWidth(), this->layout->getHeight())); + connect(this->layout, &Layout::layoutDimensionsChanged, map_ruler, &MapRuler::setMapDimensions); + + ui->comboBox_PrimaryTileset->blockSignals(true); + ui->comboBox_SecondaryTileset->blockSignals(true); + ui->comboBox_PrimaryTileset->setCurrentText(this->layout->tileset_primary_label); + ui->comboBox_SecondaryTileset->setCurrentText(this->layout->tileset_secondary_label); + ui->comboBox_PrimaryTileset->blockSignals(false); + ui->comboBox_SecondaryTileset->blockSignals(false); + + const QSignalBlocker b0(this->ui->comboBox_LayoutSelector); + int index = this->ui->comboBox_LayoutSelector->findText(layoutId); + if (index < 0) index = 0; + this->ui->comboBox_LayoutSelector->setCurrentIndex(index); + return true; } -void Editor::onMapStartPaint(QGraphicsSceneMouseEvent *event, MapPixmapItem *item) { - if (item->paintingMode != MapPixmapItem::PaintMode::Metatiles) { +void Editor::onMapStartPaint(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *) { + if (!this->getEditingLayout()) { return; } QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - if (event->buttons() & Qt::RightButton && (map_edit_mode == "paint" || map_edit_mode == "fill")) { + if (event->buttons() & Qt::RightButton && (mapEditAction == EditAction::Paint || mapEditAction == EditAction::Fill)) { this->cursorMapTileRect->initRightClickSelectionAnchor(pos.x(), pos.y()); } else { this->cursorMapTileRect->initAnchor(pos.x(), pos.y()); } } -void Editor::onMapEndPaint(QGraphicsSceneMouseEvent *, MapPixmapItem *item) { - if (!(item->paintingMode == MapPixmapItem::PaintMode::Metatiles)) { +void Editor::onMapEndPaint(QGraphicsSceneMouseEvent *, LayoutPixmapItem *) { + if (!this->getEditingLayout()) { return; } this->cursorMapTileRect->stopRightClickSelectionAnchor(); @@ -1301,16 +1365,16 @@ void Editor::setStraightPathCursorMode(QGraphicsSceneMouseEvent *event) { } } -void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, MapPixmapItem *item) { +void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item) { // TODO: add event tab object painting tool buttons stuff here - if (item->paintingMode == MapPixmapItem::PaintMode::Disabled) { + if (!item->getEditsEnabled()) { return; } QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - if (item->paintingMode == MapPixmapItem::PaintMode::Metatiles) { - if (map_edit_mode == "paint") { + if (this->getEditingLayout()) { + if (mapEditAction == EditAction::Paint) { if (event->buttons() & Qt::RightButton) { item->updateMetatileSelection(event); } else if (event->buttons() & Qt::MiddleButton) { @@ -1332,9 +1396,9 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, MapPixmapItem *item } item->paint(event); } - } else if (map_edit_mode == "select") { + } else if (mapEditAction == EditAction::Select) { item->select(event); - } else if (map_edit_mode == "fill") { + } else if (mapEditAction == EditAction::Fill) { if (event->buttons() & Qt::RightButton) { item->updateMetatileSelection(event); } else if (event->modifiers() & Qt::ControlModifier) { @@ -1342,13 +1406,13 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, MapPixmapItem *item } else { item->floodFill(event); } - } else if (map_edit_mode == "pick") { + } else if (mapEditAction == EditAction::Pick) { if (event->buttons() & Qt::RightButton) { item->updateMetatileSelection(event); } else { item->pick(event); } - } else if (map_edit_mode == "shift") { + } else if (mapEditAction == EditAction::Shift) { this->setStraightPathCursorMode(event); if (this->cursorMapTileRect->getStraightPathMode()) { item->lockNondominantAxis(event); @@ -1356,11 +1420,11 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, MapPixmapItem *item } item->shift(event); } - } else if (item->paintingMode == MapPixmapItem::PaintMode::EventObjects) { - if (obj_edit_mode == "paint" && event->type() == QEvent::GraphicsSceneMousePress) { + } else if (this->editMode == EditMode::Events) { + if (objectEditAction == EditAction::Paint && event->type() == QEvent::GraphicsSceneMousePress) { // Right-clicking while in paint mode will change mode to select. if (event->buttons() & Qt::RightButton) { - this->obj_edit_mode = "select"; + this->objectEditAction = EditAction::Select; this->settings->mapCursor = QCursor(); this->cursorMapTileRect->setSingleTileMode(); this->ui->toolButton_Paint->setChecked(false); @@ -1382,9 +1446,9 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, MapPixmapItem *item } } } - } else if (obj_edit_mode == "select") { + } else if (objectEditAction == EditAction::Select) { // do nothing here, at least for now - } else if (obj_edit_mode == "shift" && item->map) { + } else if (objectEditAction == EditAction::Shift) { static QPoint selection_origin; static unsigned actionId = 0; @@ -1400,8 +1464,8 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, MapPixmapItem *item QList selectedEvents; - for (DraggablePixmapItem *item : getObjects()) { - selectedEvents.append(item->event); + for (DraggablePixmapItem *pixmapItem : getObjects()) { + selectedEvents.append(pixmapItem->event); } selection_origin = QPoint(pos.x(), pos.y()); @@ -1414,13 +1478,13 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, MapPixmapItem *item } void Editor::mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixmapItem *item) { - if (item->paintingMode != MapPixmapItem::PaintMode::Metatiles) { + if (!item->getEditsEnabled()) { return; } QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - if (map_edit_mode == "paint") { + if (mapEditAction == EditAction::Paint) { if (event->buttons() & Qt::RightButton) { item->updateMovementPermissionSelection(event); } else if (event->buttons() & Qt::MiddleButton) { @@ -1437,9 +1501,9 @@ void Editor::mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixm } item->paint(event); } - } else if (map_edit_mode == "select") { + } else if (mapEditAction == EditAction::Select) { item->select(event); - } else if (map_edit_mode == "fill") { + } else if (mapEditAction == EditAction::Fill) { if (event->buttons() & Qt::RightButton) { item->pick(event); } else if (event->modifiers() & Qt::ControlModifier) { @@ -1447,9 +1511,9 @@ void Editor::mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixm } else { item->floodFill(event); } - } else if (map_edit_mode == "pick") { + } else if (mapEditAction == EditAction::Pick) { item->pick(event); - } else if (map_edit_mode == "shift") { + } else if (mapEditAction == EditAction::Shift) { this->setStraightPathCursorMode(event); if (this->cursorMapTileRect->getStraightPathMode()) { item->lockNondominantAxis(event); @@ -1486,26 +1550,40 @@ void Editor::clearMap() { } bool Editor::displayMap() { + if (!this->map) + return false; + + displayMapEvents(); + displayMapConnections(); + maskNonVisibleConnectionTiles(); + + if (events_group) { + events_group->setVisible(false); + } + return true; +} + +bool Editor::displayLayout() { + if (!this->layout) + return false; + if (!scene) { scene = new QGraphicsScene; - MapSceneEventFilter *filter = new MapSceneEventFilter(); + MapSceneEventFilter *filter = new MapSceneEventFilter(scene); scene->installEventFilter(filter); connect(filter, &MapSceneEventFilter::wheelZoom, this, &Editor::onWheelZoom); scene->installEventFilter(this->map_ruler); } + clearConnectionMask(); displayMetatileSelector(); - displayMovementPermissionSelector(); displayMapMetatiles(); + displayMovementPermissionSelector(); displayMapMovementPermissions(); displayBorderMetatiles(); displayCurrentMetatilesSelection(); - displayMapEvents(); - displayMapConnections(); displayMapBorder(); displayMapGrid(); - displayWildMonTables(); - maskNonVisibleConnectionTiles(); this->map_ruler->setZValue(1000); scene->addItem(this->map_ruler); @@ -1516,9 +1594,7 @@ bool Editor::displayMap() { if (collision_item) { collision_item->setVisible(false); } - if (events_group) { - events_group->setVisible(false); - } + return true; } @@ -1534,7 +1610,7 @@ void Editor::displayMetatileSelector() { scene_metatiles = new QGraphicsScene; if (!metatile_selector_item) { - metatile_selector_item = new MetatileSelector(8, map); + metatile_selector_item = new MetatileSelector(8, this->layout); connect(metatile_selector_item, &MetatileSelector::hoveredMetatileSelectionChanged, this, &Editor::onHoveredMetatileSelectionChanged); connect(metatile_selector_item, &MetatileSelector::hoveredMetatileSelectionCleared, @@ -1543,14 +1619,14 @@ void Editor::displayMetatileSelector() { this, &Editor::onSelectedMetatilesChanged); metatile_selector_item->select(0); } else { - metatile_selector_item->setMap(map); + metatile_selector_item->setLayout(this->layout); if (metatile_selector_item->primaryTileset - && metatile_selector_item->primaryTileset != map->layout->tileset_primary) - emit tilesetUpdated(map->layout->tileset_primary->name); + && metatile_selector_item->primaryTileset != this->layout->tileset_primary) + emit tilesetUpdated(this->layout->tileset_primary->name); if (metatile_selector_item->secondaryTileset - && metatile_selector_item->secondaryTileset != map->layout->tileset_secondary) - emit tilesetUpdated(map->layout->tileset_secondary->name); - metatile_selector_item->setTilesets(map->layout->tileset_primary, map->layout->tileset_secondary); + && metatile_selector_item->secondaryTileset != this->layout->tileset_secondary) + emit tilesetUpdated(this->layout->tileset_secondary->name); + metatile_selector_item->setTilesets(this->layout->tileset_primary, this->layout->tileset_secondary); } scene_metatiles->addItem(metatile_selector_item); @@ -1567,12 +1643,12 @@ void Editor::clearMapMetatiles() { void Editor::displayMapMetatiles() { clearMapMetatiles(); - map_item = new MapPixmapItem(map, this->metatile_selector_item, this->settings); - connect(map_item, &MapPixmapItem::mouseEvent, this, &Editor::mouseEvent_map); - connect(map_item, &MapPixmapItem::startPaint, this, &Editor::onMapStartPaint); - connect(map_item, &MapPixmapItem::endPaint, this, &Editor::onMapEndPaint); - connect(map_item, &MapPixmapItem::hoveredMapMetatileChanged, this, &Editor::onHoveredMapMetatileChanged); - connect(map_item, &MapPixmapItem::hoveredMapMetatileCleared, this, &Editor::onHoveredMapMetatileCleared); + map_item = new LayoutPixmapItem(this->layout, this->metatile_selector_item, this->settings); + connect(map_item, &LayoutPixmapItem::mouseEvent, this, &Editor::mouseEvent_map); + connect(map_item, &LayoutPixmapItem::startPaint, this, &Editor::onMapStartPaint); + connect(map_item, &LayoutPixmapItem::endPaint, this, &Editor::onMapEndPaint); + connect(map_item, &LayoutPixmapItem::hoveredMapMetatileChanged, this, &Editor::onHoveredMapMetatileChanged); + connect(map_item, &LayoutPixmapItem::hoveredMapMetatileCleared, this, &Editor::onHoveredMapMetatileCleared); map_item->draw(true); scene->addItem(map_item); @@ -1597,7 +1673,7 @@ void Editor::clearMapMovementPermissions() { void Editor::displayMapMovementPermissions() { clearMapMovementPermissions(); - collision_item = new CollisionPixmapItem(map, ui->spinBox_SelectedCollision, ui->spinBox_SelectedElevation, + collision_item = new CollisionPixmapItem(this->layout, ui->spinBox_SelectedCollision, ui->spinBox_SelectedElevation, this->metatile_selector_item, this->settings, &this->collisionOpacity); connect(collision_item, &CollisionPixmapItem::mouseEvent, this, &Editor::mouseEvent_collision); connect(collision_item, &CollisionPixmapItem::hoveredMapMovementPermissionChanged, @@ -1621,7 +1697,7 @@ void Editor::displayBorderMetatiles() { clearBorderMetatiles(); scene_selected_border_metatiles = new QGraphicsScene; - selected_border_metatiles_item = new BorderMetatilesPixmapItem(map, this->metatile_selector_item); + selected_border_metatiles_item = new BorderMetatilesPixmapItem(this->layout, this->metatile_selector_item); selected_border_metatiles_item->draw(); scene_selected_border_metatiles->addItem(selected_border_metatiles_item); @@ -1646,14 +1722,14 @@ void Editor::displayCurrentMetatilesSelection() { clearCurrentMetatilesSelection(); scene_current_metatile_selection = new QGraphicsScene; - current_metatile_selection_item = new CurrentSelectedMetatilesPixmapItem(map, this->metatile_selector_item); + current_metatile_selection_item = new CurrentSelectedMetatilesPixmapItem(this->layout, this->metatile_selector_item); current_metatile_selection_item->draw(); scene_current_metatile_selection->addItem(current_metatile_selection_item); } void Editor::redrawCurrentMetatilesSelection() { if (current_metatile_selection_item) { - current_metatile_selection_item->setMap(map); + current_metatile_selection_item->setLayout(this->layout); current_metatile_selection_item->draw(); emit currentMetatilesSelectionChanged(); } @@ -1779,8 +1855,8 @@ void Editor::maskNonVisibleConnectionTiles() { mask.addRect( -BORDER_DISTANCE * 16, -BORDER_DISTANCE * 16, - (map->getWidth() + BORDER_DISTANCE * 2) * 16, - (map->getHeight() + BORDER_DISTANCE * 2) * 16 + (layout->getWidth() + BORDER_DISTANCE * 2) * 16, + (layout->getHeight() + BORDER_DISTANCE * 2) * 16 ); // Mask the tiles with the current theme's background color. @@ -1803,13 +1879,13 @@ void Editor::clearMapBorder() { void Editor::displayMapBorder() { clearMapBorder(); - int borderWidth = map->getBorderWidth(); - int borderHeight = map->getBorderHeight(); + int borderWidth = this->layout->getBorderWidth(); + int borderHeight = this->layout->getBorderHeight(); int borderHorzDist = getBorderDrawDistance(borderWidth); int borderVertDist = getBorderDrawDistance(borderHeight); - QPixmap pixmap = map->renderBorder(); - for (int y = -borderVertDist; y < map->getHeight() + borderVertDist; y += borderHeight) - for (int x = -borderHorzDist; x < map->getWidth() + borderHorzDist; x += borderWidth) { + QPixmap pixmap = this->layout->renderBorder(); + for (int y = -borderVertDist; y < this->layout->getHeight() + borderVertDist; y += borderHeight) + for (int x = -borderHorzDist; x < this->layout->getWidth() + borderHorzDist; x += borderWidth) { QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap); item->setX(x * 16); item->setY(y * 16); @@ -1820,7 +1896,7 @@ void Editor::displayMapBorder() { } void Editor::updateMapBorder() { - QPixmap pixmap = this->map->renderBorder(true); + QPixmap pixmap = this->layout->renderBorder(true); for (auto item : this->borderItems) { item->setPixmap(pixmap); } @@ -1842,59 +1918,88 @@ int Editor::getBorderDrawDistance(int dimension) { } } -void Editor::onToggleGridClicked(bool checked) { +void Editor::toggleGrid(bool checked) { + if (porymapConfig.showGrid == checked) + return; porymapConfig.showGrid = checked; + + // Synchronize action and checkbox + const QSignalBlocker b_Action(ui->actionShow_Grid); + const QSignalBlocker b_Checkbox(ui->checkBox_ToggleGrid); + ui->actionShow_Grid->setChecked(checked); + ui->checkBox_ToggleGrid->setChecked(checked); + + this->mapGrid->setVisible(checked); + if (ui->graphicsView_Map->scene()) ui->graphicsView_Map->scene()->update(); } void Editor::clearMapGrid() { - for (QGraphicsLineItem* item : gridLines) { - if (item) delete item; - } - gridLines.clear(); + delete this->mapGrid; + this->mapGrid = nullptr; } void Editor::displayMapGrid() { clearMapGrid(); - ui->checkBox_ToggleGrid->disconnect(); - int pixelWidth = map->getWidth() * 16; - int pixelHeight = map->getHeight() * 16; - for (int i = 0; i <= map->getWidth(); i++) { - int x = i * 16; - QGraphicsLineItem *line = new QGraphicsLineItem(x, 0, x, pixelHeight); - line->setVisible(ui->checkBox_ToggleGrid->isChecked()); - gridLines.append(line); - connect(ui->checkBox_ToggleGrid, &QCheckBox::toggled, [=](bool checked){line->setVisible(checked);}); + // Note: The grid lines are not added to the scene. They need to be drawn on top of the overlay + // elements of the scripting API, so they're painted manually in MapView::drawForeground. + this->mapGrid = new QGraphicsItemGroup(); + + const int pixelMapWidth = this->layout->getWidth() * 16; + const int pixelMapHeight = this->layout->getHeight() * 16; + + // The grid can be moved with a user-specified x/y offset. The grid's dash patterns will only wrap in full pattern increments, + // so we draw an additional row/column outside the map that can be revealed as the offset changes. + const int offsetX = (this->gridSettings.offsetX % this->gridSettings.width) - this->gridSettings.width; + const int offsetY = (this->gridSettings.offsetY % this->gridSettings.height) - this->gridSettings.height; + + QPen pen; + pen.setColor(this->gridSettings.color); + + // Create vertical lines + pen.setDashPattern(this->gridSettings.getVerticalDashPattern()); + for (int i = offsetX; i <= pixelMapWidth; i += this->gridSettings.width) { + auto line = new QGraphicsLineItem(i, offsetY, i, pixelMapHeight); + line->setPen(pen); + this->mapGrid->addToGroup(line); } - for (int j = 0; j <= map->getHeight(); j++) { - int y = j * 16; - QGraphicsLineItem *line = new QGraphicsLineItem(0, y, pixelWidth, y); - line->setVisible(ui->checkBox_ToggleGrid->isChecked()); - gridLines.append(line); - connect(ui->checkBox_ToggleGrid, &QCheckBox::toggled, [=](bool checked){line->setVisible(checked);}); + + // Create horizontal lines + pen.setDashPattern(this->gridSettings.getHorizontalDashPattern()); + for (int i = offsetY; i <= pixelMapHeight; i += this->gridSettings.height) { + auto line = new QGraphicsLineItem(offsetX, i, pixelMapWidth, i); + line->setPen(pen); + this->mapGrid->addToGroup(line); } - connect(ui->checkBox_ToggleGrid, &QCheckBox::toggled, this, &Editor::onToggleGridClicked); + + this->mapGrid->setVisible(porymapConfig.showGrid); +} + +void Editor::updateMapGrid() { + displayMapGrid(); + if (ui->graphicsView_Map->scene()) + ui->graphicsView_Map->scene()->update(); } void Editor::updatePrimaryTileset(QString tilesetLabel, bool forceLoad) { - if (map->layout->tileset_primary_label != tilesetLabel || forceLoad) + if (this->layout->tileset_primary_label != tilesetLabel || forceLoad) { - map->layout->tileset_primary_label = tilesetLabel; - map->layout->tileset_primary = project->getTileset(tilesetLabel, forceLoad); - map->clearBorderCache(); + this->layout->tileset_primary_label = tilesetLabel; + this->layout->tileset_primary = project->getTileset(tilesetLabel, forceLoad); + layout->clearBorderCache(); } } void Editor::updateSecondaryTileset(QString tilesetLabel, bool forceLoad) { - if (map->layout->tileset_secondary_label != tilesetLabel || forceLoad) + if (this->layout->tileset_secondary_label != tilesetLabel || forceLoad) { - map->layout->tileset_secondary_label = tilesetLabel; - map->layout->tileset_secondary = project->getTileset(tilesetLabel, forceLoad); - map->clearBorderCache(); + this->layout->tileset_secondary_label = tilesetLabel; + this->layout->tileset_secondary = project->getTileset(tilesetLabel, forceLoad); + layout->clearBorderCache(); } } @@ -1940,7 +2045,7 @@ void Editor::updateCustomMapHeaderValues(QTableWidget *table) Tileset* Editor::getCurrentMapPrimaryTileset() { - QString tilesetLabel = map->layout->tileset_primary_label; + QString tilesetLabel = this->layout->tileset_primary_label; return project->getTileset(tilesetLabel); } @@ -1975,12 +2080,12 @@ void Editor::redrawObject(DraggablePixmapItem *item) { void Editor::updateWarpEventWarning(Event *event) { if (porymapConfig.warpBehaviorWarningDisabled) return; - if (!project || !map || !event || event->getEventType() != Event::Type::Warp) + if (!project || !map || !map->layout || !event || event->getEventType() != Event::Type::Warp) return; Block block; Metatile * metatile = nullptr; WarpEvent * warpEvent = static_cast(event); - if (map->getBlock(warpEvent->getX(), warpEvent->getY(), &block)) { + if (map->layout->getBlock(warpEvent->getX(), warpEvent->getY(), &block)) { metatile = Tileset::getMetatile(block.metatileId(), map->layout->tileset_primary, map->layout->tileset_secondary); } // metatile may be null if the warp is in the map border. Display the warning in this case @@ -2060,7 +2165,7 @@ void Editor::selectedEventIndexChanged(int index, Event::Group eventGroup) { } void Editor::duplicateSelectedEvents() { - if (!selected_events || !selected_events->length() || !map || !current_view || map_item->paintingMode != MapPixmapItem::PaintMode::EventObjects) + if (!selected_events || !selected_events->length() || !map || !current_view || this->getEditingLayout()) return; QList selectedEvents; @@ -2117,6 +2222,50 @@ bool Editor::eventLimitReached(Event::Type event_type) { return false; } +void Editor::deleteSelectedEvents() { + if (!this->selected_events || this->selected_events->length() == 0 || !this->map || this->editMode != EditMode::Events) + return; + + DraggablePixmapItem *nextSelectedEvent = nullptr; + QList selectedEvents; + int numDeleted = 0; + for (DraggablePixmapItem *item : *this->selected_events) { + Event::Group event_group = item->event->getEventGroup(); + if (event_group != Event::Group::Heal) { + numDeleted++; + item->event->setPixmapItem(item); + selectedEvents.append(item->event); + } + else { // don't allow deletion of heal locations + logWarn(QString("Cannot delete event of type '%1'").arg(Event::eventTypeToString(item->event->getEventType()))); + } + } + if (numDeleted) { + // Get the index for the event that should be selected after this event has been deleted. + // Select event at next smallest index when deleting a single event. + // If deleting multiple events, just let editor work out next selected. + if (numDeleted == 1) { + Event::Group event_group = selectedEvents[0]->getEventGroup(); + int index = this->map->events.value(event_group).indexOf(selectedEvents[0]); + if (index != this->map->events.value(event_group).size() - 1) + index++; + else + index--; + Event *event = nullptr; + if (index >= 0) + event = this->map->events.value(event_group).at(index); + for (QGraphicsItem *child : this->events_group->childItems()) { + DraggablePixmapItem *event_item = static_cast(child); + if (event_item->event == event) { + nextSelectedEvent = event_item; + break; + } + } + } + this->map->editHistory.push(new EventDelete(this, this->map, selectedEvents, nextSelectedEvent ? nextSelectedEvent->event : nullptr)); + } +} + void Editor::openMapScripts() const { openInTextEditor(map->getScriptsFilePath()); } @@ -2199,11 +2348,11 @@ bool Editor::startDetachedProcess(const QString &command, const QString &working // is clicking on the background instead of an event. void Editor::objectsView_onMousePress(QMouseEvent *event) { // make sure we are in object editing mode - if (map_item && map_item->paintingMode != MapPixmapItem::PaintMode::EventObjects) { + if (map_item && this->editMode != EditMode::Events) { return; } - if (this->obj_edit_mode == "paint" && event->buttons() & Qt::RightButton) { - this->obj_edit_mode = "select"; + if (this->objectEditAction == EditAction::Paint && event->buttons() & Qt::RightButton) { + this->objectEditAction = EditAction::Select; this->settings->mapCursor = QCursor(); this->cursorMapTileRect->setSingleTileMode(); this->ui->toolButton_Paint->setChecked(false); diff --git a/src/lib/fex/lexer.cpp b/src/lib/fex/lexer.cpp index 2dd4b249c..e8545f2e9 100644 --- a/src/lib/fex/lexer.cpp +++ b/src/lib/fex/lexer.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace fex { @@ -155,48 +156,26 @@ namespace fex return Token(Token::Type::kDefine, filename_, line_number_); } - std::vector Lexer::LexString(const std::string &data) + std::vector Lexer::LexFile(const QString &path) { - filename_ = "string literal"; + filename_ = path.toStdString(); line_number_ = 1; - index_ = 0; - data_ = data; - - return Lex(); - } - std::vector Lexer::LexFile(const std::string &path) - { - filename_ = path; - line_number_ = 1; + // Note: Using QFile instead of ifstream to handle encoding differences between platforms + // (specifically to handle accented characters on Windows) + QFile file(path); + file.open(QIODevice::ReadOnly); - std::ifstream file; - file.open(path); - - std::stringstream stream; - stream << file.rdbuf(); + const QByteArray data = file.readAll(); index_ = 0; - data_ = stream.str(); + data_ = data.toStdString(); file.close(); return Lex(); } - void Lexer::LexFileDumpTokens(const std::string &path, const std::string &out) - { - std::ofstream file; - file.open(out); - - for (Token token : LexFile(path)) - { - file << token.ToString() << std::endl; - } - - file.close(); - } - std::vector Lexer::Lex() { std::vector tokens; diff --git a/src/lib/fex/parser.cpp b/src/lib/fex/parser.cpp index bb5c90a83..2e2a6f3e8 100644 --- a/src/lib/fex/parser.cpp +++ b/src/lib/fex/parser.cpp @@ -337,7 +337,7 @@ namespace fex return DefineStatement(identifer, value); } - std::map Parser::ReadDefines(const std::string &filename, std::vector matching) + std::map Parser::ReadDefines(const QString &filename, std::vector matching) { std::map out; diff --git a/src/lib/fex/parser_util.cpp b/src/lib/fex/parser_util.cpp deleted file mode 100644 index 0f375b817..000000000 --- a/src/lib/fex/parser_util.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "lib/fex/parser_util.h" - -#include - -#include "lib/fex/parser.h" - -ParserUtil::ParserUtil(QString root): root_(root) {} - -QStringList ParserUtil::ReadDefines(QString filename, QString prefix) -{ - if (filename.isEmpty()) { - return QStringList(); - } - - QString filepath = root_ + "/" + filename; - - fex::Parser parser; - - std::vector match_list = { prefix.toStdString() + ".*" }; - std::map defines = parser.ReadDefines(filepath.toStdString(), match_list); - - QStringList out; - for(auto const& define : defines) { - out.append(QString::fromStdString(define.first)); - } - - return out; -} - -QStringList ParserUtil::ReadDefinesValueSort(QString filename, QString prefix) -{ - - if (filename.isEmpty()) { - return QStringList(); - } - - QString filepath = root_ + "/" + filename; - - fex::Parser parser; - - std::vector match_list = { prefix.toStdString() + ".*" }; - std::map defines = parser.ReadDefines(filepath.toStdString(), match_list); - - QMultiMap defines_keyed_by_value; - for (const auto& pair : defines) { - defines_keyed_by_value.insert(pair.second, QString::fromStdString(pair.first)); - } - - return defines_keyed_by_value.values(); -} diff --git a/src/lib/orderedjson.cpp b/src/lib/orderedjson.cpp index 387dac6dd..d7dd35e8c 100644 --- a/src/lib/orderedjson.cpp +++ b/src/lib/orderedjson.cpp @@ -33,7 +33,6 @@ static const int max_depth = 200; using std::map; using std::make_shared; using std::initializer_list; -using std::move; /* Helper for representing null - just a do-nothing struct, plus comparison * operators so the helpers in JsonValue work. We can't use nullptr_t because @@ -164,7 +163,7 @@ class Value : public JsonValue { // Constructors explicit Value(const T &value) : m_value(value) {} - explicit Value(T &&value) : m_value(move(value)) {} + explicit Value(T &&value) : m_value(std::move(value)) {} // Get type tag Json::Type type() const override { @@ -211,7 +210,7 @@ class JsonString final : public Value { const QString &string_value() const override { return m_value; } public: explicit JsonString(const QString &value) : Value(value) {} - explicit JsonString(QString &&value) : Value(move(value)) {} + explicit JsonString(QString &&value) : Value(std::move(value)) {} }; class JsonArray final : public Value { @@ -219,7 +218,7 @@ class JsonArray final : public Value { const Json & operator[](int i) const override; public: explicit JsonArray(const Json::array &value) : Value(value) {} - explicit JsonArray(Json::array &&value) : Value(move(value)) {} + explicit JsonArray(Json::array &&value) : Value(std::move(value)) {} }; class JsonObject final : public Value { @@ -227,7 +226,7 @@ class JsonObject final : public Value { const Json & operator[](const QString &key) const override; public: explicit JsonObject(const Json::object &value) : Value(value) {} - explicit JsonObject(Json::object &&value) : Value(move(value)) {} + explicit JsonObject(Json::object &&value) : Value(std::move(value)) {} }; class JsonNull final : public Value { @@ -269,12 +268,12 @@ Json::Json(double value) : m_ptr(make_shared(value)) { Json::Json(int value) : m_ptr(make_shared(value)) {} Json::Json(bool value) : m_ptr(value ? statics().t : statics().f) {} Json::Json(const QString &value) : m_ptr(make_shared(value)) {} -Json::Json(QString &&value) : m_ptr(make_shared(move(value))) {} +Json::Json(QString &&value) : m_ptr(make_shared(std::move(value))) {} Json::Json(const char * value) : m_ptr(make_shared(value)) {} Json::Json(const Json::array &values) : m_ptr(make_shared(values)) {} -Json::Json(Json::array &&values) : m_ptr(make_shared(move(values))) {} +Json::Json(Json::array &&values) : m_ptr(make_shared(std::move(values))) {} Json::Json(const Json::object &values) : m_ptr(make_shared(values)) {} -Json::Json(Json::object &&values) : m_ptr(make_shared(move(values))) {} +Json::Json(Json::object &&values) : m_ptr(make_shared(std::move(values))) {} /* * * * * * * * * * * * * * * * * * * * * Accessors @@ -399,7 +398,7 @@ struct JsonParser final { * Mark this parse as failed. */ Json fail(QString &&msg) { - return fail(move(msg), Json()); + return fail(std::move(msg), Json()); } template diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ecac17031..d9e65eb30 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -18,9 +18,12 @@ #include "prefab.h" #include "montabwidget.h" #include "imageexport.h" +#include "maplistmodels.h" +#include "eventfilters.h" #include "newmapconnectiondialog.h" +#include "config.h" +#include "filedialog.h" -#include #include #include #include @@ -116,7 +119,7 @@ void MainWindow::initWindow() { this->initExtraSignals(); this->initEditor(); this->initMiscHeapObjects(); - this->initMapSortOrder(); + this->initMapList(); this->initShortcuts(); this->restoreWindowState(); @@ -146,19 +149,10 @@ void MainWindow::initExtraShortcuts() { shortcutReset_Zoom->setObjectName("shortcutZoom_Reset"); shortcutReset_Zoom->setWhatsThis("Zoom Reset"); - auto *shortcutToggle_Grid = new Shortcut(QKeySequence("Ctrl+G"), ui->checkBox_ToggleGrid, SLOT(toggle())); - shortcutToggle_Grid->setObjectName("shortcutToggle_Grid"); - shortcutToggle_Grid->setWhatsThis("Toggle Grid"); - auto *shortcutDuplicate_Events = new Shortcut(QKeySequence("Ctrl+D"), this, SLOT(duplicate())); shortcutDuplicate_Events->setObjectName("shortcutDuplicate_Events"); shortcutDuplicate_Events->setWhatsThis("Duplicate Selected Event(s)"); - auto *shortcutDelete_Object = new Shortcut( - {QKeySequence("Del"), QKeySequence("Backspace")}, this, SLOT(onDeleteKeyPressed())); - shortcutDelete_Object->setObjectName("shortcutDelete_Object"); - shortcutDelete_Object->setWhatsThis("Delete Selected Item(s)"); - auto *shortcutToggle_Border = new Shortcut(QKeySequence(), ui->checkBox_ToggleBorder, SLOT(toggle())); shortcutToggle_Border->setObjectName("shortcutToggle_Border"); shortcutToggle_Border->setWhatsThis("Toggle Border"); @@ -167,11 +161,15 @@ void MainWindow::initExtraShortcuts() { shortcutToggle_Smart_Paths->setObjectName("shortcutToggle_Smart_Paths"); shortcutToggle_Smart_Paths->setWhatsThis("Toggle Smart Paths"); - auto *shortcutExpand_All = new Shortcut(QKeySequence(), this, SLOT(on_toolButton_ExpandAll_clicked())); + auto *shortcutHide_Show = new Shortcut(QKeySequence(), this, SLOT(mapListShortcut_ToggleEmptyFolders())); + shortcutHide_Show->setObjectName("shortcutHide_Show"); + shortcutHide_Show->setWhatsThis("Map List: Hide/Show Empty Folders"); + + auto *shortcutExpand_All = new Shortcut(QKeySequence(), this, SLOT(mapListShortcut_ExpandAll())); shortcutExpand_All->setObjectName("shortcutExpand_All"); shortcutExpand_All->setWhatsThis("Map List: Expand all folders"); - auto *shortcutCollapse_All = new Shortcut(QKeySequence(), this, SLOT(on_toolButton_CollapseAll_clicked())); + auto *shortcutCollapse_All = new Shortcut(QKeySequence(), this, SLOT(mapListShortcut_CollapseAll())); shortcutCollapse_All->setObjectName("shortcutCollapse_All"); shortcutCollapse_All->setWhatsThis("Map List: Collapse all folders"); @@ -213,31 +211,33 @@ void MainWindow::applyUserShortcuts() { shortcut->setKeys(shortcutsConfig.userShortcuts(shortcut)); } -static const QMap mainTabNames = { - {MainTab::Map, "Map"}, - {MainTab::Events, "Events"}, - {MainTab::Header, "Header"}, - {MainTab::Connections, "Connections"}, - {MainTab::WildPokemon, "Wild Pokemon"}, -}; - void MainWindow::initCustomUI() { + static const QMap mainTabNames = { + {MainTab::Map, "Map"}, + {MainTab::Events, "Events"}, + {MainTab::Header, "Header"}, + {MainTab::Connections, "Connections"}, + {MainTab::WildPokemon, "Wild Pokemon"}, + }; + + static const QMap mainTabIcons = { + {MainTab::Map, QIcon(QStringLiteral(":/icons/minimap.ico"))}, + {MainTab::Events, QIcon(QStringLiteral(":/icons/viewsprites.ico"))}, + {MainTab::Header, QIcon(QStringLiteral(":/icons/application_form_edit.ico"))}, + {MainTab::Connections, QIcon(QStringLiteral(":/icons/connections.ico"))}, + {MainTab::WildPokemon, QIcon(QStringLiteral(":/icons/tall_grass.ico"))}, + }; + // Set up the tab bar while (ui->mainTabBar->count()) ui->mainTabBar->removeTab(0); - for (int i = 0; i < mainTabNames.count(); i++) + 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"))); + ui->mainTabBar->setTabIcon(i, mainTabIcons.value(i)); + } } void MainWindow::initExtraSignals() { - // Right-clicking on items in the map list tree view brings up a context menu. - ui->mapList->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->mapList, &QTreeView::customContextMenuRequested, - this, &MainWindow::onOpenMapListContextMenu); - // other signals connect(ui->newEventToolButton, &NewEventToolButton::newEventAdded, this, &MainWindow::addNewEvent); connect(ui->tabWidget_EventType, &QTabWidget::currentChanged, this, &MainWindow::eventTabChanged); @@ -314,8 +314,7 @@ void MainWindow::initEditor() { connect(this->editor, &Editor::wildMonTableEdited, [this] { this->markMapEdited(); }); connect(this->editor, &Editor::mapRulerStatusChanged, this, &MainWindow::onMapRulerStatusChanged); 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); + connect(ui->toolButton_deleteObject, &QAbstractButton::clicked, this->editor, &Editor::deleteSelectedEvents); this->loadUserSettings(); @@ -343,7 +342,7 @@ void MainWindow::initEditor() { ui->menuEdit->addAction(showHistory); // Toggle an asterisk in the window title when the undo state is changed - connect(&editor->editGroup, &QUndoGroup::cleanChanged, this, &MainWindow::showWindowTitle); + connect(&editor->editGroup, &QUndoGroup::indexChanged, this, &MainWindow::updateWindowTitle); // selecting objects from the spinners connect(this->ui->spinner_ObjectID, QOverload::of(&QSpinBox::valueChanged), [this](int value) { @@ -364,125 +363,161 @@ void MainWindow::initEditor() { } void MainWindow::initMiscHeapObjects() { - mapIcon = QIcon(QStringLiteral(":/icons/map.ico")); + ui->tabWidget_EventType->clear(); +} - mapListModel = new QStandardItemModel; - mapGroupItemsList = new QList; - mapListProxyModel = new FilterChildrenProxyModel; +void MainWindow::initMapList() { + ui->mapListContainer->setCurrentIndex(porymapConfig.mapListTab); - mapListProxyModel->setSourceModel(mapListModel); - ui->mapList->setModel(mapListProxyModel); + WheelFilter *wheelFilter = new WheelFilter(this); + ui->mainTabBar->installEventFilter(wheelFilter); + ui->mapListContainer->tabBar()->installEventFilter(wheelFilter); - ui->tabWidget_EventType->clear(); -} + // Create buttons for adding and removing items from the mapList + QFrame *buttonFrame = new QFrame(this->ui->mapListContainer); + buttonFrame->setFrameShape(QFrame::NoFrame); + + QHBoxLayout *layout = new QHBoxLayout(buttonFrame); + layout->setSpacing(0); + layout->setContentsMargins(0, 0, 0, 0); + + // Create add map/layout button + QPushButton *buttonAdd = new QPushButton(QIcon(":/icons/add.ico"), ""); + connect(buttonAdd, &QPushButton::clicked, this, &MainWindow::on_action_NewMap_triggered); + layout->addWidget(buttonAdd); + + /* TODO: Remove button disabled, no current support for deleting maps/layouts + // Create remove map/layout button + QPushButton *buttonRemove = new QPushButton(QIcon(":/icons/delete.ico"), ""); + connect(buttonRemove, &QPushButton::clicked, this, &MainWindow::deleteCurrentMapOrLayout); + layout->addWidget(buttonRemove); + */ + + ui->mapListContainer->setCornerWidget(buttonFrame, Qt::TopRightCorner); -void MainWindow::initMapSortOrder() { - QMenu *mapSortOrderMenu = new QMenu(this); - QActionGroup *mapSortOrderActionGroup = new QActionGroup(ui->toolButton_MapSortOrder); + // Connect tool bars to lists + ui->mapListToolBar_Groups->setList(ui->mapList); + ui->mapListToolBar_Areas->setList(ui->areaList); + ui->mapListToolBar_Layouts->setList(ui->layoutList); - mapSortOrderMenu->addAction(ui->actionSort_by_Group); - mapSortOrderMenu->addAction(ui->actionSort_by_Area); - mapSortOrderMenu->addAction(ui->actionSort_by_Layout); - ui->toolButton_MapSortOrder->setMenu(mapSortOrderMenu); + // Left-clicking on items in the map list opens the corresponding map/layout. + connect(ui->mapList, &QAbstractItemView::activated, this, &MainWindow::openMapListItem); + connect(ui->areaList, &QAbstractItemView::activated, this, &MainWindow::openMapListItem); + connect(ui->layoutList, &QAbstractItemView::activated, this, &MainWindow::openMapListItem); - mapSortOrderActionGroup->addAction(ui->actionSort_by_Group); - mapSortOrderActionGroup->addAction(ui->actionSort_by_Area); - mapSortOrderActionGroup->addAction(ui->actionSort_by_Layout); + // Right-clicking on items in the map list brings up a context menu. + connect(ui->mapList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); + connect(ui->areaList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); + connect(ui->layoutList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); - connect(mapSortOrderActionGroup, &QActionGroup::triggered, this, &MainWindow::mapSortOrder_changed); + // Only the groups list allows reorganizing folder contents, editing folder names, etc. + ui->mapListToolBar_Areas->setEditsAllowedButtonVisible(false); + ui->mapListToolBar_Layouts->setEditsAllowedButtonVisible(false); - QAction* sortOrder = ui->toolButton_MapSortOrder->menu()->actions()[porymapConfig.mapSortOrder]; - ui->toolButton_MapSortOrder->setIcon(sortOrder->icon()); - sortOrder->setChecked(true); + // When map list search filter is cleared we want the current map/layout in the editor to be visible in the list. + connect(ui->mapListToolBar_Groups, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentMap); + connect(ui->mapListToolBar_Areas, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentMap); + connect(ui->mapListToolBar_Layouts, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentLayout); + + // Connect the "add folder" button in each of the map lists + connect(ui->mapListToolBar_Groups, &MapListToolBar::addFolderClicked, this, &MainWindow::mapListAddGroup); + connect(ui->mapListToolBar_Areas, &MapListToolBar::addFolderClicked, this, &MainWindow::mapListAddArea); + connect(ui->mapListToolBar_Layouts, &MapListToolBar::addFolderClicked, this, &MainWindow::mapListAddLayout); + + connect(ui->mapListContainer, &QTabWidget::currentChanged, this, &MainWindow::saveMapListTab); } -void MainWindow::showWindowTitle() { +void MainWindow::updateWindowTitle() { + if (!editor || !editor->project) { + setWindowTitle(QCoreApplication::applicationName()); + return; + } + + const QString projectName = editor->project->getProjectTitle(); + if (!editor->layout) { + setWindowTitle(projectName); + return; + } + if (editor->map) { setWindowTitle(QString("%1%2 - %3") .arg(editor->map->hasUnsavedChanges() ? "* " : "") .arg(editor->map->name) - .arg(editor->project->getProjectTitle()) + .arg(projectName) ); + } else { + setWindowTitle(QString("%1%2 - %3") + .arg(editor->layout->hasUnsavedChanges() ? "* " : "") + .arg(editor->layout->name) + .arg(projectName) + ); + } + + // For some reason (perhaps on Qt < 6?) we had to clear the icon first here or mainTabBar wouldn't display correctly. + ui->mainTabBar->setTabIcon(MainTab::Map, QIcon()); + + QPixmap pixmap = editor->layout->pixmap; + if (!pixmap.isNull()) { + ui->mainTabBar->setTabIcon(MainTab::Map, QIcon(pixmap)); + } else { + ui->mainTabBar->setTabIcon(MainTab::Map, QIcon(QStringLiteral(":/icons/map.ico"))); } } void MainWindow::markMapEdited() { - if (editor) markMapEdited(editor->map); + if (editor) markSpecificMapEdited(editor->map); } -void MainWindow::markMapEdited(Map* map) { +void MainWindow::markSpecificMapEdited(Map* map) { if (!map) return; map->hasUnsavedDataChanges = true; - updateMapListIcon(map->name); if (editor && editor->map == map) - showWindowTitle(); -} - -void MainWindow::mapSortOrder_changed(QAction *action) -{ - QList items = ui->toolButton_MapSortOrder->menu()->actions(); - int i = 0; - for (; i < items.count(); i++) - { - if (items[i] == action) - { - break; - } - } - - if (i != porymapConfig.mapSortOrder) - { - ui->toolButton_MapSortOrder->setIcon(action->icon()); - porymapConfig.mapSortOrder = static_cast(i); - if (isProjectOpen()) - { - sortMapList(); - applyMapListFilter(ui->lineEdit_filterBox->text()); - } - } -} - -void MainWindow::on_lineEdit_filterBox_textChanged(const QString &arg1) -{ - this->applyMapListFilter(arg1); -} - -void MainWindow::applyMapListFilter(QString filterText) -{ - mapListProxyModel->setFilterRegularExpression(QRegularExpression(filterText, QRegularExpression::CaseInsensitiveOption)); - if (filterText.isEmpty()) { - ui->mapList->collapseAll(); - } else { - ui->mapList->expandToDepth(0); - } - ui->mapList->setExpanded(mapListProxyModel->mapFromSource(mapListIndexes.value(editor->map->name)), true); - ui->mapList->scrollTo(mapListProxyModel->mapFromSource(mapListIndexes.value(editor->map->name)), QAbstractItemView::PositionAtCenter); + updateWindowTitle(); + updateMapList(); } 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); - + // Better Cursors ui->actionBetter_Cursors->setChecked(porymapConfig.prettyCursors); this->editor->settings->betterCursors = porymapConfig.prettyCursors; + + // Player view rectangle ui->actionPlayer_View_Rectangle->setChecked(porymapConfig.showPlayerView); this->editor->settings->playerViewRectEnabled = porymapConfig.showPlayerView; + + // Cursor tile outline ui->actionCursor_Tile_Outline->setChecked(porymapConfig.showCursorTile); this->editor->settings->cursorTileRectEnabled = porymapConfig.showCursorTile; + + // Border ui->checkBox_ToggleBorder->setChecked(porymapConfig.showBorder); + + // Grid + const QSignalBlocker b_Grid(ui->checkBox_ToggleGrid); + ui->actionShow_Grid->setChecked(porymapConfig.showGrid); ui->checkBox_ToggleGrid->setChecked(porymapConfig.showGrid); + + // Mirror connections ui->checkBox_MirrorConnections->setChecked(porymapConfig.mirrorConnectingMaps); + + // Collision opacity/transparency + const QSignalBlocker b_CollisionTransparency(ui->horizontalSlider_CollisionTransparency); this->editor->collisionOpacity = static_cast(porymapConfig.collisionOpacity) / 100; ui->horizontalSlider_CollisionTransparency->setValue(porymapConfig.collisionOpacity); + + // Dive map opacity/transparency + const QSignalBlocker b_DiveEmergeOpacity(ui->slider_DiveEmergeMapOpacity); + const QSignalBlocker b_DiveMapOpacity(ui->slider_DiveMapOpacity); + const QSignalBlocker b_EmergeMapOpacity(ui->slider_EmergeMapOpacity); ui->slider_DiveEmergeMapOpacity->setValue(porymapConfig.diveEmergeMapOpacity); ui->slider_DiveMapOpacity->setValue(porymapConfig.diveMapOpacity); ui->slider_EmergeMapOpacity->setValue(porymapConfig.emergeMapOpacity); + + // Zoom + const QSignalBlocker b_MetatileZoom(ui->horizontalSlider_MetatileZoom); + const QSignalBlocker b_CollisionZoom(ui->horizontalSlider_CollisionZoom); ui->horizontalSlider_MetatileZoom->setValue(porymapConfig.metatilesZoom); ui->horizontalSlider_CollisionZoom->setValue(porymapConfig.collisionZoom); @@ -558,16 +593,11 @@ bool MainWindow::openProject(QString dir, bool initial) { Scripting::init(this); // Create the project - auto project = new Project(this); + auto project = new Project(editor); 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(); - }); + connect(project, &Project::fileChanged, this, &MainWindow::showFileWatcherWarning); + connect(project, &Project::mapLoaded, this, &MainWindow::onMapLoaded); + connect(project, &Project::mapSectionIdNamesChanged, this, &MainWindow::refreshLocationsComboBox); this->editor->setProject(project); // Make sure project looks reasonable before attempting to load it @@ -588,7 +618,7 @@ bool MainWindow::openProject(QString dir, bool initial) { // Only create the config files once the project has opened successfully in case the user selected an invalid directory this->editor->project->saveConfig(); - showWindowTitle(); + updateWindowTitle(); this->statusBar()->showMessage(QString("Opened %1").arg(projectString)); porymapConfig.projectManuallyClosed = false; @@ -599,7 +629,7 @@ bool MainWindow::openProject(QString dir, bool initial) { editor->metatile_selector_item, ui->scrollAreaWidgetContents_Prefabs, ui->label_prefabHelp, - editor->map); + editor->layout); Scripting::cb_ProjectOpened(dir); setWindowDisabled(false); return true; @@ -647,22 +677,28 @@ bool MainWindow::isProjectOpen() { } bool MainWindow::setInitialMap() { - QStringList names; - if (editor && editor->project) - names = editor->project->mapNames; - - // Try to set most recently-opened map, if it's still in the list. - QString recentMap = userConfig.recentMap; - if (!recentMap.isEmpty() && names.contains(recentMap) && setMap(recentMap, true)) - return true; + const QString recent = userConfig.recentMapOrLayout; + if (editor->project->mapNames.contains(recent)) { + // User recently had a map open that still exists. + if (setMap(recent)) + return true; + } else if (editor->project->mapLayoutsTable.contains(recent)) { + // User recently had a layout open that still exists. + if (setLayout(recent)) + return true; + } - // Failing that, try loading maps in the map list sequentially. - for (auto name : names) { - if (name != recentMap && setMap(name, true)) + // Failed to open recent map/layout, or no recent map/layout. Try opening maps then layouts sequentially. + for (const auto &name : editor->project->mapNames) { + if (name != recent && setMap(name)) + return true; + } + for (const auto &id : editor->project->mapLayoutsTable) { + if (id != recent && setLayout(id)) return true; } - logError("Failed to load any maps."); + logError("Failed to load any maps or layouts."); return false; } @@ -714,13 +750,53 @@ void MainWindow::openSubWindow(QWidget * window) { } } +void MainWindow::showFileWatcherWarning(QString filepath) { + if (!porymapConfig.monitorFiles || !isProjectOpen()) + return; + + Project *project = this->editor->project; + if (project->modifiedFileTimestamps.contains(filepath)) { + if (QDateTime::currentMSecsSinceEpoch() < project->modifiedFileTimestamps[filepath]) { + return; + } + project->modifiedFileTimestamps.remove(filepath); + } + + static bool showing = false; + if (showing) return; + + QMessageBox notice(this); + notice.setText("File Changed"); + notice.setInformativeText(QString("The file %1 has changed on disk. Would you like to reload the project?") + .arg(filepath.remove(project->root + "/"))); + notice.setStandardButtons(QMessageBox::No | QMessageBox::Yes); + notice.setDefaultButton(QMessageBox::No); + notice.setIcon(QMessageBox::Question); + + QCheckBox showAgainCheck("Do not ask again."); + notice.setCheckBox(&showAgainCheck); + + showing = true; + int choice = notice.exec(); + if (choice == QMessageBox::Yes) { + on_action_Reload_Project_triggered(); + } else if (choice == QMessageBox::No) { + if (showAgainCheck.isChecked()) { + porymapConfig.monitorFiles = false; + if (this->preferenceEditor) + this->preferenceEditor->updateFields(); + } + } + showing = false; +} + QString MainWindow::getExistingDirectory(QString dir) { - return QFileDialog::getExistingDirectory(this, "Open Directory", dir, QFileDialog::ShowDirsOnly); + return FileDialog::getExistingDirectory(this, "Open Directory", dir, QFileDialog::ShowDirsOnly); } void MainWindow::on_action_Open_Project_triggered() { - QString dir = getExistingDirectory(!userConfig.recentMap.isEmpty() ? userConfig.recentMap : "."); + QString dir = getExistingDirectory(!projectConfig.projectDir.isEmpty() ? userConfig.projectDir : "."); if (!dir.isEmpty()) openProject(dir); } @@ -743,9 +819,14 @@ void MainWindow::on_action_Close_Project_triggered() { porymapConfig.projectManuallyClosed = true; } +void MainWindow::unsetMap() { + this->editor->unsetMap(); + setLayoutOnlyMode(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) { +bool MainWindow::userSetMap(QString map_name) { if (editor->map && editor->map->name == map_name) return true; // Already set @@ -756,7 +837,7 @@ bool MainWindow::userSetMap(QString map_name, bool scrollTreeView) { return false; } - if (!setMap(map_name, scrollTreeView)) { + if (!setMap(map_name)) { 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) @@ -768,60 +849,105 @@ bool MainWindow::userSetMap(QString map_name, bool scrollTreeView) { return true; } -bool MainWindow::setMap(QString map_name, bool scrollTreeView) { - logInfo(QString("Setting map to '%1'").arg(map_name)); +bool MainWindow::setMap(QString map_name) { if (map_name.isEmpty() || map_name == DYNAMIC_MAP_NAME) { + logInfo(QString("Cannot set map to '%1'").arg(map_name)); return false; } + logInfo(QString("Setting map to '%1'").arg(map_name)); + if (!editor || !editor->setMap(map_name)) { logWarn(QString("Failed to set map to '%1'").arg(map_name)); return false; } - if (editor->map != nullptr && !editor->map->name.isNull()) { - ui->mapList->setExpanded(mapListProxyModel->mapFromSource(mapListIndexes.value(editor->map->name)), false); - } - + setLayoutOnlyMode(false); this->lastSelectedEvent.clear(); + refreshMapScene(); displayMapProperties(); + updateWindowTitle(); + updateMapList(); + resetMapListFilters(); + + connect(editor->map, &Map::modified, this, &MainWindow::markMapEdited, Qt::UniqueConnection); + + connect(editor->layout, &Layout::layoutChanged, this, &MainWindow::onLayoutChanged, Qt::UniqueConnection); + connect(editor->layout, &Layout::needsRedrawing, this, &MainWindow::redrawMapScene, Qt::UniqueConnection); + + userConfig.recentMapOrLayout = map_name; + + Scripting::cb_MapOpened(map_name); + prefab.updatePrefabUi(editor->layout); + updateTilesetEditor(); + return true; +} + +// These parts of the UI only make sense when editing maps. +// When editing in layout-only mode they are disabled. +void MainWindow::setLayoutOnlyMode(bool layoutOnly) { + bool mapEditingEnabled = !layoutOnly; + this->ui->mainTabBar->setTabEnabled(MainTab::Events, mapEditingEnabled); + this->ui->mainTabBar->setTabEnabled(MainTab::Header, mapEditingEnabled); + this->ui->mainTabBar->setTabEnabled(MainTab::Connections, mapEditingEnabled); + this->ui->mainTabBar->setTabEnabled(MainTab::WildPokemon, mapEditingEnabled); - if (scrollTreeView) { - // Make sure we clear the filter first so we actually have a scroll target - mapListProxyModel->setFilterRegularExpression(QString()); - ui->mapList->setCurrentIndex(mapListProxyModel->mapFromSource(mapListIndexes.value(map_name))); - ui->mapList->scrollTo(ui->mapList->currentIndex(), QAbstractItemView::PositionAtCenter); + this->ui->comboBox_LayoutSelector->setEnabled(mapEditingEnabled); +} + +// setLayout, but with a visible error message in case of failure. +// Use when the user is specifically requesting a layout to open. +bool MainWindow::userSetLayout(QString layoutId) { + if (!setLayout(layoutId)) { + QMessageBox msgBox(this); + QString errorMsg = QString("There was an error opening layout %1. Please see %2 for full error details.\n\n%3") + .arg(layoutId) + .arg(getLogPath()) + .arg(getMostRecentError()); + msgBox.critical(nullptr, "Error Opening Layout", errorMsg); + return false; } + return true; +} - ui->mapList->setExpanded(mapListProxyModel->mapFromSource(mapListIndexes.value(map_name)), true); +bool MainWindow::setLayout(QString layoutId) { + if (this->editor->map) + logInfo("Switching to a layout-only editing mode. Disabling map-related edits."); - showWindowTitle(); + unsetMap(); - connect(editor->map, &Map::mapNeedsRedrawing, this, &MainWindow::onMapNeedsRedrawing); + // Prefer logging the name of the layout as displayed in the map list. + const QString layoutName = this->editor->project ? this->editor->project->layoutIdsToNames.value(layoutId, layoutId) : layoutId; + logInfo(QString("Setting layout to '%1'").arg(layoutName)); - // 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; - updateMapListIcon(userConfig.recentMap); + if (!this->editor->setLayout(layoutId)) { + return false; + } + + layoutTreeModel->setLayout(layoutId); + + refreshMapScene(); + updateWindowTitle(); + updateMapList(); + resetMapListFilters(); + + connect(editor->layout, &Layout::needsRedrawing, this, &MainWindow::redrawMapScene, Qt::UniqueConnection); - Scripting::cb_MapOpened(map_name); - prefab.updatePrefabUi(editor->map); updateTilesetEditor(); + + userConfig.recentMapOrLayout = layoutId; + return true; } -void MainWindow::redrawMapScene() -{ - if (!editor->displayMap()) - return; - - this->refreshMapScene(); +void MainWindow::redrawMapScene() { + editor->displayMap(); + editor->displayLayout(); + refreshMapScene(); } -void MainWindow::refreshMapScene() -{ +void MainWindow::refreshMapScene() { on_mainTabBar_tabBarClicked(ui->mainTabBar->currentIndex()); ui->graphicsView_Map->setScene(editor->scene); @@ -857,7 +983,7 @@ void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_ } // Open the destination map. - if (!userSetMap(map_name, true)) + if (!userSetMap(map_name)) return; // Select the target event. @@ -931,6 +1057,16 @@ void MainWindow::displayMapProperties() { ui->tableWidget_CustomHeaderFields->blockSignals(false); } +void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &text) { + if (editor && editor->project && editor->map) { + if (editor->project->mapLayouts.contains(text)) { + editor->map->setLayout(editor->project->loadLayout(text)); + setMap(editor->map->name); + markMapEdited(); + } + } +} + void MainWindow::on_comboBox_Song_currentTextChanged(const QString &song) { if (editor && editor->map) { @@ -1025,7 +1161,6 @@ bool MainWindow::setProjectUI() { // Block signals to the comboboxes while they are being modified const QSignalBlocker blocker1(ui->comboBox_Song); - const QSignalBlocker blocker2(ui->comboBox_Location); const QSignalBlocker blocker3(ui->comboBox_PrimaryTileset); const QSignalBlocker blocker4(ui->comboBox_SecondaryTileset); const QSignalBlocker blocker5(ui->comboBox_Weather); @@ -1033,12 +1168,11 @@ bool MainWindow::setProjectUI() { const QSignalBlocker blocker7(ui->comboBox_Type); const QSignalBlocker blocker8(ui->comboBox_DiveMap); const QSignalBlocker blocker9(ui->comboBox_EmergeMap); + const QSignalBlocker blocker10(ui->comboBox_LayoutSelector); // Set up project comboboxes ui->comboBox_Song->clear(); ui->comboBox_Song->addItems(project->songNames); - ui->comboBox_Location->clear(); - ui->comboBox_Location->addItems(project->mapSectionValueToName.values()); ui->comboBox_PrimaryTileset->clear(); ui->comboBox_PrimaryTileset->addItems(project->primaryTilesetLabels); ui->comboBox_SecondaryTileset->clear(); @@ -1049,6 +1183,8 @@ bool MainWindow::setProjectUI() { ui->comboBox_BattleScene->addItems(project->mapBattleScenes); ui->comboBox_Type->clear(); ui->comboBox_Type->addItems(project->mapTypes); + ui->comboBox_LayoutSelector->clear(); + ui->comboBox_LayoutSelector->addItems(project->mapLayoutsTable); ui->comboBox_DiveMap->clear(); ui->comboBox_DiveMap->addItems(project->mapNames); ui->comboBox_DiveMap->setClearButtonEnabled(true); @@ -1057,8 +1193,7 @@ bool MainWindow::setProjectUI() { ui->comboBox_EmergeMap->addItems(project->mapNames); ui->comboBox_EmergeMap->setClearButtonEnabled(true); ui->comboBox_EmergeMap->setFocusedScrollingEnabled(false); - - sortMapList(); + refreshLocationsComboBox(); // Show/hide parts of the UI that are dependent on the user's project settings @@ -1086,9 +1221,39 @@ bool MainWindow::setProjectUI() { ui->spinBox_SelectedElevation->setMaximum(Block::getMaxElevation()); ui->spinBox_SelectedCollision->setMaximum(Block::getMaxCollision()); + // map models + this->mapGroupModel = new MapGroupModel(editor->project); + this->groupListProxyModel = new FilterChildrenProxyModel(); + groupListProxyModel->setSourceModel(this->mapGroupModel); + ui->mapList->setModel(groupListProxyModel); + + this->ui->mapList->setItemDelegateForColumn(0, new GroupNameDelegate(this->editor->project, this)); + connect(this->mapGroupModel, &MapGroupModel::dragMoveCompleted, this->ui->mapList, &MapTree::removeSelected); + + this->mapAreaModel = new MapAreaModel(editor->project); + this->areaListProxyModel = new FilterChildrenProxyModel(); + areaListProxyModel->setSourceModel(this->mapAreaModel); + ui->areaList->setModel(areaListProxyModel); + + this->layoutTreeModel = new LayoutTreeModel(editor->project); + this->layoutListProxyModel = new FilterChildrenProxyModel(); + this->layoutListProxyModel->setSourceModel(this->layoutTreeModel); + ui->layoutList->setModel(layoutListProxyModel); + return true; } +void MainWindow::refreshLocationsComboBox() { + QStringList locations = this->editor->project->mapSectionIdNames; + locations.sort(); + + const QSignalBlocker b(ui->comboBox_Location); + ui->comboBox_Location->clear(); + ui->comboBox_Location->addItems(locations); + if (this->editor->map) + ui->comboBox_Location->setCurrentText(this->editor->map->location); +} + void MainWindow::clearProjectUI() { // Block signals to the comboboxes while they are being modified const QSignalBlocker blocker1(ui->comboBox_Song); @@ -1100,7 +1265,7 @@ void MainWindow::clearProjectUI() { const QSignalBlocker blocker7(ui->comboBox_Type); const QSignalBlocker blocker8(ui->comboBox_DiveMap); const QSignalBlocker blocker9(ui->comboBox_EmergeMap); - const QSignalBlocker blockerA(ui->lineEdit_filterBox); + const QSignalBlocker blockerA(ui->comboBox_LayoutSelector); ui->comboBox_Song->clear(); ui->comboBox_Location->clear(); @@ -1111,188 +1276,318 @@ void MainWindow::clearProjectUI() { ui->comboBox_Type->clear(); ui->comboBox_DiveMap->clear(); ui->comboBox_EmergeMap->clear(); - ui->lineEdit_filterBox->clear(); + ui->comboBox_LayoutSelector->clear(); - // Clear map list - mapListModel->clear(); - mapListIndexes.clear(); - mapGroupItemsList->clear(); + // Clear map models + delete this->mapGroupModel; + delete this->groupListProxyModel; + delete this->mapAreaModel; + delete this->areaListProxyModel; + delete this->layoutTreeModel; + delete this->layoutListProxyModel; + resetMapListFilters(); + + Event::clearIcons(); } -void MainWindow::sortMapList() { - Project *project = editor->project; +void MainWindow::scrollMapList(MapTree *list, QString itemName) { + if (!list || itemName.isEmpty()) + return; + auto model = static_cast(list->model()); + auto sourceModel = static_cast(model->sourceModel()); + QModelIndex sourceIndex = sourceModel->indexOf(itemName); + if (!sourceIndex.isValid()) + return; + QModelIndex index = model->mapFromSource(sourceIndex); + if (!index.isValid()) + return; - QIcon mapFolderIcon; - mapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off); - mapFolderIcon.addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On); - - QIcon folderIcon; - folderIcon.addFile(QStringLiteral(":/icons/folder_closed.ico"), QSize(), QIcon::Normal, QIcon::Off); - //folderIcon.addFile(QStringLiteral(":/icons/folder.ico"), QSize(), QIcon::Normal, QIcon::On); - - ui->mapList->setUpdatesEnabled(false); - mapListModel->clear(); - mapListIndexes.clear(); - mapGroupItemsList->clear(); - QStandardItem *root = mapListModel->invisibleRootItem(); - - switch (porymapConfig.mapSortOrder) - { - case MapSortOrder::Group: - for (int i = 0; i < project->groupNames.length(); i++) { - QString group_name = project->groupNames.value(i); - QStandardItem *group = new QStandardItem; - group->setText(group_name); - group->setIcon(mapFolderIcon); - group->setEditable(false); - group->setData(group_name, Qt::UserRole); - group->setData("map_group", MapListUserRoles::TypeRole); - group->setData(i, MapListUserRoles::GroupRole); - root->appendRow(group); - mapGroupItemsList->append(group); - QStringList names = project->groupedMapNames.value(i); - for (int j = 0; j < names.length(); j++) { - QString map_name = names.value(j); - QStandardItem *map = createMapItem(map_name, i, j); - group->appendRow(map); - mapListIndexes.insert(map_name, map->index()); - } - } - break; - case MapSortOrder::Area: - { - QMap mapsecToGroupNum; - int row = 0; - for (auto mapsec_value : project->mapSectionValueToName.keys()) { - QString mapsec_name = project->mapSectionValueToName.value(mapsec_value); - QStandardItem *mapsec = new QStandardItem; - mapsec->setText(mapsec_name); - mapsec->setIcon(folderIcon); - mapsec->setEditable(false); - mapsec->setData(mapsec_name, Qt::UserRole); - mapsec->setData("map_sec", MapListUserRoles::TypeRole); - root->appendRow(mapsec); - mapGroupItemsList->append(mapsec); - mapsecToGroupNum.insert(mapsec_name, row++); - } - for (int i = 0; i < project->groupNames.length(); i++) { - QStringList names = project->groupedMapNames.value(i); - for (int j = 0; j < names.length(); j++) { - QString map_name = names.value(j); - QStandardItem *map = createMapItem(map_name, i, j); - QString location = project->readMapLocation(map_name); - QStandardItem *mapsecItem = mapGroupItemsList->at(mapsecToGroupNum[location]); - mapsecItem->setIcon(mapFolderIcon); - mapsecItem->appendRow(map); - mapListIndexes.insert(map_name, map->index()); - } - } - break; - } - case MapSortOrder::Layout: - { - QMap layoutIndices; - for (int i = 0; i < project->mapLayoutsTable.length(); i++) { - QString layoutId = project->mapLayoutsTable.value(i); - MapLayout *layout = project->mapLayouts.value(layoutId); - QStandardItem *layoutItem = new QStandardItem; - layoutItem->setText(layout->name); - layoutItem->setIcon(folderIcon); - layoutItem->setEditable(false); - layoutItem->setData(layout->name, Qt::UserRole); - layoutItem->setData("map_layout", MapListUserRoles::TypeRole); - layoutItem->setData(layout->id, MapListUserRoles::TypeRole2); - layoutItem->setData(i, MapListUserRoles::GroupRole); - root->appendRow(layoutItem); - mapGroupItemsList->append(layoutItem); - layoutIndices[layoutId] = i; - } - for (int i = 0; i < project->groupNames.length(); i++) { - QStringList names = project->groupedMapNames.value(i); - for (int j = 0; j < names.length(); j++) { - QString map_name = names.value(j); - QStandardItem *map = createMapItem(map_name, i, j); - QString layoutId = project->readMapLayoutId(map_name); - QStandardItem *layoutItem = mapGroupItemsList->at(layoutIndices.value(layoutId)); - layoutItem->setIcon(mapFolderIcon); - layoutItem->appendRow(map); - mapListIndexes.insert(map_name, map->index()); - } - } - break; + list->setCurrentIndex(index); + list->setExpanded(index, true); + list->scrollTo(index, QAbstractItemView::PositionAtCenter); +} + +void MainWindow::scrollMapListToCurrentMap(MapTree *list) { + if (this->editor->map) { + scrollMapList(list, this->editor->map->name); + } +} + +void MainWindow::scrollMapListToCurrentLayout(MapTree *list) { + if (this->editor->layout) { + scrollMapList(list, this->editor->layout->id); + } +} + +void MainWindow::onOpenMapListContextMenu(const QPoint &point) { + // Get selected item from list + auto list = getCurrentMapList(); + if (!list) return; + auto model = static_cast(list->model()); + QModelIndex index = model->mapToSource(list->indexAt(point)); + if (!index.isValid()) return; + auto sourceModel = static_cast(model->sourceModel()); + QStandardItem *selectedItem = sourceModel->itemFromIndex(index); + const QString itemType = selectedItem->data(MapListUserRoles::TypeRole).toString(); + const QString itemName = selectedItem->data(Qt::UserRole).toString(); + + QMenu menu(this); + QAction* addToFolderAction = nullptr; + QAction* deleteFolderAction = nullptr; + QAction* openItemAction = nullptr; + if (itemType == "map_name") { + // Right-clicking on a map. + openItemAction = menu.addAction("Open Map"); + //menu.addSeparator(); + //connect(menu.addAction("Delete Map"), &QAction::triggered, [this, index] { deleteMapListItem(index); }); // TODO: No support for deleting maps + } else if (itemType == "map_group") { + // Right-clicking on a map group folder + addToFolderAction = menu.addAction("Add New Map to Group"); + menu.addSeparator(); + deleteFolderAction = menu.addAction("Delete Map Group"); + } else if (itemType == "map_section") { + // Right-clicking on a MAPSEC folder + addToFolderAction = menu.addAction("Add New Map to Area"); + menu.addSeparator(); + deleteFolderAction = menu.addAction("Delete Area"); + if (itemName == this->editor->project->getEmptyMapsecName()) + deleteFolderAction->setEnabled(false); // Disallow deleting the default name + } else if (itemType == "map_layout") { + // Right-clicking on a map layout + openItemAction = menu.addAction("Open Layout"); + addToFolderAction = menu.addAction("Add New Map with Layout"); + //menu.addSeparator(); + //deleteFolderAction = menu.addAction("Delete Layout"); // TODO: No support for deleting layouts + } + + if (addToFolderAction) { + connect(addToFolderAction, &QAction::triggered, [this, itemName] { + openNewMapPopupWindow(); + this->newMapPrompt->init(ui->mapListContainer->currentIndex(), itemName); + }); + } + if (deleteFolderAction) { + connect(deleteFolderAction, &QAction::triggered, [sourceModel, index] { + sourceModel->removeItemAt(index); + }); + if (selectedItem->hasChildren()){ + // TODO: No support for deleting maps, so you may only delete folders if they don't contain any maps. + deleteFolderAction->setEnabled(false); } } + if (openItemAction) { + connect(openItemAction, &QAction::triggered, [this, index] { openMapListItem(index); }); + } - ui->mapList->setUpdatesEnabled(true); - ui->mapList->repaint(); - updateMapList(); + if (menu.actions().length() != 0) + menu.exec(QCursor::pos()); } -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->setEditable(false); - map->setData(mapName, Qt::UserRole); - map->setData("map_name", MapListUserRoles::TypeRole); - return map; -} +void MainWindow::mapListAddGroup() { + QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); + dialog.setWindowModality(Qt::ApplicationModal); + QDialogButtonBox newItemButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); + connect(&newItemButtonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); -void MainWindow::onOpenMapListContextMenu(const QPoint &point) -{ - QModelIndex index = mapListProxyModel->mapToSource(ui->mapList->indexAt(point)); - if (!index.isValid()) { - return; - } + QLineEdit *newNameEdit = new QLineEdit(&dialog); + newNameEdit->setClearButtonEnabled(true); - QStandardItem *selectedItem = mapListModel->itemFromIndex(index); - QVariant itemType = selectedItem->data(MapListUserRoles::TypeRole); - if (!itemType.isValid()) { - return; - } + static const QRegularExpression re_validChars("[A-Za-z_]+[\\w]*"); + newNameEdit->setValidator(new QRegularExpressionValidator(re_validChars, newNameEdit)); - // Build custom context menu depending on which type of item was selected (map group, map name, etc.) - if (itemType == "map_group") { - QString groupName = selectedItem->data(Qt::UserRole).toString(); - int groupNum = selectedItem->data(MapListUserRoles::GroupRole).toInt(); - QMenu* menu = new QMenu(this); - QActionGroup* actions = new QActionGroup(menu); - actions->addAction(menu->addAction("Add New Map to Group"))->setData(groupNum); - connect(actions, &QActionGroup::triggered, this, &MainWindow::onAddNewMapToGroupClick); - menu->exec(QCursor::pos()); - } else if (itemType == "map_sec") { - QString secName = selectedItem->data(Qt::UserRole).toString(); - QMenu* menu = new QMenu(this); - QActionGroup* actions = new QActionGroup(menu); - actions->addAction(menu->addAction("Add New Map to Area"))->setData(secName); - connect(actions, &QActionGroup::triggered, this, &MainWindow::onAddNewMapToAreaClick); - menu->exec(QCursor::pos()); - } else if (itemType == "map_layout") { - QString layoutId = selectedItem->data(MapListUserRoles::TypeRole2).toString(); - QMenu* menu = new QMenu(this); - QActionGroup* actions = new QActionGroup(menu); - actions->addAction(menu->addAction("Add New Map with Layout"))->setData(layoutId); - connect(actions, &QActionGroup::triggered, this, &MainWindow::onAddNewMapToLayoutClick); - menu->exec(QCursor::pos()); + QLabel *errorMessageLabel = new QLabel(&dialog); + errorMessageLabel->setVisible(false); + errorMessageLabel->setStyleSheet("QLabel { background-color: rgba(255, 0, 0, 25%) }"); + + connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ + const QString mapGroupName = newNameEdit->text(); + if (this->editor->project->groupNames.contains(mapGroupName)) { + errorMessageLabel->setText(QString("A map group with the name '%1' already exists").arg(mapGroupName)); + errorMessageLabel->setVisible(true); + } else { + dialog.accept(); + } + }); + + QFormLayout form(&dialog); + + form.addRow("New Group Name", newNameEdit); + form.addRow("", errorMessageLabel); + form.addRow(&newItemButtonBox); + + if (dialog.exec() == QDialog::Accepted) { + QString newFieldName = newNameEdit->text(); + if (newFieldName.isEmpty()) return; + this->mapGroupModel->insertGroupItem(newFieldName); } } -void MainWindow::onAddNewMapToGroupClick(QAction* triggeredAction) -{ - openNewMapPopupWindow(); - this->newMapPrompt->init(MapSortOrder::Group, triggeredAction->data()); -} +// TODO: Pull this all out into a custom window. Connect that to an action in the main menu as well. +// (or, re-use the new map dialog with some tweaks) +// TODO: This needs to take the same default settings you would get for a new map (tilesets, dimensions, etc.) +// and initialize it with the same fill settings (default metatile/collision/elevation, default border) +void MainWindow::mapListAddLayout() { + if (!editor || !editor->project) return; -void MainWindow::onAddNewMapToAreaClick(QAction* triggeredAction) -{ - openNewMapPopupWindow(); - this->newMapPrompt->init(MapSortOrder::Area, triggeredAction->data()); + QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); + dialog.setWindowModality(Qt::ApplicationModal); + QDialogButtonBox newItemButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); + connect(&newItemButtonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + + QLineEdit *newNameEdit = new QLineEdit(&dialog); + newNameEdit->setClearButtonEnabled(true); + + static const QRegularExpression re_validChars("[A-Za-z_]+[\\w]*"); + newNameEdit->setValidator(new QRegularExpressionValidator(re_validChars, newNameEdit)); + + // TODO: Support arbitrary LAYOUT_ ID names (Note from GriffinR: This is already handled in an unopened PR) + QLabel *newId = new QLabel("LAYOUT_", &dialog); + connect(newNameEdit, &QLineEdit::textChanged, [&](QString text){ + newId->setText(Layout::layoutConstantFromName(text.remove("_Layout"))); + }); + + NoScrollComboBox *useExistingCombo = new NoScrollComboBox(&dialog); + useExistingCombo->addItems(this->editor->project->mapLayoutsTable); + useExistingCombo->setEnabled(false); + + QCheckBox *useExistingCheck = new QCheckBox(&dialog); + + QLabel *errorMessageLabel = new QLabel(&dialog); + errorMessageLabel->setVisible(false); + errorMessageLabel->setStyleSheet("QLabel { background-color: rgba(255, 0, 0, 25%) }"); + + QComboBox *primaryCombo = new QComboBox(&dialog); + primaryCombo->addItems(this->editor->project->primaryTilesetLabels); + QComboBox *secondaryCombo = new QComboBox(&dialog); + secondaryCombo->addItems(this->editor->project->secondaryTilesetLabels); + + QSpinBox *widthSpin = new QSpinBox(&dialog); + QSpinBox *heightSpin = new QSpinBox(&dialog); + + widthSpin->setMinimum(1); + heightSpin->setMinimum(1); + widthSpin->setMaximum(this->editor->project->getMaxMapWidth()); + heightSpin->setMaximum(this->editor->project->getMaxMapHeight()); + + connect(useExistingCheck, &QCheckBox::stateChanged, [&](int state){ + bool useExisting = (state == Qt::Checked); + useExistingCombo->setEnabled(useExisting); + primaryCombo->setEnabled(!useExisting); + secondaryCombo->setEnabled(!useExisting); + widthSpin->setEnabled(!useExisting); + heightSpin->setEnabled(!useExisting); + }); + + QFormLayout form(&dialog); + form.addRow("New Layout Name", newNameEdit); + form.addRow("New Layout ID", newId); + form.addRow("Copy Existing Layout", useExistingCheck); + form.addRow("", useExistingCombo); + form.addRow("Primary Tileset", primaryCombo); + form.addRow("Secondary Tileset", secondaryCombo); + form.addRow("Layout Width", widthSpin); + form.addRow("Layout Height", heightSpin); + form.addRow("", errorMessageLabel); + + connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ + // verify some things + QString errorMessage; + QString tryLayoutName = newNameEdit->text(); + // name not empty + if (tryLayoutName.isEmpty()) { + errorMessage = "Name cannot be empty"; + } + // unique layout name & id + else if (this->editor->project->mapLayoutsTable.contains(newId->text()) + || this->editor->project->layoutIdsToNames.find(tryLayoutName) != this->editor->project->layoutIdsToNames.end()) { + errorMessage = "Layout Name / ID is not unique"; + } + // from id is existing value + else if (useExistingCheck->isChecked()) { + if (!this->editor->project->mapLayoutsTable.contains(useExistingCombo->currentText())) { + errorMessage = "Existing layout ID is not valid"; + } + } + + if (!errorMessage.isEmpty()) { + // show error + errorMessageLabel->setText(errorMessage); + errorMessageLabel->setVisible(true); + } + else { + dialog.accept(); + } + }); + + form.addRow(&newItemButtonBox); + + if (dialog.exec() == QDialog::Accepted) { + Layout::SimpleSettings layoutSettings; + QString layoutName = newNameEdit->text(); + layoutSettings.name = layoutName; + layoutSettings.id = Layout::layoutConstantFromName(layoutName.remove("_Layout")); + if (useExistingCheck->isChecked()) { + layoutSettings.from_id = useExistingCombo->currentText(); + } else { + layoutSettings.width = widthSpin->value(); + layoutSettings.height = heightSpin->value(); + layoutSettings.tileset_primary_label = primaryCombo->currentText(); + layoutSettings.tileset_secondary_label = secondaryCombo->currentText(); + } + Layout *newLayout = this->editor->project->createNewLayout(layoutSettings); + this->layoutTreeModel->insertLayoutItem(newLayout->id); + setLayout(newLayout->id); + } } -void MainWindow::onAddNewMapToLayoutClick(QAction* triggeredAction) -{ - openNewMapPopupWindow(); - this->newMapPrompt->init(MapSortOrder::Layout, triggeredAction->data()); +void MainWindow::mapListAddArea() { + QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); + dialog.setWindowModality(Qt::ApplicationModal); + QDialogButtonBox newItemButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); + connect(&newItemButtonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + + const QString prefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix); + QLineEdit *newNameEdit = new QLineEdit(&dialog); + QLineEdit *newNameDisplay = new QLineEdit(&dialog); + newNameDisplay->setText(prefix); + newNameDisplay->setEnabled(false); + connect(newNameEdit, &QLineEdit::textEdited, [newNameDisplay, prefix] (const QString &text) { + // As the user types a name, update the label to show the name with the prefix. + newNameDisplay->setText(prefix + text); + }); + + QLabel *errorMessageLabel = new QLabel(&dialog); + errorMessageLabel->setVisible(false); + errorMessageLabel->setStyleSheet("QLabel { background-color: rgba(255, 0, 0, 25%) }"); + + static const QRegularExpression re_validChars("[A-Za-z_]+[\\w]*"); + newNameEdit->setValidator(new QRegularExpressionValidator(re_validChars, newNameEdit)); + + connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ + const QString newAreaName = newNameDisplay->text(); + if (this->editor->project->mapSectionIdNames.contains(newAreaName)){ + errorMessageLabel->setText(QString("An area with the name '%1' already exists").arg(newAreaName)); + errorMessageLabel->setVisible(true); + } else { + dialog.accept(); + } + }); + + QLabel *newNameEditLabel = new QLabel("New Area Name", &dialog); + QLabel *newNameDisplayLabel = new QLabel("Constant Name", &dialog); + newNameDisplayLabel->setEnabled(false); + + QFormLayout form(&dialog); + + form.addRow(newNameEditLabel, newNameEdit); + form.addRow(newNameDisplayLabel, newNameDisplay); + form.addRow("", errorMessageLabel); + form.addRow(&newItemButtonBox); + + if (dialog.exec() == QDialog::Accepted) { + if (newNameEdit->text().isEmpty()) return; + this->mapAreaModel->insertAreaItem(newNameDisplay->text()); + } } void MainWindow::onNewMapCreated() { @@ -1306,29 +1601,36 @@ void MainWindow::onNewMapCreated() { logInfo(QString("Created a new map named %1.").arg(newMapName)); + // TODO: Creating a new map shouldn't be automatically saved editor->project->saveMap(newMap); editor->project->saveAllDataStructures(); - QStandardItem* groupItem = mapGroupItemsList->at(newMapGroup); - int numMapsInGroup = groupItem->rowCount(); - - QStandardItem *newMapItem = createMapItem(newMapName, newMapGroup, numMapsInGroup); - groupItem->appendRow(newMapItem); - mapListIndexes.insert(newMapName, newMapItem->index()); - - sortMapList(); - setMap(newMapName, true); + // Add new Map / Layout to the mapList models + this->mapGroupModel->insertMapItem(newMapName, editor->project->groupNames[newMapGroup]); + this->mapAreaModel->insertMapItem(newMapName, newMap->location, newMapGroup); + this->layoutTreeModel->insertMapItem(newMapName, newMap->layout->id); // 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); + int mapIndex = this->editor->project->mapNames.indexOf(newMapName); + if (mapIndex >= 0) { + const QSignalBlocker b_DiveMap(ui->comboBox_DiveMap); + const QSignalBlocker b_EmergeMap(ui->comboBox_EmergeMap); + ui->comboBox_DiveMap->insertItem(mapIndex, newMapName); + ui->comboBox_EmergeMap->insertItem(mapIndex, newMapName); + } + + // Refresh layout combo box (if a new one was created) + if (!existingLayout) { + int layoutIndex = this->editor->project->mapLayoutsTable.indexOf(newMap->layout->id); + if (layoutIndex >= 0) { + const QSignalBlocker b_Layouts(ui->comboBox_LayoutSelector); + ui->comboBox_LayoutSelector->insertItem(layoutIndex, newMap->layout->id); + } } + setMap(newMapName); + if (newMap->needsHealLocation) { addNewEvent(Event::Type::HealLocation); editor->project->saveHealLocations(newMap); @@ -1354,6 +1656,7 @@ void MainWindow::openNewMapPopupWindow() { void MainWindow::on_action_NewMap_triggered() { openNewMapPopupWindow(); + this->newMapPrompt->initUi(); this->newMapPrompt->init(); } @@ -1429,7 +1732,7 @@ void MainWindow::on_actionNew_Tileset_triggered() { } mt->tiles.append(tile); } - newSet.metatiles.append(mt); + newSet.addMetatile(mt); } for(int i = 0; i < 16; ++i) { QList currentPal; @@ -1475,7 +1778,7 @@ void MainWindow::on_actionNew_Tileset_triggered() { void MainWindow::updateTilesetEditor() { if (this->tilesetEditor) { this->tilesetEditor->update( - this->editor->map, + this->editor->layout, editor->ui->comboBox_PrimaryTileset->currentText(), editor->ui->comboBox_SecondaryTileset->currentText() ); @@ -1530,62 +1833,68 @@ void MainWindow::currentMetatilesSelectionChanged() { scrollMetatileSelectorToSelection(); } -void MainWindow::on_mapList_activated(const QModelIndex &index) -{ - QVariant data = index.data(Qt::UserRole); - if (index.data(MapListUserRoles::TypeRole) == "map_name" && !data.isNull()) - userSetMap(data.toString()); +void MainWindow::saveMapListTab(int index) { + porymapConfig.mapListTab = index; } -void MainWindow::updateMapListIcon(const QString &mapName) { - if (!editor->project || !editor->project->mapCache.contains(mapName)) +void MainWindow::openMapListItem(const QModelIndex &index) { + if (!index.isValid()) return; - QStandardItem *item = mapListModel->itemFromIndex(mapListIndexes.value(mapName)); - if (!item) + QVariant data = index.data(Qt::UserRole); + if (data.isNull()) return; - static const QIcon mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico")); - static const QIcon mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico")); + // Normally when a new map/layout is opened the search filters are cleared and the lists will scroll to display that map/layout in the list. + // We don't want to do this when the user interacts with a list directly, so we temporarily prevent changes to the search filter. + auto toolbar = getCurrentMapListToolBar(); + if (toolbar) toolbar->setFilterLocked(true); - 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); + QString type = index.data(MapListUserRoles::TypeRole).toString(); + if (type == "map_name") { + userSetMap(data.toString()); + } else if (type == "map_layout") { + userSetLayout(data.toString()); } + + if (toolbar) toolbar->setFilterLocked(false); } void MainWindow::updateMapList() { - QList 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()); - } - } + if (this->editor->map) { + this->mapGroupModel->setMap(this->editor->map->name); + this->groupListProxyModel->layoutChanged(); + this->mapAreaModel->setMap(this->editor->map->name); + this->areaListProxyModel->layoutChanged(); + } else { + this->mapGroupModel->setMap(QString()); + this->groupListProxyModel->layoutChanged(); + this->ui->mapList->clearSelection(); + this->mapAreaModel->setMap(QString()); + this->areaListProxyModel->layoutChanged(); + this->ui->areaList->clearSelection(); + } + + if (this->editor->layout) { + this->layoutTreeModel->setLayout(this->editor->layout->id); + this->layoutListProxyModel->layoutChanged(); + } else { + this->layoutTreeModel->setLayout(QString()); + this->layoutListProxyModel->layoutChanged(); + this->ui->layoutList->clearSelection(); } } void MainWindow::on_action_Save_Project_triggered() { editor->saveProject(); + updateWindowTitle(); updateMapList(); - showWindowTitle(); } void MainWindow::on_action_Save_triggered() { editor->save(); - if (editor->map) - updateMapListIcon(editor->map->name); - showWindowTitle(); + updateWindowTitle(); + updateMapList(); } void MainWindow::duplicate() { @@ -1640,7 +1949,7 @@ void MainWindow::copy() { case MainTab::Map: { // copy the map image - QPixmap pixmap = editor->map ? editor->map->render(true) : QPixmap(); + QPixmap pixmap = editor->layout ? editor->layout->render(true) : QPixmap(); setClipboardData(pixmap.toImage()); logInfo("Copied current map image to clipboard"); break; @@ -1711,7 +2020,7 @@ void MainWindow::setClipboardData(QImage image) { } void MainWindow::paste() { - if (!editor || !editor->project || !editor->map) return; + if (!editor || !editor->project || !(editor->map || editor->layout)) return; QClipboard *clipboard = QGuiApplication::clipboard(); QString clipboardText(clipboard->text()); @@ -1727,7 +2036,6 @@ void MainWindow::paste() { else if (!clipboardText.isEmpty()) { // we only can paste json text // so, check if clipboard text is valid json - QString parseError; QJsonDocument pasteJsonDoc = QJsonDocument::fromJson(clipboardText.toUtf8()); // test empty @@ -1817,17 +2125,17 @@ void MainWindow::on_mapViewTab_tabBarClicked(int index) Scripting::cb_MapViewTabChanged(oldIndex, index); if (index == MapViewTab::Metatiles) { - editor->setEditingMap(); + editor->setEditingMetatiles(); } else if (index == MapViewTab::Collision) { editor->setEditingCollision(); } else if (index == MapViewTab::Prefabs) { - editor->setEditingMap(); + editor->setEditingMetatiles(); if (projectConfig.prefabFilepath.isEmpty() && !projectConfig.prefabImportPrompted) { // User hasn't set up prefabs and hasn't been prompted before. // Ask if they'd like to import the default prefabs file. if (prefab.tryImportDefaultPrefabs(this, projectConfig.baseGameVersion)) - prefab.updatePrefabUi(this->editor->map); - } + prefab.updatePrefabUi(this->editor->layout); + } } editor->setCursorRectVisible(false); } @@ -1851,16 +2159,18 @@ void MainWindow::on_mainTabBar_tabBarClicked(int index) if (index == MainTab::Map) { ui->stackedWidget_MapEvents->setCurrentIndex(0); on_mapViewTab_tabBarClicked(ui->mapViewTab->currentIndex()); - clickToolButtonFromEditMode(editor->map_edit_mode); + clickToolButtonFromEditAction(editor->mapEditAction); } else if (index == MainTab::Events) { ui->stackedWidget_MapEvents->setCurrentIndex(1); editor->setEditingObjects(); - clickToolButtonFromEditMode(editor->obj_edit_mode); + clickToolButtonFromEditAction(editor->objectEditAction); } else if (index == MainTab::Connections) { editor->setEditingConnections(); - // Stop the Dive/Emerge combo boxes from getting the initial focus - ui->graphicsView_Connections->setFocus(); + } else if (index == MainTab::WildPokemon) { + editor->setEditingEncounters(); } + + if (!editor->map) return; if (index != MainTab::WildPokemon) { if (editor->project && editor->project->wildEncountersLoaded) editor->saveEncounterTabData(); @@ -1907,6 +2217,18 @@ void MainWindow::on_actionCursor_Tile_Outline_triggered() } } +void MainWindow::on_actionShow_Grid_triggered() { + this->editor->toggleGrid(ui->actionShow_Grid->isChecked()); +} + +void MainWindow::on_actionGrid_Settings_triggered() { + if (!this->gridSettingsDialog) { + this->gridSettingsDialog = new GridSettingsDialog(&this->editor->gridSettings, this); + connect(this->gridSettingsDialog, &GridSettingsDialog::changedGridSettings, this->editor, &Editor::updateMapGrid); + } + openSubWindow(this->gridSettingsDialog); +} + void MainWindow::on_actionShortcuts_triggered() { if (!shortcutsEditor) @@ -2033,7 +2355,6 @@ void MainWindow::updateObjects() { } void MainWindow::updateSelectedObjects() { - QList all_events = editor->getObjects(); QList events; if (editor->selected_events && editor->selected_events->length()) { @@ -2288,66 +2609,12 @@ void MainWindow::on_horizontalSlider_CollisionTransparency_valueChanged(int valu this->editor->collision_item->draw(true); } -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; - QList selectedEvents; - int numDeleted = 0; - for (DraggablePixmapItem *item : *editor->selected_events) { - Event::Group event_group = item->event->getEventGroup(); - if (event_group != Event::Group::Heal) { - numDeleted++; - item->event->setPixmapItem(item); - selectedEvents.append(item->event); - } - else { // don't allow deletion of heal locations - logWarn(QString("Cannot delete event of type '%1'").arg(Event::eventTypeToString(item->event->getEventType()))); - } - } - if (numDeleted) { - // Get the index for the event that should be selected after this event has been deleted. - // Select event at next smallest index when deleting a single event. - // If deleting multiple events, just let editor work out next selected. - if (numDeleted == 1) { - Event::Group event_group = selectedEvents[0]->getEventGroup(); - int index = editor->map->events.value(event_group).indexOf(selectedEvents[0]); - if (index != editor->map->events.value(event_group).size() - 1) - index++; - else - index--; - Event *event = nullptr; - if (index >= 0) - event = editor->map->events.value(event_group).at(index); - for (QGraphicsItem *child : editor->events_group->childItems()) { - DraggablePixmapItem *event_item = static_cast(child); - if (event_item->event == event) { - nextSelectedEvent = event_item; - break; - } - } - } - editor->map->editHistory.push(new EventDelete(editor, editor->map, selectedEvents, nextSelectedEvent ? nextSelectedEvent->event : nullptr)); - } - } - } -} - void MainWindow::on_toolButton_Paint_clicked() { if (ui->mainTabBar->currentIndex() == MainTab::Map) - editor->map_edit_mode = "paint"; + editor->mapEditAction = Editor::EditAction::Paint; else - editor->obj_edit_mode = "paint"; + editor->objectEditAction = Editor::EditAction::Paint; editor->settings->mapCursor = QCursor(QPixmap(":/icons/pencil_cursor.ico"), 10, 10); @@ -2366,9 +2633,9 @@ void MainWindow::on_toolButton_Paint_clicked() void MainWindow::on_toolButton_Select_clicked() { if (ui->mainTabBar->currentIndex() == MainTab::Map) - editor->map_edit_mode = "select"; + editor->mapEditAction = Editor::EditAction::Select; else - editor->obj_edit_mode = "select"; + editor->objectEditAction = Editor::EditAction::Select; editor->settings->mapCursor = QCursor(); editor->cursorMapTileRect->setSingleTileMode(); @@ -2385,9 +2652,9 @@ void MainWindow::on_toolButton_Select_clicked() void MainWindow::on_toolButton_Fill_clicked() { if (ui->mainTabBar->currentIndex() == MainTab::Map) - editor->map_edit_mode = "fill"; + editor->mapEditAction = Editor::EditAction::Fill; else - editor->obj_edit_mode = "fill"; + editor->objectEditAction = Editor::EditAction::Fill; editor->settings->mapCursor = QCursor(QPixmap(":/icons/fill_color_cursor.ico"), 10, 10); editor->cursorMapTileRect->setSingleTileMode(); @@ -2404,9 +2671,9 @@ void MainWindow::on_toolButton_Fill_clicked() void MainWindow::on_toolButton_Dropper_clicked() { if (ui->mainTabBar->currentIndex() == MainTab::Map) - editor->map_edit_mode = "pick"; + editor->mapEditAction = Editor::EditAction::Pick; else - editor->obj_edit_mode = "pick"; + editor->objectEditAction = Editor::EditAction::Pick; editor->settings->mapCursor = QCursor(QPixmap(":/icons/pipette_cursor.ico"), 10, 10); editor->cursorMapTileRect->setSingleTileMode(); @@ -2423,9 +2690,9 @@ void MainWindow::on_toolButton_Dropper_clicked() void MainWindow::on_toolButton_Move_clicked() { if (ui->mainTabBar->currentIndex() == MainTab::Map) - editor->map_edit_mode = "move"; + editor->mapEditAction = Editor::EditAction::Move; else - editor->obj_edit_mode = "move"; + editor->objectEditAction = Editor::EditAction::Move; editor->settings->mapCursor = QCursor(QPixmap(":/icons/move.ico"), 7, 7); editor->cursorMapTileRect->setSingleTileMode(); @@ -2442,9 +2709,9 @@ void MainWindow::on_toolButton_Move_clicked() void MainWindow::on_toolButton_Shift_clicked() { if (ui->mainTabBar->currentIndex() == MainTab::Map) - editor->map_edit_mode = "shift"; + editor->mapEditAction = Editor::EditAction::Shift; else - editor->obj_edit_mode = "shift"; + editor->objectEditAction = Editor::EditAction::Shift; editor->settings->mapCursor = QCursor(QPixmap(":/icons/shift_cursor.ico"), 10, 10); editor->cursorMapTileRect->setSingleTileMode(); @@ -2459,37 +2726,37 @@ void MainWindow::on_toolButton_Shift_clicked() } void MainWindow::checkToolButtons() { - QString edit_mode; + Editor::EditAction editAction; if (ui->mainTabBar->currentIndex() == MainTab::Map) { - edit_mode = editor->map_edit_mode; + editAction = editor->mapEditAction; } else { - edit_mode = editor->obj_edit_mode; - if (edit_mode == "select" && editor->map_ruler) + editAction = editor->objectEditAction; + if (editAction == Editor::EditAction::Select && editor->map_ruler) editor->map_ruler->setEnabled(true); else if (editor->map_ruler) editor->map_ruler->setEnabled(false); } - ui->toolButton_Paint->setChecked(edit_mode == "paint"); - ui->toolButton_Select->setChecked(edit_mode == "select"); - ui->toolButton_Fill->setChecked(edit_mode == "fill"); - ui->toolButton_Dropper->setChecked(edit_mode == "pick"); - ui->toolButton_Move->setChecked(edit_mode == "move"); - ui->toolButton_Shift->setChecked(edit_mode == "shift"); + ui->toolButton_Paint->setChecked(editAction == Editor::EditAction::Paint); + ui->toolButton_Select->setChecked(editAction == Editor::EditAction::Select); + ui->toolButton_Fill->setChecked(editAction == Editor::EditAction::Fill); + ui->toolButton_Dropper->setChecked(editAction == Editor::EditAction::Pick); + ui->toolButton_Move->setChecked(editAction == Editor::EditAction::Move); + ui->toolButton_Shift->setChecked(editAction == Editor::EditAction::Shift); } -void MainWindow::clickToolButtonFromEditMode(QString editMode) { - if (editMode == "paint") { +void MainWindow::clickToolButtonFromEditAction(Editor::EditAction editAction) { + if (editAction == Editor::EditAction::Paint) { on_toolButton_Paint_clicked(); - } else if (editMode == "select") { + } else if (editAction == Editor::EditAction::Select) { on_toolButton_Select_clicked(); - } else if (editMode == "fill") { + } else if (editAction == Editor::EditAction::Fill) { on_toolButton_Fill_clicked(); - } else if (editMode == "pick") { + } else if (editAction == Editor::EditAction::Pick) { on_toolButton_Dropper_clicked(); - } else if (editMode == "move") { + } else if (editAction == Editor::EditAction::Move) { on_toolButton_Move_clicked(); - } else if (editMode == "shift") { + } else if (editAction == Editor::EditAction::Shift) { on_toolButton_Shift_clicked(); } } @@ -2497,34 +2764,30 @@ void MainWindow::clickToolButtonFromEditMode(QString editMode) { void MainWindow::onOpenConnectedMap(MapConnection *connection) { if (!connection) return; - if (userSetMap(connection->targetMapName(), true)) + if (userSetMap(connection->targetMapName())) editor->setSelectedConnection(connection->findMirror()); } -void MainWindow::onMapNeedsRedrawing() { - redrawMapScene(); -} - -void MainWindow::onMapCacheCleared() { - editor->map = nullptr; +void MainWindow::onLayoutChanged(Layout *) { + updateMapList(); } void MainWindow::onMapLoaded(Map *map) { - connect(map, &Map::modified, [this, map] { this->markMapEdited(map); }); + connect(map, &Map::modified, [this, map] { this->markSpecificMapEdited(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 bool updated = false; - if (primaryTilesetLabel == this->editor->map->layout->tileset_primary_label) { + if (primaryTilesetLabel == this->editor->layout->tileset_primary_label) { this->editor->updatePrimaryTileset(primaryTilesetLabel, true); Scripting::cb_TilesetUpdated(primaryTilesetLabel); updated = true; } else { this->editor->project->getTileset(primaryTilesetLabel, true); } - if (secondaryTilesetLabel == this->editor->map->layout->tileset_secondary_label) { + if (secondaryTilesetLabel == this->editor->layout->tileset_secondary_label) { this->editor->updateSecondaryTileset(secondaryTilesetLabel, true); Scripting::cb_TilesetUpdated(secondaryTilesetLabel); updated = true; @@ -2557,6 +2820,17 @@ void MainWindow::on_action_Export_Map_Image_triggered() { } void MainWindow::on_actionExport_Stitched_Map_Image_triggered() { + if (!this->editor->map) { + QMessageBox warning(this); + warning.setText("Notice"); + warning.setInformativeText("Map stitch images are not possible without a map selected."); + warning.setStandardButtons(QMessageBox::Ok); + warning.setDefaultButton(QMessageBox::Cancel); + warning.setIcon(QMessageBox::Warning); + + warning.exec(); + return; + } showExportMapImageWindow(ImageExporterMode::Stitch); } @@ -2570,18 +2844,14 @@ void MainWindow::on_actionImport_Map_from_Advance_Map_1_92_triggered(){ void MainWindow::importMapFromAdvanceMap1_92() { - QString filepath = QFileDialog::getOpenFileName( - this, - QString("Import Map from Advance Map 1.92"), - this->editor->project->importExportPath, - "Advance Map 1.92 Map Files (*.map)"); + QString filepath = FileDialog::getOpenFileName(this, "Import Map from Advance Map 1.92", "", "Advance Map 1.92 Map Files (*.map)"); if (filepath.isEmpty()) { return; } - this->editor->project->setImportExportPath(filepath); + MapParser parser; bool error = false; - MapLayout *mapLayout = parser.parse(filepath, &error, editor->project); + Layout *mapLayout = parser.parse(filepath, &error, editor->project); if (error) { QMessageBox msgBox(this); msgBox.setText("Failed to import map from Advance Map 1.92 .map file."); @@ -2644,13 +2914,13 @@ void MainWindow::on_pushButton_ConfigureEncountersJSON_clicked() { void MainWindow::on_button_OpenDiveMap_clicked() { const QString mapName = ui->comboBox_DiveMap->currentText(); if (editor->project->mapNames.contains(mapName)) - userSetMap(mapName, true); + userSetMap(mapName); } void MainWindow::on_button_OpenEmergeMap_clicked() { const QString mapName = ui->comboBox_EmergeMap->currentText(); if (editor->project->mapNames.contains(mapName)) - userSetMap(mapName, true); + userSetMap(mapName); } void MainWindow::on_comboBox_DiveMap_currentTextChanged(const QString &mapName) { @@ -2666,30 +2936,31 @@ void MainWindow::on_comboBox_EmergeMap_currentTextChanged(const QString &mapName void MainWindow::on_comboBox_PrimaryTileset_currentTextChanged(const QString &tilesetLabel) { - if (editor->project->primaryTilesetLabels.contains(tilesetLabel) && editor->map) { + if (editor->project->primaryTilesetLabels.contains(tilesetLabel) && editor->layout) { editor->updatePrimaryTileset(tilesetLabel); redrawMapScene(); on_horizontalSlider_MetatileZoom_valueChanged(ui->horizontalSlider_MetatileZoom->value()); updateTilesetEditor(); - prefab.updatePrefabUi(editor->map); + prefab.updatePrefabUi(editor->layout); markMapEdited(); } } void MainWindow::on_comboBox_SecondaryTileset_currentTextChanged(const QString &tilesetLabel) { - if (editor->project->secondaryTilesetLabels.contains(tilesetLabel) && editor->map) { + if (editor->project->secondaryTilesetLabels.contains(tilesetLabel) && editor->layout) { editor->updateSecondaryTileset(tilesetLabel); redrawMapScene(); on_horizontalSlider_MetatileZoom_valueChanged(ui->horizontalSlider_MetatileZoom->value()); updateTilesetEditor(); - prefab.updatePrefabUi(editor->map); + prefab.updatePrefabUi(editor->layout); markMapEdited(); } } -void MainWindow::on_pushButton_ChangeDimensions_clicked() -{ +void MainWindow::on_pushButton_ChangeDimensions_clicked() { + if (!editor || !editor->layout) return; + QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); dialog.setWindowTitle("Change Map Dimensions"); dialog.setWindowModality(Qt::NonModal); @@ -2708,10 +2979,10 @@ void MainWindow::on_pushButton_ChangeDimensions_clicked() heightSpinBox->setMaximum(editor->project->getMaxMapHeight()); bwidthSpinBox->setMaximum(MAX_BORDER_WIDTH); bheightSpinBox->setMaximum(MAX_BORDER_HEIGHT); - widthSpinBox->setValue(editor->map->getWidth()); - heightSpinBox->setValue(editor->map->getHeight()); - bwidthSpinBox->setValue(editor->map->getBorderWidth()); - bheightSpinBox->setValue(editor->map->getBorderHeight()); + widthSpinBox->setValue(editor->layout->getWidth()); + heightSpinBox->setValue(editor->layout->getHeight()); + bwidthSpinBox->setValue(editor->layout->getBorderWidth()); + bheightSpinBox->setValue(editor->layout->getBorderHeight()); if (projectConfig.useCustomBorderSize) { form.addRow(new QLabel("Map Width"), widthSpinBox); form.addRow(new QLabel("Map Height"), heightSpinBox); @@ -2739,8 +3010,8 @@ void MainWindow::on_pushButton_ChangeDimensions_clicked() dialog.accept(); } else { QString errorText = QString("Error: The specified width and height are too large.\n" - "The maximum map width and height is the following: (width + 15) * (height + 14) <= %1\n" - "The specified map width and height was: (%2 + 15) * (%3 + 14) = %4") + "The maximum layout width and height is the following: (width + 15) * (height + 14) <= %1\n" + "The specified layout width and height was: (%2 + 15) * (%3 + 14) = %4") .arg(maxMetatiles) .arg(widthSpinBox->value()) .arg(heightSpinBox->value()) @@ -2754,21 +3025,21 @@ void MainWindow::on_pushButton_ChangeDimensions_clicked() form.addRow(errorLabel); if (dialog.exec() == QDialog::Accepted) { - Map *map = editor->map; - Blockdata oldMetatiles = map->layout->blockdata; - Blockdata oldBorder = map->layout->border; - QSize oldMapDimensions(map->getWidth(), map->getHeight()); - QSize oldBorderDimensions(map->getBorderWidth(), map->getBorderHeight()); + Layout *layout = editor->layout; + Blockdata oldMetatiles = layout->blockdata; + Blockdata oldBorder = layout->border; + QSize oldMapDimensions(layout->getWidth(), layout->getHeight()); + QSize oldBorderDimensions(layout->getBorderWidth(), layout->getBorderHeight()); QSize newMapDimensions(widthSpinBox->value(), heightSpinBox->value()); QSize newBorderDimensions(bwidthSpinBox->value(), bheightSpinBox->value()); if (oldMapDimensions != newMapDimensions || oldBorderDimensions != newBorderDimensions) { - editor->map->setDimensions(newMapDimensions.width(), newMapDimensions.height(), true, true); - editor->map->setBorderDimensions(newBorderDimensions.width(), newBorderDimensions.height(), true, true); - editor->map->editHistory.push(new ResizeMap(map, + layout->setDimensions(newMapDimensions.width(), newMapDimensions.height(), true, true); + layout->setBorderDimensions(newBorderDimensions.width(), newBorderDimensions.height(), true, true); + editor->layout->editHistory.push(new ResizeLayout(layout, oldMapDimensions, newMapDimensions, - oldMetatiles, map->layout->blockdata, + oldMetatiles, layout->blockdata, oldBorderDimensions, newBorderDimensions, - oldBorder, map->layout->border + oldBorder, layout->border )); } } @@ -2808,22 +3079,47 @@ void MainWindow::on_actionTileset_Editor_triggered() } void MainWindow::initTilesetEditor() { - this->tilesetEditor = new TilesetEditor(this->editor->project, this->editor->map, this); + this->tilesetEditor = new TilesetEditor(this->editor->project, this->editor->layout, this); connect(this->tilesetEditor, &TilesetEditor::tilesetsSaved, this, &MainWindow::onTilesetsSaved); } -void MainWindow::on_toolButton_ExpandAll_clicked() -{ - if (ui->mapList) { - ui->mapList->expandToDepth(0); +MapListToolBar* MainWindow::getCurrentMapListToolBar() { + switch (ui->mapListContainer->currentIndex()) { + case MapListTab::Groups: return ui->mapListToolBar_Groups; + case MapListTab::Areas: return ui->mapListToolBar_Areas; + case MapListTab::Layouts: return ui->mapListToolBar_Layouts; + default: return nullptr; } } -void MainWindow::on_toolButton_CollapseAll_clicked() -{ - if (ui->mapList) { - ui->mapList->collapseAll(); - } +MapTree* MainWindow::getCurrentMapList() { + auto toolbar = getCurrentMapListToolBar(); + if (toolbar) + return toolbar->list(); + return nullptr; +} + +// Clear the search filters on all the map lists. +// When the search filter is cleared the map lists will (if possible) display the currently-selected map/layout. +void MainWindow::resetMapListFilters() { + ui->mapListToolBar_Groups->clearFilter(); + ui->mapListToolBar_Areas->clearFilter(); + ui->mapListToolBar_Layouts->clearFilter(); +} + +void MainWindow::mapListShortcut_ExpandAll() { + auto toolbar = getCurrentMapListToolBar(); + if (toolbar) toolbar->expandList(); +} + +void MainWindow::mapListShortcut_CollapseAll() { + auto toolbar = getCurrentMapListToolBar(); + if (toolbar) toolbar->collapseList(); +} + +void MainWindow::mapListShortcut_ToggleEmptyFolders() { + auto toolbar = getCurrentMapListToolBar(); + if (toolbar) toolbar->toggleEmptyFolders(); } void MainWindow::on_actionAbout_Porymap_triggered() @@ -2926,7 +3222,7 @@ void MainWindow::reloadScriptEngine() { // Lying to the scripts here, simulating a project reload Scripting::cb_ProjectOpened(projectConfig.projectDir); if (editor && editor->map) - Scripting::cb_MapOpened(editor->map->name); + Scripting::cb_MapOpened(editor->map->name); // TODO: API should have equivalent for layout } void MainWindow::on_pushButton_AddCustomHeaderField_clicked() @@ -3012,7 +3308,7 @@ void MainWindow::on_actionRegion_Map_Editor_triggered() { } void MainWindow::on_pushButton_CreatePrefab_clicked() { - PrefabCreationDialog dialog(this, this->editor->metatile_selector_item, this->editor->map); + PrefabCreationDialog dialog(this, this->editor->metatile_selector_item, this->editor->layout); dialog.setWindowTitle("Create Prefab"); dialog.setWindowModality(Qt::NonModal); if (dialog.exec() == QDialog::Accepted) { @@ -3059,6 +3355,11 @@ bool MainWindow::askToFixRegionMapEditor() { return false; } +void MainWindow::clearOverlay() { + if (ui->graphicsView_Map) + ui->graphicsView_Map->clearOverlayMap(); +} + // Attempt to close any open sub-windows of the main window, giving each a chance to abort the process. // Each of these windows is a widget with WA_DeleteOnClose set, so manually deleting them isn't necessary. // Because they're tracked with QPointers nullifying them shouldn't be necessary either, but it seems the @@ -3110,16 +3411,7 @@ bool MainWindow::closeProject() { if (!isProjectOpen()) return true; - // 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) { + if (this->editor->project->hasUnsavedChanges()) { QMessageBox::StandardButton result = QMessageBox::question( this, "porymap", "The project has been modified, save changes?", QMessageBox::No | QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); @@ -3132,10 +3424,10 @@ bool MainWindow::closeProject() { return false; } } - clearProjectUI(); editor->closeProject(); + clearProjectUI(); setWindowDisabled(true); - setWindowTitle(QCoreApplication::applicationName()); + updateWindowTitle(); return true; } diff --git a/src/project.cpp b/src/project.cpp index c87d2c9ce..43b62ecfa 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -7,6 +7,7 @@ #include "tile.h" #include "tileset.h" #include "map.h" +#include "filedialog.h" #include "orderedjson.h" @@ -34,60 +35,23 @@ int Project::max_map_data_size = 10240; // 0x2800 int Project::default_map_size = 20; int Project::max_object_events = 64; -Project::Project(QWidget *parent) : +Project::Project(QObject *parent) : QObject(parent) { - initSignals(); + QObject::connect(&this->fileWatcher, &QFileSystemWatcher::fileChanged, this, &Project::fileChanged); } Project::~Project() { clearMapCache(); clearTilesetCache(); -} - -void Project::initSignals() { - // detect changes to specific filepaths being monitored - QObject::connect(&fileWatcher, &QFileSystemWatcher::fileChanged, [this](QString changed){ - if (!porymapConfig.monitorFiles) return; - if (modifiedFileTimestamps.contains(changed)) { - if (QDateTime::currentMSecsSinceEpoch() < modifiedFileTimestamps[changed]) { - return; - } - modifiedFileTimestamps.remove(changed); - } - - static bool showing = false; - if (showing) return; - - QMessageBox notice(this->parentWidget()); - notice.setText("File Changed"); - notice.setInformativeText(QString("The file %1 has changed on disk. Would you like to reload the project?") - .arg(changed.remove(this->root + "/"))); - notice.setStandardButtons(QMessageBox::No | QMessageBox::Yes); - notice.setDefaultButton(QMessageBox::No); - notice.setIcon(QMessageBox::Question); - - QCheckBox showAgainCheck("Do not ask again."); - notice.setCheckBox(&showAgainCheck); - - showing = true; - int choice = notice.exec(); - if (choice == QMessageBox::Yes) { - emit reloadProject(); - } else if (choice == QMessageBox::No) { - if (showAgainCheck.isChecked()) { - porymapConfig.monitorFiles = false; - emit uncheckMonitorFilesAction(); - } - } - showing = false; - }); + clearMapLayouts(); + clearEventGraphics(); } void Project::set_root(QString dir) { this->root = dir; - this->importExportPath = dir; + FileDialog::setDirectory(dir); this->parser.set_root(dir); } @@ -160,7 +124,6 @@ void Project::clearMapCache() { delete map; } mapCache.clear(); - emit mapCacheCleared(); } void Project::clearTilesetCache() { @@ -362,6 +325,7 @@ bool Project::loadMapData(Map* map) { heal->setRespawnNPC(loc.respawnNPC); } map->events[Event::Group::Heal].append(heal); + map->ownedEvents.append(heal); } } @@ -425,15 +389,87 @@ QString Project::readMapLocation(QString map_name) { return ParseUtil::jsonToQString(mapObj["region_map_section"]); } -bool Project::loadLayout(MapLayout *layout) { - // Force these to run even if one fails - bool loadedTilesets = loadLayoutTilesets(layout); - bool loadedBlockdata = loadBlockdata(layout); - bool loadedBorder = loadLayoutBorder(layout); +Layout *Project::createNewLayout(Layout::SimpleSettings &layoutSettings) { + QString basePath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders); + Layout *layout; + + // Handle the case where we are copying from an existing layout first. + if (!layoutSettings.from_id.isEmpty()) { + // load from layout + loadLayout(mapLayouts[layoutSettings.from_id]); + + layout = mapLayouts[layoutSettings.from_id]->copy(); + layout->name = layoutSettings.name; + layout->id = layoutSettings.id; + layout->border_path = QString("%1%2/border.bin").arg(basePath, layoutSettings.name); + layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, layoutSettings.name); + } + else { + layout = new Layout; + + layout->name = layoutSettings.name; + layout->id = layoutSettings.id; + layout->width = layoutSettings.width; + layout->height = layoutSettings.height; + layout->border_width = DEFAULT_BORDER_WIDTH; + layout->border_height = DEFAULT_BORDER_HEIGHT; + layout->tileset_primary_label = layoutSettings.tileset_primary_label; + layout->tileset_secondary_label = layoutSettings.tileset_secondary_label; + layout->border_path = QString("%1%2/border.bin").arg(basePath, layoutSettings.name); + layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, layoutSettings.name); + + setNewLayoutBlockdata(layout); + setNewLayoutBorder(layout); + } + + // Create a new directory for the layout + QString newLayoutDir = QString(root + "/%1%2").arg(projectConfig.getFilePath(ProjectFilePath::data_layouts_folders), layout->name); + if (!QDir::root().mkdir(newLayoutDir)) { + logError(QString("Error: failed to create directory for new layout: '%1'").arg(newLayoutDir)); + delete layout; + return nullptr; + } + + mapLayouts.insert(layout->id, layout); + mapLayoutsMaster.insert(layout->id, layout->copy()); + mapLayoutsTable.append(layout->id); + mapLayoutsTableMaster.append(layout->id); + layoutIdsToNames.insert(layout->id, layout->name); + + saveLayout(layout); + + this->loadLayout(layout); + + return layout; +} + +bool Project::loadLayout(Layout *layout) { + if (!layout->loaded) { + // Force these to run even if one fails + bool loadedTilesets = loadLayoutTilesets(layout); + bool loadedBlockdata = loadBlockdata(layout); + bool loadedBorder = loadLayoutBorder(layout); + + if (loadedTilesets && loadedBlockdata && loadedBorder) { + layout->loaded = true; + return true; + } else { + return false; + } + } + return true; +} + +Layout *Project::loadLayout(QString layoutId) { + if (mapLayouts.contains(layoutId)) { + Layout *layout = mapLayouts[layoutId]; + if (loadLayout(layout)) { + return layout; + } + } - return loadedTilesets - && loadedBlockdata - && loadedBorder; + logError(QString("Error: Failed to load layout '%1'").arg(layoutId)); + return nullptr; } bool Project::loadMapLayout(Map* map) { @@ -455,9 +491,17 @@ bool Project::loadMapLayout(Map* map) { } } -bool Project::readMapLayouts() { +void Project::clearMapLayouts() { + qDeleteAll(mapLayouts); mapLayouts.clear(); + qDeleteAll(mapLayoutsMaster); + mapLayoutsMaster.clear(); mapLayoutsTable.clear(); + layoutIdsToNames.clear(); +} + +bool Project::readMapLayouts() { + clearMapLayouts(); QString layoutsFilepath = projectConfig.getFilePath(ProjectFilePath::json_layouts); QString fullFilepath = QString("%1/%2").arg(root).arg(layoutsFilepath); @@ -483,7 +527,7 @@ bool Project::readMapLayouts() { .arg(layoutsLabel)); } - QList requiredFields = QList{ + static const QList requiredFields = QList{ "id", "name", "width", @@ -501,7 +545,7 @@ bool Project::readMapLayouts() { logError(QString("Layout %1 is missing field(s) in %2.").arg(i).arg(layoutsFilepath)); return false; } - MapLayout *layout = new MapLayout(); + Layout *layout = new Layout(); layout->id = ParseUtil::jsonToQString(layoutObj["id"]); if (layout->id.isEmpty()) { logError(QString("Missing 'id' value on layout %1 in %2").arg(i).arg(layoutsFilepath)); @@ -577,14 +621,12 @@ bool Project::readMapLayouts() { return false; } mapLayouts.insert(layout->id, layout); + mapLayoutsMaster.insert(layout->id, layout->copy()); mapLayoutsTable.append(layout->id); + mapLayoutsTableMaster.append(layout->id); + layoutIdsToNames.insert(layout->id, layout->name); } - // Deep copy - mapLayoutsMaster = mapLayouts; - mapLayoutsMaster.detach(); - mapLayoutsTableMaster = mapLayoutsTable; - mapLayoutsTableMaster.detach(); return true; } @@ -601,7 +643,7 @@ void Project::saveMapLayouts() { OrderedJson::array layoutsArr; for (QString layoutId : mapLayoutsTableMaster) { - MapLayout *layout = mapLayouts.value(layoutId); + Layout *layout = mapLayoutsMaster.value(layoutId); OrderedJson::object layoutObj; layoutObj["id"] = layout->id; layoutObj["name"] = layout->name; @@ -666,6 +708,47 @@ void Project::saveMapGroups() { mapGroupsFile.close(); } +void Project::saveRegionMapSections() { + const QString filepath = QString("%1/%2").arg(this->root).arg(projectConfig.getFilePath(ProjectFilePath::json_region_map_entries)); + QFile file(filepath); + if (!file.open(QIODevice::WriteOnly)) { + logError(QString("Could not open '%1' for writing").arg(filepath)); + return; + } + + const QString emptyMapsecName = getEmptyMapsecName(); + OrderedJson::array mapSectionArray; + for (const auto &idName : this->mapSectionIdNames) { + // The 'empty' map section (MAPSEC_NONE) isn't normally present in the region map sections data file. + // We append this name to mapSectionIdNames ourselves if it isn't present, in which case we don't want to output data for it here. + if (!this->saveEmptyMapsec && idName == emptyMapsecName) + continue; + + OrderedJson::object mapSectionObj; + mapSectionObj["id"] = idName; + + if (this->regionMapEntries.contains(idName)) { + MapSectionEntry entry = this->regionMapEntries.value(idName); + mapSectionObj["name"] = entry.name; + mapSectionObj["x"] = entry.x; + mapSectionObj["y"] = entry.y; + mapSectionObj["width"] = entry.width; + mapSectionObj["height"] = entry.height; + } + + mapSectionArray.append(mapSectionObj); + } + + OrderedJson::object object; + object["map_sections"] = mapSectionArray; + + ignoreWatchedFileTemporarily(filepath); + OrderedJson json(object); + OrderedJsonDoc jsonDoc(&json); + jsonDoc.dump(&file); + file.close(); +} + void Project::saveWildMonData() { if (!this->wildEncountersLoaded) return; @@ -1004,7 +1087,7 @@ void Project::saveTilesetMetatileAttributes(Tileset *tileset) { QFile attrs_file(tileset->metatile_attrs_path); if (attrs_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { QByteArray data; - for (Metatile *metatile : tileset->metatiles) { + for (const auto &metatile : tileset->metatiles()) { uint32_t attributes = metatile->getAttributes(); for (int i = 0; i < projectConfig.metatileAttributesSize; i++) data.append(static_cast(attributes >> (8 * i))); @@ -1020,7 +1103,7 @@ void Project::saveTilesetMetatiles(Tileset *tileset) { if (metatiles_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { QByteArray data; int numTiles = projectConfig.getNumTilesInMetatile(); - for (Metatile *metatile : tileset->metatiles) { + for (const auto &metatile : tileset->metatiles()) { for (int i = 0; i < numTiles; i++) { uint16_t tile = metatile->tiles.at(i).rawValue(); data.append(static_cast(tile)); @@ -1029,7 +1112,7 @@ void Project::saveTilesetMetatiles(Tileset *tileset) { } metatiles_file.write(data); } else { - tileset->metatiles.clear(); + tileset->clearMetatiles(); logError(QString("Could not open tileset metatiles file '%1'").arg(tileset->metatiles_path)); } } @@ -1047,13 +1130,14 @@ void Project::saveTilesetTilesImage(Tileset *tileset) { } void Project::saveTilesetPalettes(Tileset *tileset) { - for (int i = 0; i < Project::getNumPalettesTotal(); i++) { + int numPalettes = qMin(tileset->palettePaths.length(), tileset->palettes.length()); + for (int i = 0; i < numPalettes; i++) { QString filepath = tileset->palettePaths.at(i); PaletteUtil::writeJASC(filepath, tileset->palettes.at(i).toVector(), 0, 16); } } -bool Project::loadLayoutTilesets(MapLayout *layout) { +bool Project::loadLayoutTilesets(Layout *layout) { layout->tileset_primary = getTileset(layout->tileset_primary_label); if (!layout->tileset_primary) { QString defaultTileset = this->getDefaultPrimaryTilesetLabel(); @@ -1121,11 +1205,11 @@ Tileset* Project::loadTileset(QString label, Tileset *tileset) { return tileset; } -bool Project::loadBlockdata(MapLayout *layout) { +bool Project::loadBlockdata(Layout *layout) { QString path = QString("%1/%2").arg(root).arg(layout->blockdata_path); layout->blockdata = readBlockdata(path); layout->lastCommitBlocks.blocks = layout->blockdata; - layout->lastCommitBlocks.mapDimensions = QSize(layout->getWidth(), layout->getHeight()); + layout->lastCommitBlocks.layoutDimensions = QSize(layout->getWidth(), layout->getHeight()); if (layout->blockdata.count() != layout->getWidth() * layout->getHeight()) { logWarn(QString("Layout blockdata length %1 does not match dimensions %2x%3 (should be %4). Resizing blockdata.") @@ -1138,19 +1222,19 @@ bool Project::loadBlockdata(MapLayout *layout) { return true; } -void Project::setNewMapBlockdata(Map *map) { - map->layout->blockdata.clear(); - int width = map->getWidth(); - int height = map->getHeight(); +void Project::setNewLayoutBlockdata(Layout *layout) { + layout->blockdata.clear(); + int width = layout->getWidth(); + int height = layout->getHeight(); Block block(projectConfig.defaultMetatileId, projectConfig.defaultCollision, projectConfig.defaultElevation); for (int i = 0; i < width * height; i++) { - map->layout->blockdata.append(block); + layout->blockdata.append(block); } - map->layout->lastCommitBlocks.blocks = map->layout->blockdata; - map->layout->lastCommitBlocks.mapDimensions = QSize(width, height); + layout->lastCommitBlocks.blocks = layout->blockdata; + layout->lastCommitBlocks.layoutDimensions = QSize(width, height); } -bool Project::loadLayoutBorder(MapLayout *layout) { +bool Project::loadLayoutBorder(Layout *layout) { QString path = QString("%1/%2").arg(root).arg(layout->border_path); layout->border = readBlockdata(path); layout->lastCommitBlocks.border = layout->border; @@ -1166,36 +1250,36 @@ bool Project::loadLayoutBorder(MapLayout *layout) { return true; } -void Project::setNewMapBorder(Map *map) { - map->layout->border.clear(); - int width = map->getBorderWidth(); - int height = map->getBorderHeight(); +void Project::setNewLayoutBorder(Layout *layout) { + layout->border.clear(); + int width = layout->getBorderWidth(); + int height = layout->getBorderHeight(); if (projectConfig.newMapBorderMetatileIds.length() != width * height) { // Border size doesn't match the number of default border metatiles. // Fill the border with empty metatiles. for (int i = 0; i < width * height; i++) { - map->layout->border.append(0); + layout->border.append(0); } } else { // Fill the border with the default metatiles from the config. for (int i = 0; i < width * height; i++) { - map->layout->border.append(projectConfig.newMapBorderMetatileIds.at(i)); + layout->border.append(projectConfig.newMapBorderMetatileIds.at(i)); } } - map->layout->lastCommitBlocks.border = map->layout->border; - map->layout->lastCommitBlocks.borderDimensions = QSize(width, height); + layout->lastCommitBlocks.border = layout->border; + layout->lastCommitBlocks.borderDimensions = QSize(width, height); } -void Project::saveLayoutBorder(Map *map) { - QString path = QString("%1/%2").arg(root).arg(map->layout->border_path); - writeBlockdata(path, map->layout->border); +void Project::saveLayoutBorder(Layout *layout) { + QString path = QString("%1/%2").arg(root).arg(layout->border_path); + writeBlockdata(path, layout->border); } -void Project::saveLayoutBlockdata(Map* map) { - QString path = QString("%1/%2").arg(root).arg(map->layout->blockdata_path); - writeBlockdata(path, map->layout->blockdata); +void Project::saveLayoutBlockdata(Layout *layout) { + QString path = QString("%1/%2").arg(root).arg(layout->blockdata_path); + writeBlockdata(path, layout->blockdata); } void Project::writeBlockdata(QString path, const Blockdata &blockdata) { @@ -1350,36 +1434,46 @@ void Project::saveMap(Map *map) { jsonDoc.dump(&mapFile); mapFile.close(); - saveLayoutBorder(map); - saveLayoutBlockdata(map); + saveLayout(map->layout); saveHealLocations(map); - // Update global data structures with current map data. - updateMapLayout(map); - map->isPersistedToFile = true; map->hasUnsavedDataChanges = false; map->editHistory.setClean(); } -void Project::updateMapLayout(Map* map) { - if (!mapLayoutsTableMaster.contains(map->layoutId)) { - mapLayoutsTableMaster.append(map->layoutId); +void Project::saveLayout(Layout *layout) { + // + saveLayoutBorder(layout); + saveLayoutBlockdata(layout); + + // Update global data structures with current map data. + updateLayout(layout); + + layout->editHistory.setClean(); +} + +void Project::updateLayout(Layout *layout) { + if (!mapLayoutsTableMaster.contains(layout->id)) { + mapLayoutsTableMaster.append(layout->id); } - // Deep copy - MapLayout *layout = mapLayouts.value(map->layoutId); - MapLayout *newLayout = new MapLayout(); - *newLayout = *layout; - mapLayoutsMaster.insert(map->layoutId, newLayout); + if (mapLayoutsMaster.contains(layout->id)) { + mapLayoutsMaster[layout->id]->copyFrom(layout); + } + else { + mapLayoutsMaster.insert(layout->id, layout->copy()); + } } void Project::saveAllDataStructures() { saveMapLayouts(); saveMapGroups(); + saveRegionMapSections(); saveMapConstantsHeader(); saveWildMonData(); saveConfig(); + this->hasUnsavedDataChanges = false; } void Project::saveConfig() { @@ -1513,16 +1607,16 @@ void Project::loadTilesetMetatiles(Tileset* tileset) { } metatiles.append(metatile); } - tileset->metatiles = metatiles; + tileset->setMetatiles(metatiles); } else { - tileset->metatiles.clear(); + tileset->clearMetatiles(); logError(QString("Could not open tileset metatiles file '%1'").arg(tileset->metatiles_path)); } QFile attrs_file(tileset->metatile_attrs_path); if (attrs_file.open(QIODevice::ReadOnly)) { QByteArray data = attrs_file.readAll(); - int num_metatiles = tileset->metatiles.count(); + int num_metatiles = tileset->numMetatiles(); int attrSize = projectConfig.metatileAttributesSize; int num_metatileAttrs = data.length() / attrSize; if (num_metatiles != num_metatileAttrs) { @@ -1535,7 +1629,7 @@ void Project::loadTilesetMetatiles(Tileset* tileset) { uint32_t attributes = 0; for (int j = 0; j < attrSize; j++) attributes |= static_cast(data.at(i * attrSize + j)) << (8 * j); - tileset->metatiles.at(i)->setAttributes(attributes); + tileset->metatileAt(i)->setAttributes(attributes); } } else { logError(QString("Could not open tileset metatile attributes file '%1'").arg(tileset->metatile_attrs_path)); @@ -1863,11 +1957,12 @@ Map* Project::addNewMapToGroup(QString mapName, int groupNum, Map *newMap, bool if (!existingLayout) { this->mapLayouts.insert(newMap->layoutId, newMap->layout); this->mapLayoutsTable.append(newMap->layoutId); + this->layoutIdsToNames.insert(newMap->layout->id, newMap->layout->name); if (!importedMap) { - setNewMapBlockdata(newMap); + setNewLayoutBlockdata(newMap->layout); } if (newMap->layout->border.isEmpty()) { - setNewMapBorder(newMap); + setNewLayoutBorder(newMap->layout); } } @@ -1933,8 +2028,6 @@ void Project::appendTilesetLabel(QString label, QString isSecondaryStr) { } bool Project::readTilesetLabels() { - QStringList primaryTilesets; - QStringList secondaryTilesets; this->primaryTilesetLabels.clear(); this->secondaryTilesetLabels.clear(); this->tilesetLabelsOrdered.clear(); @@ -1963,9 +2056,9 @@ bool Project::readTilesetLabels() { } else { this->usingAsmTilesets = false; const auto structs = parser.readCStructs(filename, "", Tileset::getHeaderMemberMap(this->usingAsmTilesets)); - QStringList labels = structs.keys(); + const QStringList labels = structs.keys(); // TODO: This is alphabetical, AdvanceMap import wants the vanilla order in tilesetLabelsOrdered - for (const auto tilesetLabel : labels){ + for (const auto &tilesetLabel : labels){ appendTilesetLabel(tilesetLabel, structs[tilesetLabel].value("isSecondary")); } } @@ -2159,24 +2252,99 @@ bool Project::readFieldmapMasks() { } bool Project::readRegionMapSections() { - this->mapSectionNameToValue.clear(); - this->mapSectionValueToName.clear(); - - const QStringList regexList = {QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix))}; - QString filename = projectConfig.getFilePath(ProjectFilePath::constants_region_map_sections); - fileWatcher.addPath(root + "/" + filename); - this->mapSectionNameToValue = parser.readCDefinesByRegex(filename, regexList); - if (this->mapSectionNameToValue.isEmpty()) { - logError(QString("Failed to read region map sections from %1.").arg(filename)); + this->mapSectionIdNames.clear(); + this->regionMapEntries.clear(); + this->saveEmptyMapsec = false; + const QString defaultName = getEmptyMapsecName(); + const QString requiredPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix); + + QJsonDocument doc; + const QString filepath = QString("%1/%2").arg(this->root).arg(projectConfig.getFilePath(ProjectFilePath::json_region_map_entries)); + if (!parser.tryParseJsonFile(&doc, filepath)) { + logError(QString("Failed to read region map sections from '%1'").arg(filepath)); return false; } + fileWatcher.addPath(filepath); + + QJsonArray mapSections = doc.object()["map_sections"].toArray(); + for (int i = 0; i < mapSections.size(); i++) { + QJsonObject mapSectionObj = mapSections.at(i).toObject(); + + // For each map section, "id" is the only required field. This is the field we use to display the location names in various drop-downs. + const QString idField = "id"; + if (!mapSectionObj.contains(idField)) { + logWarn(QString("Ignoring data for map section %1. Missing required field \"%2\"").arg(i).arg(idField)); + continue; + } + const QString idName = ParseUtil::jsonToQString(mapSectionObj[idField]); + if (!idName.startsWith(requiredPrefix)) { + logWarn(QString("Ignoring data for map section '%1'. IDs must start with the prefix '%2'").arg(idName).arg(requiredPrefix)); + continue; + } - for (QString defineName : this->mapSectionNameToValue.keys()) { - this->mapSectionValueToName.insert(this->mapSectionNameToValue[defineName], defineName); + this->mapSectionIdNames.append(idName); + if (idName == defaultName) { + // If the user has data for the 'empty' MAPSEC we need to know to output it later, + // because we will otherwise add a dummy entry for this value. + this->saveEmptyMapsec = true; + } + + // Map sections may have additional data indicating their position on the region map. + // If they have this data, we can add them to the region map entry list. + bool hasRegionMapData = true; + static const QSet regionMapFieldNames = { "name", "x", "y", "width", "height" }; + for (auto fieldName : regionMapFieldNames) { + if (!mapSectionObj.contains(fieldName)) { + hasRegionMapData = false; + break; + } + } + if (!hasRegionMapData) + continue; + + MapSectionEntry entry; + entry.name = ParseUtil::jsonToQString(mapSectionObj["name"]); + entry.x = ParseUtil::jsonToInt(mapSectionObj["x"]); + entry.y = ParseUtil::jsonToInt(mapSectionObj["y"]); + entry.width = ParseUtil::jsonToInt(mapSectionObj["width"]); + entry.height = ParseUtil::jsonToInt(mapSectionObj["height"]); + entry.valid = true; + this->regionMapEntries[idName] = entry; } + + // Make sure the default name is present in the list. + if (!this->mapSectionIdNames.contains(defaultName)) { + this->mapSectionIdNames.append(defaultName); + } + return true; } +QString Project::getEmptyMapsecName() { + return projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix) + projectConfig.getIdentifier(ProjectIdentifier::define_map_section_empty); +} + +// This function assumes a valid and unique name +void Project::addNewMapsec(const QString &name) { + if (!this->mapSectionIdNames.isEmpty() && this->mapSectionIdNames.last() == getEmptyMapsecName()) { + // If the default map section name (MAPSEC_NONE) is last in the list we'll keep it last in the list. + this->mapSectionIdNames.insert(this->mapSectionIdNames.length() - 1, name); + } else { + this->mapSectionIdNames.append(name); + } + this->hasUnsavedDataChanges = true; + emit mapSectionIdNamesChanged(); +} + +void Project::removeMapsec(const QString &name) { + if (!this->mapSectionIdNames.contains(name) || name == getEmptyMapsecName()) + return; + + this->mapSectionIdNames.removeOne(name); + this->hasUnsavedDataChanges = true; + emit mapSectionIdNamesChanged(); +} + // Read the constants to preserve any "unused" heal locations when writing the file later bool Project::readHealLocationConstants() { this->healLocationNameToValue.clear(); @@ -2242,7 +2410,7 @@ bool Project::readHealLocations() { // Pattern for an x, y number pair const QString coordPattern = "\\s*(?[0-9A-Fa-fx]+),\\s*(?[0-9A-Fa-fx]+)"; - for (const auto idName : constants) { + for (const auto &idName : constants) { // Create regex pattern for e.g. "SPAWN_PALLET_TOWN - 1] = " const QString initializerPattern = QString("%1\\s*-\\s*1\\s*\\]\\s*=\\s*").arg(idName); @@ -2556,7 +2724,14 @@ void Project::setEventPixmap(Event *event, bool forceLoad) { event->loadPixmap(this); } +void Project::clearEventGraphics() { + qDeleteAll(eventGraphicsMap); + eventGraphicsMap.clear(); +} + bool Project::readEventGraphics() { + clearEventGraphics(); + fileWatcher.addPaths(QStringList() << root + "/" + projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx_pointers) << root + "/" + projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx_info) << root + "/" + projectConfig.getFilePath(ProjectFilePath::data_obj_event_pic_tables) @@ -2566,8 +2741,6 @@ bool Project::readEventGraphics() { const QString pointersName = projectConfig.getIdentifier(ProjectIdentifier::symbol_obj_event_gfx_pointers); QMap pointerHash = parser.readNamedIndexCArray(pointersFilepath, pointersName); - qDeleteAll(eventGraphicsMap); - eventGraphicsMap.clear(); QStringList gfxNames = gfxDefines.keys(); // The positions of each of the required members for the gfx info struct. @@ -2586,14 +2759,13 @@ bool Project::readEventGraphics() { QMap graphicIncbins = parser.readCIncbinMulti(projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx)); for (QString gfxName : gfxNames) { - EventGraphics * eventGraphics = new EventGraphics; - QString info_label = pointerHash[gfxName].replace("&", ""); if (!gfxInfos.contains(info_label)) continue; const auto gfxInfoAttributes = gfxInfos[info_label]; + auto eventGraphics = new EventGraphics; eventGraphics->inanimate = ParseUtil::gameStringToBool(gfxInfoAttributes.value("inanimate")); QString pic_label = gfxInfoAttributes.value("images"); QString dimensions_label = gfxInfoAttributes.value("oam"); @@ -2819,11 +2991,6 @@ QString Project::getDynamicMapDefineName() { return prefix + projectConfig.getIdentifier(ProjectIdentifier::define_map_dynamic); } -void Project::setImportExportPath(QString filename) -{ - this->importExportPath = QFileInfo(filename).absolutePath(); -} - // If the provided filepath is an absolute path to an existing file, return filepath. // If not, and the provided filepath is a relative path from the project dir to an existing file, return the relative path. // Otherwise return empty string. @@ -2860,3 +3027,23 @@ void Project::applyParsedLimits() { projectConfig.collisionSheetHeight = qMin(projectConfig.collisionSheetHeight, Block::getMaxElevation() + 1); projectConfig.collisionSheetWidth = qMin(projectConfig.collisionSheetWidth, Block::getMaxCollision() + 1); } + +bool Project::hasUnsavedChanges() { + if (this->hasUnsavedDataChanges) + return true; + + // Check layouts for unsaved changes + for (auto i = this->mapLayouts.constBegin(); i != this->mapLayouts.constEnd(); i++) { + auto layout = i.value(); + if (layout && layout->hasUnsavedChanges()) + return true; + } + + // Check loaded maps for unsaved changes + for (auto i = this->mapCache.constBegin(); i != this->mapCache.constEnd(); i++) { + auto map = i.value(); + if (map && map->hasUnsavedChanges()) + return true; + } + return false; +} diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index 2911fe74c..98478f94b 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -24,7 +24,7 @@ void MainWindow::tryRedrawMapArea(bool forceRedraw) { this->editor->updateMapBorder(); this->editor->updateMapConnections(); if (this->tilesetEditor) - this->tilesetEditor->updateTilesets(this->editor->map->layout->tileset_primary_label, this->editor->map->layout->tileset_secondary_label); + this->tilesetEditor->updateTilesets(this->editor->layout->tileset_primary_label, this->editor->layout->tileset_secondary_label); if (this->editor->metatile_selector_item) this->editor->metatile_selector_item->draw(); if (this->editor->selected_border_metatiles_item) @@ -42,13 +42,13 @@ void MainWindow::tryRedrawMapArea(bool forceRedraw) { void MainWindow::tryCommitMapChanges(bool commitChanges) { if (commitChanges) { - Map *map = this->editor->map; - if (map) { - map->editHistory.push(new ScriptEditMap(map, - map->layout->lastCommitBlocks.mapDimensions, QSize(map->getWidth(), map->getHeight()), - map->layout->lastCommitBlocks.blocks, map->layout->blockdata, - map->layout->lastCommitBlocks.borderDimensions, QSize(map->getBorderWidth(), map->getBorderHeight()), - map->layout->lastCommitBlocks.border, map->layout->border + Layout *layout = this->editor->layout; + if (layout) { + layout->editHistory.push(new ScriptEditLayout(layout, + layout->lastCommitBlocks.layoutDimensions, QSize(layout->getWidth(), layout->getHeight()), + layout->lastCommitBlocks.blocks, layout->blockdata, + layout->lastCommitBlocks.borderDimensions, QSize(layout->getBorderWidth(), layout->getBorderHeight()), + layout->lastCommitBlocks.border, layout->border )); } } @@ -59,27 +59,27 @@ void MainWindow::tryCommitMapChanges(bool commitChanges) { //===================== QJSValue MainWindow::getBlock(int x, int y) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return QJSValue(); Block block; - if (!this->editor->map->getBlock(x, y, &block)) { + if (!this->editor->layout->getBlock(x, y, &block)) { return Scripting::fromBlock(Block()); } return Scripting::fromBlock(block); } void MainWindow::setBlock(int x, int y, int metatileId, int collision, int elevation, bool forceRedraw, bool commitChanges) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; - this->editor->map->setBlock(x, y, Block(metatileId, collision, elevation)); + this->editor->layout->setBlock(x, y, Block(metatileId, collision, elevation)); this->tryCommitMapChanges(commitChanges); this->tryRedrawMapArea(forceRedraw); } void MainWindow::setBlock(int x, int y, int rawValue, bool forceRedraw, bool commitChanges) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; - this->editor->map->setBlock(x, y, Block(static_cast(rawValue))); + this->editor->layout->setBlock(x, y, Block(static_cast(rawValue))); this->tryCommitMapChanges(commitChanges); this->tryRedrawMapArea(forceRedraw); } @@ -93,73 +93,73 @@ void MainWindow::setBlocksFromSelection(int x, int y, bool forceRedraw, bool com } int MainWindow::getMetatileId(int x, int y) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return 0; Block block; - if (!this->editor->map->getBlock(x, y, &block)) { + if (!this->editor->layout->getBlock(x, y, &block)) { return 0; } return block.metatileId(); } void MainWindow::setMetatileId(int x, int y, int metatileId, bool forceRedraw, bool commitChanges) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; Block block; - if (!this->editor->map->getBlock(x, y, &block)) { + if (!this->editor->layout->getBlock(x, y, &block)) { return; } - this->editor->map->setBlock(x, y, Block(metatileId, block.collision(), block.elevation())); + this->editor->layout->setBlock(x, y, Block(metatileId, block.collision(), block.elevation())); this->tryCommitMapChanges(commitChanges); this->tryRedrawMapArea(forceRedraw); } int MainWindow::getCollision(int x, int y) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return 0; Block block; - if (!this->editor->map->getBlock(x, y, &block)) { + if (!this->editor->layout->getBlock(x, y, &block)) { return 0; } return block.collision(); } void MainWindow::setCollision(int x, int y, int collision, bool forceRedraw, bool commitChanges) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; Block block; - if (!this->editor->map->getBlock(x, y, &block)) { + if (!this->editor->layout->getBlock(x, y, &block)) { return; } - this->editor->map->setBlock(x, y, Block(block.metatileId(), collision, block.elevation())); + this->editor->layout->setBlock(x, y, Block(block.metatileId(), collision, block.elevation())); this->tryCommitMapChanges(commitChanges); this->tryRedrawMapArea(forceRedraw); } int MainWindow::getElevation(int x, int y) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return 0; Block block; - if (!this->editor->map->getBlock(x, y, &block)) { + if (!this->editor->layout->getBlock(x, y, &block)) { return 0; } return block.elevation(); } void MainWindow::setElevation(int x, int y, int elevation, bool forceRedraw, bool commitChanges) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; Block block; - if (!this->editor->map->getBlock(x, y, &block)) { + if (!this->editor->layout->getBlock(x, y, &block)) { return; } - this->editor->map->setBlock(x, y, Block(block.metatileId(), block.collision(), elevation)); + this->editor->layout->setBlock(x, y, Block(block.metatileId(), block.collision(), elevation)); this->tryCommitMapChanges(commitChanges); this->tryRedrawMapArea(forceRedraw); } void MainWindow::bucketFill(int x, int y, int metatileId, bool forceRedraw, bool commitChanges) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; this->editor->map_item->floodFill(x, y, metatileId, true); this->tryCommitMapChanges(commitChanges); @@ -167,7 +167,7 @@ void MainWindow::bucketFill(int x, int y, int metatileId, bool forceRedraw, bool } void MainWindow::bucketFillFromSelection(int x, int y, bool forceRedraw, bool commitChanges) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; this->editor->map_item->floodFill(x, y, true); this->tryCommitMapChanges(commitChanges); @@ -175,7 +175,7 @@ void MainWindow::bucketFillFromSelection(int x, int y, bool forceRedraw, bool co } void MainWindow::magicFill(int x, int y, int metatileId, bool forceRedraw, bool commitChanges) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; this->editor->map_item->magicFill(x, y, metatileId, true); this->tryCommitMapChanges(commitChanges); @@ -183,7 +183,7 @@ void MainWindow::magicFill(int x, int y, int metatileId, bool forceRedraw, bool } void MainWindow::magicFillFromSelection(int x, int y, bool forceRedraw, bool commitChanges) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; this->editor->map_item->magicFill(x, y, true); this->tryCommitMapChanges(commitChanges); @@ -191,7 +191,7 @@ void MainWindow::magicFillFromSelection(int x, int y, bool forceRedraw, bool com } void MainWindow::shift(int xDelta, int yDelta, bool forceRedraw, bool commitChanges) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; this->editor->map_item->shift(xDelta, yDelta, true); this->tryCommitMapChanges(commitChanges); @@ -207,51 +207,51 @@ void MainWindow::commit() { } QJSValue MainWindow::getDimensions() { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return QJSValue(); - return Scripting::dimensions(this->editor->map->getWidth(), this->editor->map->getHeight()); + return Scripting::dimensions(this->editor->layout->getWidth(), this->editor->layout->getHeight()); } int MainWindow::getWidth() { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return 0; - return this->editor->map->getWidth(); + return this->editor->layout->getWidth(); } int MainWindow::getHeight() { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return 0; - return this->editor->map->getHeight(); + return this->editor->layout->getHeight(); } void MainWindow::setDimensions(int width, int height) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; if (!Project::mapDimensionsValid(width, height)) return; - this->editor->map->setDimensions(width, height); + this->editor->layout->setDimensions(width, height); this->tryCommitMapChanges(true); - this->onMapNeedsRedrawing(); + this->redrawMapScene(); } void MainWindow::setWidth(int width) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; - if (!Project::mapDimensionsValid(width, this->editor->map->getHeight())) + if (!Project::mapDimensionsValid(width, this->editor->layout->getHeight())) return; - this->editor->map->setDimensions(width, this->editor->map->getHeight()); + this->editor->layout->setDimensions(width, this->editor->layout->getHeight()); this->tryCommitMapChanges(true); - this->onMapNeedsRedrawing(); + this->redrawMapScene(); } void MainWindow::setHeight(int height) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; - if (!Project::mapDimensionsValid(this->editor->map->getWidth(), height)) + if (!Project::mapDimensionsValid(this->editor->layout->getWidth(), height)) return; - this->editor->map->setDimensions(this->editor->map->getWidth(), height); + this->editor->layout->setDimensions(this->editor->layout->getWidth(), height); this->tryCommitMapChanges(true); - this->onMapNeedsRedrawing(); + this->redrawMapScene(); } //===================== @@ -259,69 +259,69 @@ void MainWindow::setHeight(int height) { //===================== int MainWindow::getBorderMetatileId(int x, int y) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return 0; - if (!this->editor->map->isWithinBorderBounds(x, y)) + if (!this->editor->layout->isWithinBorderBounds(x, y)) return 0; - return this->editor->map->getBorderMetatileId(x, y); + return this->editor->layout->getBorderMetatileId(x, y); } void MainWindow::setBorderMetatileId(int x, int y, int metatileId, bool forceRedraw, bool commitChanges) { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return; - if (!this->editor->map->isWithinBorderBounds(x, y)) + if (!this->editor->layout->isWithinBorderBounds(x, y)) return; - this->editor->map->setBorderMetatileId(x, y, metatileId); + this->editor->layout->setBorderMetatileId(x, y, metatileId); this->tryCommitMapChanges(commitChanges); this->tryRedrawMapArea(forceRedraw); } QJSValue MainWindow::getBorderDimensions() { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return QJSValue(); - return Scripting::dimensions(this->editor->map->getBorderWidth(), this->editor->map->getBorderHeight()); + return Scripting::dimensions(this->editor->layout->getBorderWidth(), this->editor->layout->getBorderHeight()); } int MainWindow::getBorderWidth() { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return 0; - return this->editor->map->getBorderWidth(); + return this->editor->layout->getBorderWidth(); } int MainWindow::getBorderHeight() { - if (!this->editor || !this->editor->map) + if (!this->editor || !this->editor->layout) return 0; - return this->editor->map->getBorderHeight(); + return this->editor->layout->getBorderHeight(); } void MainWindow::setBorderDimensions(int width, int height) { - if (!this->editor || !this->editor->map || !projectConfig.useCustomBorderSize) + if (!this->editor || !this->editor->layout || !projectConfig.useCustomBorderSize) return; if (width < 1 || height < 1 || width > MAX_BORDER_WIDTH || height > MAX_BORDER_HEIGHT) return; - this->editor->map->setBorderDimensions(width, height); + this->editor->layout->setBorderDimensions(width, height); this->tryCommitMapChanges(true); - this->onMapNeedsRedrawing(); + this->redrawMapScene(); } void MainWindow::setBorderWidth(int width) { - if (!this->editor || !this->editor->map || !projectConfig.useCustomBorderSize) + if (!this->editor || !this->editor->layout || !projectConfig.useCustomBorderSize) return; if (width < 1 || width > MAX_BORDER_WIDTH) return; - this->editor->map->setBorderDimensions(width, this->editor->map->getBorderHeight()); + this->editor->layout->setBorderDimensions(width, this->editor->layout->getBorderHeight()); this->tryCommitMapChanges(true); - this->onMapNeedsRedrawing(); + this->redrawMapScene(); } void MainWindow::setBorderHeight(int height) { - if (!this->editor || !this->editor->map || !projectConfig.useCustomBorderSize) + if (!this->editor || !this->editor->layout || !projectConfig.useCustomBorderSize) return; if (height < 1 || height > MAX_BORDER_HEIGHT) return; - this->editor->map->setBorderDimensions(this->editor->map->getBorderWidth(), height); + this->editor->layout->setBorderDimensions(this->editor->layout->getBorderWidth(), height); this->tryCommitMapChanges(true); - this->onMapNeedsRedrawing(); + this->redrawMapScene(); } //====================== @@ -330,7 +330,7 @@ void MainWindow::setBorderHeight(int height) { void MainWindow::refreshAfterPaletteChange(Tileset *tileset) { if (this->tilesetEditor) { - this->tilesetEditor->updateTilesets(this->editor->map->layout->tileset_primary_label, this->editor->map->layout->tileset_secondary_label); + this->tilesetEditor->updateTilesets(this->editor->layout->tileset_primary_label, this->editor->layout->tileset_secondary_label); } this->editor->metatile_selector_item->draw(); this->editor->selected_border_metatiles_item->draw(); @@ -341,7 +341,7 @@ void MainWindow::refreshAfterPaletteChange(Tileset *tileset) { } void MainWindow::setTilesetPalette(Tileset *tileset, int paletteIndex, QList> colors) { - if (!this->editor || !this->editor->map || !this->editor->map->layout) + if (!this->editor || !this->editor->layout) return; if (paletteIndex >= tileset->palettes.size()) return; @@ -357,42 +357,42 @@ void MainWindow::setTilesetPalette(Tileset *tileset, int paletteIndex, QList> colors, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_primary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return; - this->setTilesetPalette(this->editor->map->layout->tileset_primary, paletteIndex, colors); + this->setTilesetPalette(this->editor->layout->tileset_primary, paletteIndex, colors); if (forceRedraw) { - this->refreshAfterPaletteChange(this->editor->map->layout->tileset_primary); + this->refreshAfterPaletteChange(this->editor->layout->tileset_primary); } } void MainWindow::setPrimaryTilesetPalettes(QList>> palettes, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_primary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return; for (int i = 0; i < palettes.size(); i++) { - this->setTilesetPalette(this->editor->map->layout->tileset_primary, i, palettes[i]); + this->setTilesetPalette(this->editor->layout->tileset_primary, i, palettes[i]); } if (forceRedraw) { - this->refreshAfterPaletteChange(this->editor->map->layout->tileset_primary); + this->refreshAfterPaletteChange(this->editor->layout->tileset_primary); } } void MainWindow::setSecondaryTilesetPalette(int paletteIndex, QList> colors, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return; - this->setTilesetPalette(this->editor->map->layout->tileset_secondary, paletteIndex, colors); + this->setTilesetPalette(this->editor->layout->tileset_secondary, paletteIndex, colors); if (forceRedraw) { - this->refreshAfterPaletteChange(this->editor->map->layout->tileset_secondary); + this->refreshAfterPaletteChange(this->editor->layout->tileset_secondary); } } void MainWindow::setSecondaryTilesetPalettes(QList>> palettes, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return; for (int i = 0; i < palettes.size(); i++) { - this->setTilesetPalette(this->editor->map->layout->tileset_secondary, i, palettes[i]); + this->setTilesetPalette(this->editor->layout->tileset_secondary, i, palettes[i]); } if (forceRedraw) { - this->refreshAfterPaletteChange(this->editor->map->layout->tileset_secondary); + this->refreshAfterPaletteChange(this->editor->layout->tileset_secondary); } } @@ -420,27 +420,27 @@ QJSValue MainWindow::getTilesetPalettes(const QList> &palettes) { } QJSValue MainWindow::getPrimaryTilesetPalette(int paletteIndex) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_primary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return QJSValue(); - return this->getTilesetPalette(this->editor->map->layout->tileset_primary->palettes, paletteIndex); + return this->getTilesetPalette(this->editor->layout->tileset_primary->palettes, paletteIndex); } QJSValue MainWindow::getPrimaryTilesetPalettes() { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_primary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return QJSValue(); - return this->getTilesetPalettes(this->editor->map->layout->tileset_primary->palettes); + return this->getTilesetPalettes(this->editor->layout->tileset_primary->palettes); } QJSValue MainWindow::getSecondaryTilesetPalette(int paletteIndex) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return QJSValue(); - return this->getTilesetPalette(this->editor->map->layout->tileset_secondary->palettes, paletteIndex); + return this->getTilesetPalette(this->editor->layout->tileset_secondary->palettes, paletteIndex); } QJSValue MainWindow::getSecondaryTilesetPalettes() { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return QJSValue(); - return this->getTilesetPalettes(this->editor->map->layout->tileset_secondary->palettes); + return this->getTilesetPalettes(this->editor->layout->tileset_secondary->palettes); } void MainWindow::refreshAfterPalettePreviewChange() { @@ -452,7 +452,7 @@ void MainWindow::refreshAfterPalettePreviewChange() { } void MainWindow::setTilesetPalettePreview(Tileset *tileset, int paletteIndex, QList> colors) { - if (!this->editor || !this->editor->map || !this->editor->map->layout) + if (!this->editor || !this->editor->layout) return; if (paletteIndex >= tileset->palettePreviews.size()) return; @@ -467,19 +467,19 @@ void MainWindow::setTilesetPalettePreview(Tileset *tileset, int paletteIndex, QL } void MainWindow::setPrimaryTilesetPalettePreview(int paletteIndex, QList> colors, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_primary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return; - this->setTilesetPalettePreview(this->editor->map->layout->tileset_primary, paletteIndex, colors); + this->setTilesetPalettePreview(this->editor->layout->tileset_primary, paletteIndex, colors); if (forceRedraw) { this->refreshAfterPalettePreviewChange(); } } void MainWindow::setPrimaryTilesetPalettesPreview(QList>> palettes, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_primary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return; for (int i = 0; i < palettes.size(); i++) { - this->setTilesetPalettePreview(this->editor->map->layout->tileset_primary, i, palettes[i]); + this->setTilesetPalettePreview(this->editor->layout->tileset_primary, i, palettes[i]); } if (forceRedraw) { this->refreshAfterPalettePreviewChange(); @@ -487,19 +487,19 @@ void MainWindow::setPrimaryTilesetPalettesPreview(QList>> palet } void MainWindow::setSecondaryTilesetPalettePreview(int paletteIndex, QList> colors, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return; - this->setTilesetPalettePreview(this->editor->map->layout->tileset_secondary, paletteIndex, colors); + this->setTilesetPalettePreview(this->editor->layout->tileset_secondary, paletteIndex, colors); if (forceRedraw) { this->refreshAfterPalettePreviewChange(); } } void MainWindow::setSecondaryTilesetPalettesPreview(QList>> palettes, bool forceRedraw) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return; for (int i = 0; i < palettes.size(); i++) { - this->setTilesetPalettePreview(this->editor->map->layout->tileset_secondary, i, palettes[i]); + this->setTilesetPalettePreview(this->editor->layout->tileset_secondary, i, palettes[i]); } if (forceRedraw) { this->refreshAfterPalettePreviewChange(); @@ -507,63 +507,63 @@ void MainWindow::setSecondaryTilesetPalettesPreview(QList>> pal } QJSValue MainWindow::getPrimaryTilesetPalettePreview(int paletteIndex) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_primary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return QJSValue(); - return this->getTilesetPalette(this->editor->map->layout->tileset_primary->palettePreviews, paletteIndex); + return this->getTilesetPalette(this->editor->layout->tileset_primary->palettePreviews, paletteIndex); } QJSValue MainWindow::getPrimaryTilesetPalettesPreview() { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_primary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return QJSValue(); - return this->getTilesetPalettes(this->editor->map->layout->tileset_primary->palettePreviews); + return this->getTilesetPalettes(this->editor->layout->tileset_primary->palettePreviews); } QJSValue MainWindow::getSecondaryTilesetPalettePreview(int paletteIndex) { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return QJSValue(); - return this->getTilesetPalette(this->editor->map->layout->tileset_secondary->palettePreviews, paletteIndex); + return this->getTilesetPalette(this->editor->layout->tileset_secondary->palettePreviews, paletteIndex); } QJSValue MainWindow::getSecondaryTilesetPalettesPreview() { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return QJSValue(); - return this->getTilesetPalettes(this->editor->map->layout->tileset_secondary->palettePreviews); + return this->getTilesetPalettes(this->editor->layout->tileset_secondary->palettePreviews); } int MainWindow::getNumPrimaryTilesetMetatiles() { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_primary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return 0; - return this->editor->map->layout->tileset_primary->metatiles.length(); + return this->editor->layout->tileset_primary->numMetatiles(); } int MainWindow::getNumSecondaryTilesetMetatiles() { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return 0; - return this->editor->map->layout->tileset_secondary->metatiles.length(); + return this->editor->layout->tileset_secondary->numMetatiles(); } int MainWindow::getNumPrimaryTilesetTiles() { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_primary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return 0; - return this->editor->map->layout->tileset_primary->tiles.length(); + return this->editor->layout->tileset_primary->tiles.length(); } int MainWindow::getNumSecondaryTilesetTiles() { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return 0; - return this->editor->map->layout->tileset_secondary->tiles.length(); + return this->editor->layout->tileset_secondary->tiles.length(); } QString MainWindow::getPrimaryTileset() { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_primary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary) return QString(); - return this->editor->map->layout->tileset_primary->name; + return this->editor->layout->tileset_primary->name; } QString MainWindow::getSecondaryTileset() { - if (!this->editor || !this->editor->map || !this->editor->map->layout || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary) return QString(); - return this->editor->map->layout->tileset_secondary->name; + return this->editor->layout->tileset_secondary->name; } void MainWindow::setPrimaryTileset(QString tileset) { @@ -575,13 +575,13 @@ void MainWindow::setSecondaryTileset(QString tileset) { } void MainWindow::saveMetatilesByMetatileId(int metatileId) { - Tileset * tileset = Tileset::getMetatileTileset(metatileId, this->editor->map->layout->tileset_primary, this->editor->map->layout->tileset_secondary); + Tileset * tileset = Tileset::getMetatileTileset(metatileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); if (this->editor->project && tileset) this->editor->project->saveTilesetMetatiles(tileset); } void MainWindow::saveMetatileAttributesByMetatileId(int metatileId) { - Tileset * tileset = Tileset::getMetatileTileset(metatileId, this->editor->map->layout->tileset_primary, this->editor->map->layout->tileset_secondary); + Tileset * tileset = Tileset::getMetatileTileset(metatileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); if (this->editor->project && tileset) this->editor->project->saveTilesetMetatileAttributes(tileset); @@ -597,19 +597,19 @@ void MainWindow::saveMetatileAttributesByMetatileId(int metatileId) { } Metatile * MainWindow::getMetatile(int metatileId) { - if (!this->editor || !this->editor->map || !this->editor->map->layout) + if (!this->editor || !this->editor->layout) return nullptr; - return Tileset::getMetatile(metatileId, this->editor->map->layout->tileset_primary, this->editor->map->layout->tileset_secondary); + return Tileset::getMetatile(metatileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); } QString MainWindow::getMetatileLabel(int metatileId) { - if (!this->editor || !this->editor->map || !this->editor->map->layout) + if (!this->editor || !this->editor->layout) return QString(); - return Tileset::getMetatileLabel(metatileId, this->editor->map->layout->tileset_primary, this->editor->map->layout->tileset_secondary); + return Tileset::getMetatileLabel(metatileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); } void MainWindow::setMetatileLabel(int metatileId, QString label) { - if (!this->editor || !this->editor->map || !this->editor->map->layout) + if (!this->editor || !this->editor->layout) return; // If the Tileset Editor is opened on this metatile we need to update the text box @@ -618,13 +618,13 @@ void MainWindow::setMetatileLabel(int metatileId, QString label) { return; } - if (!Tileset::setMetatileLabel(metatileId, label, this->editor->map->layout->tileset_primary, this->editor->map->layout->tileset_secondary)) { + if (!Tileset::setMetatileLabel(metatileId, label, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary)) { logError("Failed to set metatile label. Must be a valid metatile id and a label containing only letters, numbers, and underscores."); return; } if (this->editor->project) - this->editor->project->saveTilesetMetatileLabels(this->editor->map->layout->tileset_primary, this->editor->map->layout->tileset_secondary); + this->editor->project->saveTilesetMetatileLabels(this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); } int MainWindow::getMetatileLayerType(int metatileId) { @@ -794,9 +794,9 @@ void MainWindow::setMetatileTile(int metatileId, int tileIndex, QJSValue tileObj } QJSValue MainWindow::getTilePixels(int tileId) { - if (tileId < 0 || !this->editor || !this->editor->project || !this->editor->map || !this->editor->map->layout) + if (tileId < 0 || !this->editor || !this->editor->project || !this->editor->map || !this->editor->layout) return QJSValue(); - QImage tileImage = getTileImage(tileId, this->editor->map->layout->tileset_primary, this->editor->map->layout->tileset_secondary); + QImage tileImage = getTileImage(tileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); if (tileImage.isNull() || tileImage.sizeInBytes() < 64) return QJSValue(); const uchar * pixels = tileImage.constBits(); @@ -836,7 +836,7 @@ QString MainWindow::getLocation() { void MainWindow::setLocation(QString location) { if (!this->ui || !this->editor || !this->editor->project) return; - if (!this->editor->project->mapSectionNameToValue.contains(location)) { + if (!this->editor->project->mapSectionIdNames.contains(location)) { logError(QString("Unknown location '%1'").arg(location)); return; } diff --git a/src/scriptapi/apioverlay.cpp b/src/scriptapi/apioverlay.cpp index bee473ff4..b12f5f095 100644 --- a/src/scriptapi/apioverlay.cpp +++ b/src/scriptapi/apioverlay.cpp @@ -254,23 +254,21 @@ void MapView::addImage(int x, int y, QString filepath, int layer, bool useCache) } void MapView::createImage(int x, int y, QString filepath, int width, int height, int xOffset, int yOffset, qreal hScale, qreal vScale, int paletteId, bool setTransparency, int layer, bool useCache) { - if (!this->editor || !this->editor->map || !this->editor->map->layout - || !this->editor->map->layout->tileset_primary || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary || !this->editor->layout->tileset_secondary) return; QList palette; if (paletteId != -1) - palette = Tileset::getPalette(paletteId, this->editor->map->layout->tileset_primary, this->editor->map->layout->tileset_secondary); + palette = Tileset::getPalette(paletteId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); if (this->getOverlay(layer)->addImage(x, y, filepath, useCache, width, height, xOffset, yOffset, hScale, vScale, palette, setTransparency)) this->scene()->update(); } void MapView::addTileImage(int x, int y, int tileId, bool xflip, bool yflip, int paletteId, bool setTransparency, int layer) { - if (!this->editor || !this->editor->map || !this->editor->map->layout - || !this->editor->map->layout->tileset_primary || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary || !this->editor->layout->tileset_secondary) return; QImage image = getPalettedTileImage(tileId, - this->editor->map->layout->tileset_primary, - this->editor->map->layout->tileset_secondary, + this->editor->layout->tileset_primary, + this->editor->layout->tileset_secondary, paletteId) .mirrored(xflip, yflip); if (setTransparency) @@ -285,14 +283,13 @@ void MapView::addTileImage(int x, int y, QJSValue tileObj, bool setTransparency, } void MapView::addMetatileImage(int x, int y, int metatileId, bool setTransparency, int layer) { - if (!this->editor || !this->editor->map || !this->editor->map->layout - || !this->editor->map->layout->tileset_primary || !this->editor->map->layout->tileset_secondary) + if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary || !this->editor->layout->tileset_secondary) return; QImage image = getMetatileImage(static_cast(metatileId), - this->editor->map->layout->tileset_primary, - this->editor->map->layout->tileset_secondary, - this->editor->map->metatileLayerOrder, - this->editor->map->metatileLayerOpacity); + this->editor->layout->tileset_primary, + this->editor->layout->tileset_secondary, + this->editor->layout->metatileLayerOrder, + this->editor->layout->metatileLayerOpacity); if (setTransparency) image.setColor(0, qRgba(0, 0, 0, 0)); if (this->getOverlay(layer)->addImage(x, y, image)) diff --git a/src/scriptapi/apiutility.cpp b/src/scriptapi/apiutility.cpp index a4aeae966..e5cebc541 100644 --- a/src/scriptapi/apiutility.cpp +++ b/src/scriptapi/apiutility.cpp @@ -201,13 +201,13 @@ QList ScriptUtility::getCustomScripts() { } QList ScriptUtility::getMetatileLayerOrder() { - if (!window || !window->editor || !window->editor->map) + if (!window || !window->editor || !window->editor->layout) return QList(); - return window->editor->map->metatileLayerOrder; + return window->editor->layout->metatileLayerOrder; } void ScriptUtility::setMetatileLayerOrder(QList order) { - if (!window || !window->editor || !window->editor->map) + if (!window || !window->editor || !window->editor->layout) return; const int numLayers = 3; @@ -226,20 +226,20 @@ void ScriptUtility::setMetatileLayerOrder(QList order) { } if (invalid) return; - window->editor->map->metatileLayerOrder = order; + window->editor->layout->metatileLayerOrder = order; window->refreshAfterPalettePreviewChange(); } QList ScriptUtility::getMetatileLayerOpacity() { - if (!window || !window->editor || !window->editor->map) + if (!window || !window->editor || !window->editor->layout) return QList(); - return window->editor->map->metatileLayerOpacity; + return window->editor->layout->metatileLayerOpacity; } void ScriptUtility::setMetatileLayerOpacity(QList order) { - if (!window || !window->editor || !window->editor->map) + if (!window || !window->editor || !window->editor->layout) return; - window->editor->map->metatileLayerOpacity = order; + window->editor->layout->metatileLayerOpacity = order; window->refreshAfterPalettePreviewChange(); } @@ -282,7 +282,7 @@ QList ScriptUtility::getSongNames() { QList ScriptUtility::getLocationNames() { if (!window || !window->editor || !window->editor->project) return QList(); - return window->editor->project->mapSectionNameToValue.keys(); + return window->editor->project->mapSectionIdNames; } QList ScriptUtility::getWeatherNames() { diff --git a/src/scriptapi/scripting.cpp b/src/scriptapi/scripting.cpp index 71fbabca7..3eb9afd64 100644 --- a/src/scriptapi/scripting.cpp +++ b/src/scriptapi/scripting.cpp @@ -29,8 +29,6 @@ void Scripting::stop() { } void Scripting::init(MainWindow *mainWindow) { - if (mainWindow->ui->graphicsView_Map) - mainWindow->ui->graphicsView_Map->clearOverlayMap(); Scripting::stop(); instance = new Scripting(mainWindow); } @@ -50,6 +48,7 @@ Scripting::Scripting(MainWindow *mainWindow) { } Scripting::~Scripting() { + if (mainWindow) mainWindow->clearOverlay(); this->engine->setInterrupted(true); qDeleteAll(this->imageCache); delete this->engine; diff --git a/src/ui/bordermetatilespixmapitem.cpp b/src/ui/bordermetatilespixmapitem.cpp index 8f8bc1348..6c5425586 100644 --- a/src/ui/bordermetatilespixmapitem.cpp +++ b/src/ui/bordermetatilespixmapitem.cpp @@ -7,30 +7,30 @@ void BorderMetatilesPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { MetatileSelection selection = this->metatileSelector->getMetatileSelection(); QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - int width = map->getBorderWidth(); - int height = map->getBorderHeight(); + int width = layout->getBorderWidth(); + int height = layout->getBorderHeight(); - Blockdata oldBorder = map->layout->border; + Blockdata oldBorder = layout->border; for (int i = 0; i < selection.dimensions.x() && (i + pos.x()) < width; i++) { for (int j = 0; j < selection.dimensions.y() && (j + pos.y()) < height; j++) { MetatileSelectionItem item = selection.metatileItems.at(j * selection.dimensions.x() + i); - map->setBorderMetatileId(pos.x() + i, pos.y() + j, item.metatileId, true); + layout->setBorderMetatileId(pos.x() + i, pos.y() + j, item.metatileId, true); } } - if (map->layout->border != oldBorder) { - map->editHistory.push(new PaintBorder(map, oldBorder, map->layout->border, 0)); + if (layout->border != oldBorder) { + layout->editHistory.push(new PaintBorder(layout, oldBorder, layout->border, 0)); } emit borderMetatilesChanged(); } void BorderMetatilesPixmapItem::draw() { - map->setBorderItem(this); + layout->setBorderItem(this); - int width = map->getBorderWidth(); - int height = map->getBorderHeight(); + int width = layout->getBorderWidth(); + int height = layout->getBorderHeight(); QImage image(16 * width, 16 * height, QImage::Format_RGBA8888); QPainter painter(&image); @@ -39,11 +39,11 @@ void BorderMetatilesPixmapItem::draw() { int x = i * 16; int y = j * 16; QImage metatile_image = getMetatileImage( - map->getBorderMetatileId(i, j), - map->layout->tileset_primary, - map->layout->tileset_secondary, - map->metatileLayerOrder, - map->metatileLayerOpacity); + layout->getBorderMetatileId(i, j), + layout->tileset_primary, + layout->tileset_secondary, + layout->metatileLayerOrder, + layout->metatileLayerOpacity); QPoint metatile_origin = QPoint(x, y); painter.drawImage(metatile_origin, metatile_image); } @@ -57,7 +57,7 @@ void BorderMetatilesPixmapItem::draw() { void BorderMetatilesPixmapItem::hoverUpdate(const QPointF &pixmapPos) { QPoint pos = Metatile::coordFromPixmapCoord(pixmapPos); - uint16_t metatileId = this->map->getBorderMetatileId(pos.x(), pos.y()); + uint16_t metatileId = this->layout->getBorderMetatileId(pos.x(), pos.y()); emit this->hoveredBorderMetatileSelectionChanged(metatileId); } diff --git a/src/ui/collisionpixmapitem.cpp b/src/ui/collisionpixmapitem.cpp index 08829fd0f..e0587b081 100644 --- a/src/ui/collisionpixmapitem.cpp +++ b/src/ui/collisionpixmapitem.cpp @@ -8,7 +8,7 @@ void CollisionPixmapItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { this->previousPos = pos; emit this->hoveredMapMovementPermissionChanged(pos.x(), pos.y()); } - if (this->settings->betterCursors && this->paintingMode == MapPixmapItem::PaintMode::Metatiles) { + if (this->settings->betterCursors && this->getEditsEnabled()) { setCursor(this->settings->mapCursor); } } @@ -21,7 +21,7 @@ void CollisionPixmapItem::hoverEnterEvent(QGraphicsSceneHoverEvent * event) { void CollisionPixmapItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) { emit this->hoveredMapMovementPermissionCleared(); - if (this->settings->betterCursors && this->paintingMode == MapPixmapItem::PaintMode::Metatiles){ + if (this->settings->betterCursors && this->getEditsEnabled()){ unsetCursor(); } this->has_mouse = false; @@ -49,9 +49,9 @@ void CollisionPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { } void CollisionPixmapItem::draw(bool ignoreCache) { - if (map) { - map->setCollisionItem(this); - setPixmap(map->renderCollision(ignoreCache)); + if (this->layout) { + this->layout->setCollisionItem(this); + setPixmap(this->layout->renderCollision(ignoreCache)); setOpacity(*this->opacity); } } @@ -59,8 +59,8 @@ void CollisionPixmapItem::draw(bool ignoreCache) { void CollisionPixmapItem::paint(QGraphicsSceneMouseEvent *event) { if (event->type() == QEvent::GraphicsSceneMouseRelease) { actionId_++; - } else if (map) { - Blockdata oldCollision = map->layout->blockdata; + } else if (this->layout) { + Blockdata oldCollision = this->layout->blockdata; QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); @@ -70,18 +70,18 @@ void CollisionPixmapItem::paint(QGraphicsSceneMouseEvent *event) { pos = this->adjustCoords(pos); } else { this->prevStraightPathState = false; - this->lockedAxis = MapPixmapItem::Axis::None; + this->lockedAxis = LayoutPixmapItem::Axis::None; } Block block; - if (map->getBlock(pos.x(), pos.y(), &block)) { + if (this->layout->getBlock(pos.x(), pos.y(), &block)) { block.setCollision(this->selectedCollision->value()); block.setElevation(this->selectedElevation->value()); - map->setBlock(pos.x(), pos.y(), block, true); + this->layout->setBlock(pos.x(), pos.y(), block, true); } - if (map->layout->blockdata != oldCollision) { - map->editHistory.push(new PaintCollision(map, oldCollision, map->layout->blockdata, actionId_)); + if (this->layout->blockdata != oldCollision) { + this->layout->editHistory.push(new PaintCollision(this->layout, oldCollision, this->layout->blockdata, actionId_)); } } } @@ -89,16 +89,16 @@ void CollisionPixmapItem::paint(QGraphicsSceneMouseEvent *event) { void CollisionPixmapItem::floodFill(QGraphicsSceneMouseEvent *event) { if (event->type() == QEvent::GraphicsSceneMouseRelease) { this->actionId_++; - } else if (map) { - Blockdata oldCollision = map->layout->blockdata; + } else if (this->layout) { + Blockdata oldCollision = this->layout->blockdata; QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); uint16_t collision = this->selectedCollision->value(); uint16_t elevation = this->selectedElevation->value(); - map->floodFillCollisionElevation(pos.x(), pos.y(), collision, elevation); + this->layout->floodFillCollisionElevation(pos.x(), pos.y(), collision, elevation); - if (map->layout->blockdata != oldCollision) { - map->editHistory.push(new BucketFillCollision(map, oldCollision, map->layout->blockdata)); + if (this->layout->blockdata != oldCollision) { + this->layout->editHistory.push(new BucketFillCollision(this->layout, oldCollision, this->layout->blockdata)); } } } @@ -106,15 +106,15 @@ void CollisionPixmapItem::floodFill(QGraphicsSceneMouseEvent *event) { void CollisionPixmapItem::magicFill(QGraphicsSceneMouseEvent *event) { if (event->type() == QEvent::GraphicsSceneMouseRelease) { this->actionId_++; - } else if (map) { - Blockdata oldCollision = map->layout->blockdata; + } else if (this->layout) { + Blockdata oldCollision = this->layout->blockdata; QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); uint16_t collision = this->selectedCollision->value(); uint16_t elevation = this->selectedElevation->value(); - map->magicFillCollisionElevation(pos.x(), pos.y(), collision, elevation); + this->layout->magicFillCollisionElevation(pos.x(), pos.y(), collision, elevation); - if (map->layout->blockdata != oldCollision) { - map->editHistory.push(new MagicFillCollision(map, oldCollision, map->layout->blockdata)); + if (this->layout->blockdata != oldCollision) { + this->layout->editHistory.push(new MagicFillCollision(this->layout, oldCollision, this->layout->blockdata)); } } } @@ -129,15 +129,15 @@ void CollisionPixmapItem::updateMovementPermissionSelection(QGraphicsSceneMouseE // Snap point to within map bounds. if (pos.x() < 0) pos.setX(0); - if (pos.x() >= map->getWidth()) pos.setX(map->getWidth() - 1); + if (pos.x() >= this->layout->getWidth()) pos.setX(this->layout->getWidth() - 1); if (pos.y() < 0) pos.setY(0); - if (pos.y() >= map->getHeight()) pos.setY(map->getHeight() - 1); + if (pos.y() >= this->layout->getHeight()) pos.setY(this->layout->getHeight() - 1); this->updateSelection(pos); } void CollisionPixmapItem::updateSelection(QPoint pos) { Block block; - if (map->getBlock(pos.x(), pos.y(), &block)) { + if (this->layout->getBlock(pos.x(), pos.y(), &block)) { this->selectedCollision->setValue(block.collision()); this->selectedElevation->setValue(block.elevation()); } diff --git a/src/ui/colorinputwidget.cpp b/src/ui/colorinputwidget.cpp new file mode 100644 index 000000000..8b40be275 --- /dev/null +++ b/src/ui/colorinputwidget.cpp @@ -0,0 +1,229 @@ +#include "colorinputwidget.h" +#include "ui_colorinputwidget.h" +#include "colorpicker.h" + +#include + +class HexCodeValidator : public QValidator { + virtual QValidator::State validate(QString &input, int &) const override { + input = input.toUpper(); + return QValidator::Acceptable; + } +}; + +static inline int rgb5(int rgb) { return round(static_cast(rgb * 31) / 255.0); } +static inline int rgb8(int rgb) { return round(rgb * 255. / 31.); } +static inline int gbaRed(int rgb) { return rgb & 0x1f; } +static inline int gbaGreen(int rgb) { return (rgb >> 5) & 0x1f; } +static inline int gbaBlue(int rgb) { return (rgb >> 10) & 0x1f; } + +ColorInputWidget::ColorInputWidget(QWidget *parent) : + QGroupBox(parent), + ui(new Ui::ColorInputWidget) +{ + init(); +} + +ColorInputWidget::ColorInputWidget(const QString &title, QWidget *parent) : + QGroupBox(title, parent), + ui(new Ui::ColorInputWidget) +{ + init(); +} + +void ColorInputWidget::init() { + ui->setupUi(this); + + // Connect color change signals + connect(ui->slider_Red, &QSlider::valueChanged, this, &ColorInputWidget::setRgbFromSliders); + connect(ui->slider_Green, &QSlider::valueChanged, this, &ColorInputWidget::setRgbFromSliders); + connect(ui->slider_Blue, &QSlider::valueChanged, this, &ColorInputWidget::setRgbFromSliders); + + connect(ui->spinBox_Red, QOverload::of(&QSpinBox::valueChanged), this, &ColorInputWidget::setRgbFromSpinners); + connect(ui->spinBox_Green, QOverload::of(&QSpinBox::valueChanged), this, &ColorInputWidget::setRgbFromSpinners); + connect(ui->spinBox_Blue, QOverload::of(&QSpinBox::valueChanged), this, &ColorInputWidget::setRgbFromSpinners); + + static const HexCodeValidator hexValidator; + ui->lineEdit_Hex->setValidator(&hexValidator); + connect(ui->lineEdit_Hex, &QLineEdit::textEdited, this, &ColorInputWidget::setRgbFromHexString); + + // We have separate signals for when color input editing finishes. + // This is mostly useful for external commit histories, esp. for the sliders which can rapidly emit color change signals. + connect(ui->slider_Red, &QSlider::sliderReleased, this, &ColorInputWidget::editingFinished); + connect(ui->slider_Green, &QSlider::sliderReleased, this, &ColorInputWidget::editingFinished); + connect(ui->slider_Blue, &QSlider::sliderReleased, this, &ColorInputWidget::editingFinished); + + connect(ui->spinBox_Red, &QSpinBox::editingFinished, this, &ColorInputWidget::editingFinished); + connect(ui->spinBox_Green, &QSpinBox::editingFinished, this, &ColorInputWidget::editingFinished); + connect(ui->spinBox_Blue, &QSpinBox::editingFinished, this, &ColorInputWidget::editingFinished); + + connect(ui->lineEdit_Hex, &QLineEdit::editingFinished, this, &ColorInputWidget::editingFinished); + + // Connect color picker + connect(ui->button_Eyedrop, &QToolButton::clicked, this, &ColorInputWidget::pickColor); + + setBitDepth(24); +} + +ColorInputWidget::~ColorInputWidget() { + delete ui; +} + +void ColorInputWidget::updateColorUi() { + blockEditSignals(true); + + int red = qRed(m_color); + int green = qGreen(m_color); + int blue = qBlue(m_color); + + if (m_bitDepth == 15) { + // Sliders + ui->slider_Red->setValue(rgb5(red)); + ui->slider_Green->setValue(rgb5(green)); + ui->slider_Blue->setValue(rgb5(blue)); + + // Hex + int hex15 = (rgb5(blue) << 10) | (rgb5(green) << 5) | rgb5(red); + ui->lineEdit_Hex->setText(QString("%1").arg(hex15, 4, 16, QLatin1Char('0')).toUpper()); + + // Spinners + ui->spinBox_Red->setValue(rgb5(red)); + ui->spinBox_Green->setValue(rgb5(green)); + ui->spinBox_Blue->setValue(rgb5(blue)); + } else { + // Sliders + ui->slider_Red->setValue(red); + ui->slider_Green->setValue(green); + ui->slider_Blue->setValue(blue); + + // Hex + QColor color(red, green, blue); + ui->lineEdit_Hex->setText(color.name().remove(0, 1).toUpper()); + + // Spinners + ui->spinBox_Red->setValue(red); + ui->spinBox_Green->setValue(green); + ui->spinBox_Blue->setValue(blue); + } + + ui->frame_ColorDisplay->setStyleSheet(QString("background-color: rgb(%1, %2, %3);").arg(red).arg(green).arg(blue)); + + blockEditSignals(false); +} + +void ColorInputWidget::blockEditSignals(bool block) { + ui->slider_Red->blockSignals(block); + ui->slider_Green->blockSignals(block); + ui->slider_Blue->blockSignals(block); + + ui->spinBox_Red->blockSignals(block); + ui->spinBox_Green->blockSignals(block); + ui->spinBox_Blue->blockSignals(block); + + ui->lineEdit_Hex->blockSignals(block); +} + +bool ColorInputWidget::setBitDepth(int bits) { + if (m_bitDepth == bits) + return true; + + int singleStep, pageStep, maximum; + QString hexInputMask; + if (bits == 15) { + singleStep = 1; + pageStep = 4; + maximum = 31; + hexInputMask = "HHHH"; + } else if (bits == 24) { + singleStep = 8; + pageStep = 24; + maximum = 255; + hexInputMask = "HHHHHH"; + } else { + // Unsupported bit depth + return false; + } + m_bitDepth = bits; + + blockEditSignals(true); + ui->slider_Red->setSingleStep(singleStep); + ui->slider_Green->setSingleStep(singleStep); + ui->slider_Blue->setSingleStep(singleStep); + ui->slider_Red->setPageStep(pageStep); + ui->slider_Green->setPageStep(pageStep); + ui->slider_Blue->setPageStep(pageStep); + ui->slider_Red->setMaximum(maximum); + ui->slider_Green->setMaximum(maximum); + ui->slider_Blue->setMaximum(maximum); + + ui->spinBox_Red->setSingleStep(singleStep); + ui->spinBox_Green->setSingleStep(singleStep); + ui->spinBox_Blue->setSingleStep(singleStep); + ui->spinBox_Red->setMaximum(maximum); + ui->spinBox_Green->setMaximum(maximum); + ui->spinBox_Blue->setMaximum(maximum); + + ui->lineEdit_Hex->setInputMask(hexInputMask); + ui->lineEdit_Hex->setMaxLength(hexInputMask.length()); + + updateColorUi(); + blockEditSignals(false); + emit bitDepthChanged(m_bitDepth); + return true; +} + +void ColorInputWidget::setColor(QRgb rgb) { + if (m_color == rgb) + return; + m_color = rgb; + updateColorUi(); + emit colorChanged(m_color); +} + +void ColorInputWidget::setRgbFromSliders() { + if (m_bitDepth == 15) { + setColor(qRgb(rgb8(ui->slider_Red->value()), + rgb8(ui->slider_Green->value()), + rgb8(ui->slider_Blue->value()))); + } else { + setColor(qRgb(ui->slider_Red->value(), + ui->slider_Green->value(), + ui->slider_Blue->value())); + } +} + +void ColorInputWidget::setRgbFromSpinners() { + if (m_bitDepth == 15) { + setColor(qRgb(rgb8(ui->spinBox_Red->value()), rgb8(ui->spinBox_Green->value()), rgb8(ui->spinBox_Blue->value()))); + } else { + setColor(qRgb(ui->spinBox_Red->value(), ui->spinBox_Green->value(), ui->spinBox_Blue->value())); + } +} + +void ColorInputWidget::setRgbFromHexString(const QString &text) { + if ((m_bitDepth == 24 && text.length() != 6) + || (m_bitDepth == 15 && text.length() != 4)) + return; + + bool ok = false; + int rgb = text.toInt(&ok, 16); + if (!ok) rgb = 0xFFFFFFFF; + + if (m_bitDepth == 15) { + int rc = gbaRed(rgb); + int gc = gbaGreen(rgb); + int bc = gbaBlue(rgb); + setColor(qRgb(rgb8(rc), rgb8(gc), rgb8(bc))); + } else { + setColor(qRgb(qRed(rgb), qGreen(rgb), qBlue(rgb))); + } +} + +void ColorInputWidget::pickColor() { + ColorPicker picker(this); + if (picker.exec() == QDialog::Accepted) { + QColor c = picker.getColor(); + setColor(c.rgb()); + emit editingFinished(); + } +} diff --git a/src/ui/colorpicker.cpp b/src/ui/colorpicker.cpp index ee2baff3c..dccffc0e1 100644 --- a/src/ui/colorpicker.cpp +++ b/src/ui/colorpicker.cpp @@ -48,7 +48,6 @@ void ColorPicker::hover(int mouseX, int mouseY) { return; // 15 X 15 box with 8x magnification = 120px square) - QRect zoomRect(mouseX - zoom_box_dimensions / 2, mouseY - zoom_box_dimensions / 2, zoom_box_dimensions, zoom_box_dimensions); QPixmap grab = screen->grabWindow(0, mouseX - zoom_box_dimensions / 2, mouseY - zoom_box_dimensions / 2, zoom_box_dimensions, zoom_box_dimensions); int pixelRatio = grab.devicePixelRatio(); diff --git a/src/ui/connectionpixmapitem.cpp b/src/ui/connectionpixmapitem.cpp index f84120128..35e07a15c 100644 --- a/src/ui/connectionpixmapitem.cpp +++ b/src/ui/connectionpixmapitem.cpp @@ -9,6 +9,7 @@ ConnectionPixmapItem::ConnectionPixmapItem(MapConnection* connection, int x, int connection(connection) { this->setEditable(true); + setFlag(ItemIsFocusable, true); this->basePixmap = pixmap(); this->setOrigin(x, y); } @@ -110,17 +111,20 @@ bool ConnectionPixmapItem::getEditable() { } void ConnectionPixmapItem::setSelected(bool selected) { + if (selected && !hasFocus()) { + setFocus(Qt::OtherFocusReason); + } + if (this->selected == selected) return; this->selected = selected; + this->render(); emit selectionChanged(selected); } void ConnectionPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *) { - if (!this->getEditable()) - return; - this->setSelected(true); + setFocus(Qt::MouseFocusReason); } void ConnectionPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { @@ -131,3 +135,18 @@ void ConnectionPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { void ConnectionPixmapItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *) { emit connectionItemDoubleClicked(this->connection); } + +void ConnectionPixmapItem::keyPressEvent(QKeyEvent* event) { + if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace) { + emit deleteRequested(this->connection); + } else { + QGraphicsPixmapItem::keyPressEvent(event); + } +} + +void ConnectionPixmapItem::focusInEvent(QFocusEvent* event) { + if (!this->getEditable()) + return; + this->setSelected(true); + QGraphicsPixmapItem::focusInEvent(event); +} diff --git a/src/ui/connectionslistitem.cpp b/src/ui/connectionslistitem.cpp index a5b8759a3..ccdf7e6c0 100644 --- a/src/ui/connectionslistitem.cpp +++ b/src/ui/connectionslistitem.cpp @@ -10,6 +10,7 @@ ConnectionsListItem::ConnectionsListItem(QWidget *parent, MapConnection * connec ui(new Ui::ConnectionsListItem) { ui->setupUi(this); + setFocusPolicy(Qt::StrongFocus); const QSignalBlocker blocker1(ui->comboBox_Direction); const QSignalBlocker blocker2(ui->comboBox_Map); @@ -101,3 +102,16 @@ void ConnectionsListItem::on_button_Delete_clicked() { void ConnectionsListItem::on_button_OpenMap_clicked() { emit openMapClicked(this->connection); } + +void ConnectionsListItem::focusInEvent(QFocusEvent* event) { + this->setSelected(true); + QFrame::focusInEvent(event); +} + +void ConnectionsListItem::keyPressEvent(QKeyEvent* event) { + if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace) { + on_button_Delete_clicked(); + } else { + QFrame::keyPressEvent(event); + } +} diff --git a/src/ui/currentselectedmetatilespixmapitem.cpp b/src/ui/currentselectedmetatilespixmapitem.cpp index 0967e5588..e8b16f49a 100644 --- a/src/ui/currentselectedmetatilespixmapitem.cpp +++ b/src/ui/currentselectedmetatilespixmapitem.cpp @@ -2,7 +2,7 @@ #include "imageproviders.h" #include -QPixmap drawMetatileSelection(MetatileSelection selection, Map *map) { +QPixmap drawMetatileSelection(MetatileSelection selection, Layout *layout) { int width = selection.dimensions.x() * 16; int height = selection.dimensions.y() * 16; QImage image(width, height, QImage::Format_RGBA8888); @@ -19,10 +19,10 @@ QPixmap drawMetatileSelection(MetatileSelection selection, Map *map) { if (item.enabled) { QImage metatile_image = getMetatileImage( item.metatileId, - map->layout->tileset_primary, - map->layout->tileset_secondary, - map->metatileLayerOrder, - map->metatileLayerOpacity); + layout->tileset_primary, + layout->tileset_secondary, + layout->metatileLayerOrder, + layout->metatileLayerOpacity); painter.drawImage(metatile_origin, metatile_image); } } @@ -34,5 +34,5 @@ QPixmap drawMetatileSelection(MetatileSelection selection, Map *map) { void CurrentSelectedMetatilesPixmapItem::draw() { MetatileSelection selection = metatileSelector->getMetatileSelection(); - setPixmap(drawMetatileSelection(selection, this->map)); + setPixmap(drawMetatileSelection(selection, this->layout)); } diff --git a/src/ui/customscriptseditor.cpp b/src/ui/customscriptseditor.cpp index b7180b923..284ea3336 100644 --- a/src/ui/customscriptseditor.cpp +++ b/src/ui/customscriptseditor.cpp @@ -4,9 +4,9 @@ #include "config.h" #include "editor.h" #include "shortcut.h" +#include "filedialog.h" #include -#include CustomScriptsEditor::CustomScriptsEditor(QWidget *parent) : QMainWindow(parent), @@ -23,8 +23,7 @@ CustomScriptsEditor::CustomScriptsEditor(QWidget *parent) : for (int i = 0; i < paths.length(); i++) this->displayScript(paths.at(i), enabled.at(i)); - this->fileDialogDir = userConfig.projectDir; - + connect(ui->button_Help, &QAbstractButton::clicked, this, &CustomScriptsEditor::openManual); connect(ui->button_CreateNewScript, &QAbstractButton::clicked, this, &CustomScriptsEditor::createNewScript); connect(ui->button_LoadScript, &QAbstractButton::clicked, this, &CustomScriptsEditor::loadScript); connect(ui->button_RefreshScripts, &QAbstractButton::clicked, this, &CustomScriptsEditor::userRefreshScripts); @@ -147,19 +146,13 @@ bool CustomScriptsEditor::getScriptEnabled(QListWidgetItem * item) const { } QString CustomScriptsEditor::chooseScript(QString dir) { - return QFileDialog::getOpenFileName(this, "Choose Custom Script File", dir, "JavaScript Files (*.js)"); + return FileDialog::getOpenFileName(this, "Choose Custom Script File", dir, "JavaScript Files (*.js)"); } void CustomScriptsEditor::createNewScript() { - QString filepath = QFileDialog::getSaveFileName(this, "Create New Script File", this->fileDialogDir + "/new_script.js", "JavaScript Files (*.js)"); - - // QFileDialog::getSaveFileName returns focus to the main editor window when closed. Workaround for this below - this->raise(); - this->activateWindow(); - + const QString filepath = FileDialog::getSaveFileName(this, "Create New Script File", FileDialog::getDirectory() + "/new_script.js", "JavaScript Files (*.js)"); if (filepath.isEmpty()) return; - this->fileDialogDir = filepath; QFile scriptFile(filepath); if (!scriptFile.open(QIODevice::WriteOnly)) { @@ -179,10 +172,9 @@ void CustomScriptsEditor::createNewScript() { } void CustomScriptsEditor::loadScript() { - QString filepath = this->chooseScript(this->fileDialogDir); + QString filepath = this->chooseScript(FileDialog::getDirectory()); if (filepath.isEmpty()) return; - this->fileDialogDir = filepath; this->displayNewScript(filepath); } @@ -238,6 +230,11 @@ void CustomScriptsEditor::openSelectedScripts() { this->openScript(item); } +void CustomScriptsEditor::openManual() { + static const QUrl url("https://huderlem.github.io/porymap/manual/scripting-capabilities.html"); + QDesktopServices::openUrl(url); +} + // When the user refreshes the scripts we show a little tooltip as feedback. // We don't want this tooltip to display when we refresh programmatically, like when changes are saved. void CustomScriptsEditor::userRefreshScripts() { diff --git a/src/ui/eventfilters.cpp b/src/ui/eventfilters.cpp new file mode 100644 index 000000000..1e7b2b804 --- /dev/null +++ b/src/ui/eventfilters.cpp @@ -0,0 +1,26 @@ +#include "eventfilters.h" + +#include + + + +bool WheelFilter::eventFilter(QObject *, QEvent *event) { + if (event->type() == QEvent::Wheel) { + return true; + } + return false; +} + + + +bool MapSceneEventFilter::eventFilter(QObject*, QEvent *event) { + if (event->type() == QEvent::GraphicsSceneWheel) { + QGraphicsSceneWheelEvent *wheelEvent = static_cast(event); + if (wheelEvent->modifiers() & Qt::ControlModifier) { + emit wheelZoom(wheelEvent->delta() > 0 ? 1 : -1); + event->accept(); + return true; + } + } + return false; +} diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index 90d5b776e..e90658a24 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -12,11 +12,6 @@ static inline QSpacerItem *createSpacerH() { return new QSpacerItem(2, 1, QSizePolicy::Expanding, QSizePolicy::Minimum); } -static inline QSpacerItem *createSpacerV() { - return new QSpacerItem(1, 2, QSizePolicy::Minimum, QSizePolicy::Expanding); -} - - void EventFrame::setup() { // delete default layout due to lack of parent at initialization diff --git a/src/ui/filterchildrenproxymodel.cpp b/src/ui/filterchildrenproxymodel.cpp index a08c150c7..99464ae60 100644 --- a/src/ui/filterchildrenproxymodel.cpp +++ b/src/ui/filterchildrenproxymodel.cpp @@ -8,6 +8,15 @@ FilterChildrenProxyModel::FilterChildrenProxyModel(QObject *parent) : bool FilterChildrenProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { + if (this->hideEmpty && source_parent.row() < 0) // want to hide children + { + QModelIndex source_index = sourceModel()->index(source_row, this->filterKeyColumn(), source_parent) ; + if(source_index.isValid()) + { + if (!sourceModel()->hasChildren(source_index)) + return false; + } + } // custom behaviour : if(filterRegularExpression().pattern().isEmpty() == false) { diff --git a/src/ui/flowlayout.cpp b/src/ui/flowlayout.cpp index 2d5a5faac..54cde082b 100644 --- a/src/ui/flowlayout.cpp +++ b/src/ui/flowlayout.cpp @@ -81,7 +81,7 @@ QSize FlowLayout::sizeHint() const { QSize FlowLayout::minimumSize() const { QSize size; - for (const QLayoutItem *item : qAsConst(itemList)) + for (const QLayoutItem *item : std::as_const(itemList)) size = size.expandedTo(item->minimumSize()); const QMargins margins = contentsMargins(); @@ -97,7 +97,7 @@ int FlowLayout::doLayout(const QRect &rect, bool testOnly) const { int y = effectiveRect.y(); int lineHeight = 0; - for (QLayoutItem *item : qAsConst(itemList)) { + for (QLayoutItem *item : std::as_const(itemList)) { const QWidget *wid = item->widget(); int spaceX = horizontalSpacing(); if (spaceX == -1) diff --git a/src/ui/graphicsview.cpp b/src/ui/graphicsview.cpp index 1c86e004b..738272114 100644 --- a/src/ui/graphicsview.cpp +++ b/src/ui/graphicsview.cpp @@ -24,17 +24,40 @@ void GraphicsView::moveEvent(QMoveEvent *event) { label_MapRulerStatus->move(mapToGlobal(QPoint(6, 6))); } +void MapView::keyPressEvent(QKeyEvent *event) { + if (editor && (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace)) { + editor->deleteSelectedEvents(); + } else { + QGraphicsView::keyPressEvent(event); + } +} + void MapView::drawForeground(QPainter *painter, const QRectF&) { - foreach (Overlay * overlay, this->overlayMap) - overlay->renderItems(painter); + for (auto i = this->overlayMap.constBegin(); i != this->overlayMap.constEnd(); i++) { + i.value()->renderItems(painter); + } if (!editor) return; QStyleOptionGraphicsItem option; - for (QGraphicsLineItem* line : editor->gridLines) { - if (line && line->isVisible()) - line->paint(painter, &option, this); + + // Draw elements of the map view that should always render on top of anything added by the user with the scripting API. + + // Draw map grid + if (editor->mapGrid && editor->mapGrid->isVisible()) { + painter->save(); + if (editor->layout) { + // We're clipping here to hide parts of the grid that are outside the map. + const QRectF mapRect(-0.5, -0.5, editor->layout->getWidth() * 16 + 1.5, editor->layout->getHeight() * 16 + 1.5); + painter->setClipping(true); + painter->setClipRect(mapRect); + } + for (auto item : editor->mapGrid->childItems()) + item->paint(painter, &option, this); + painter->restore(); } + + // Draw cursor rectangles if (editor->playerViewRect && editor->playerViewRect->isVisible()) editor->playerViewRect->paint(painter, &option, this); if (editor->cursorMapTileRect && editor->cursorMapTileRect->isVisible()) @@ -42,9 +65,8 @@ void MapView::drawForeground(QPainter *painter, const QRectF&) { } void MapView::clearOverlayMap() { - foreach (Overlay * overlay, this->overlayMap) { - overlay->clearItems(); - delete overlay; + for (auto i = this->overlayMap.constBegin(); i != this->overlayMap.constEnd(); i++) { + delete i.value(); } this->overlayMap.clear(); } diff --git a/src/ui/gridsettings.cpp b/src/ui/gridsettings.cpp new file mode 100644 index 000000000..d3346f11e --- /dev/null +++ b/src/ui/gridsettings.cpp @@ -0,0 +1,230 @@ +#include "ui_gridsettingsdialog.h" +#include "gridsettings.h" + +// TODO: Save settings in config + +const QMap GridSettings::styleToName = { + {Style::Solid, "Solid"}, + {Style::LargeDashes, "Large Dashes"}, + {Style::SmallDashes, "Small Dashes"}, + {Style::Crosshairs, "Crosshairs"}, + {Style::Dots, "Dots"}, +}; + +QString GridSettings::getStyleName(GridSettings::Style style) { + return styleToName.value(style); +} + +GridSettings::Style GridSettings::getStyleFromName(const QString &name) { + return styleToName.key(name, GridSettings::Style::Solid); +} + +// We do some extra work here to A: try and center the dashes away from the intersections, and B: keep the dash pattern's total +// length equal to the length of a grid square. This keeps the patterns looking reasonable regardless of the grid size. +// Otherwise, the dashes can start to intersect in weird ways and create grid patterns that don't look like a rectangular grid. +QVector GridSettings::getCenteredDashPattern(uint length, qreal dashLength, qreal gapLength) const { + const qreal minEdgesLength = 0.6*2; + if (length <= dashLength + minEdgesLength) + return {dashLength}; + + // Every dash after the first one needs to have room for a 'gapLength' segment. + const int numDashes = 1 + ((length - minEdgesLength) - dashLength) / (dashLength + gapLength); + + // Total length of the pattern excluding the centering edges. There are always 1 fewer gap segments than dashes. + const qreal mainLength = (dashLength * numDashes) + (gapLength * (numDashes-1)); + + const qreal edgeLength = (length - mainLength) / 2; + + // Fill the pattern + QVector pattern = {0, edgeLength}; + for (int i = 0; i < numDashes-1; i++) { + pattern.append(dashLength); + pattern.append(gapLength); + } + pattern.append(dashLength); + pattern.append(edgeLength); + + return pattern; +} + +QVector GridSettings::getDashPattern(uint length) const { + switch (this->style) { + + // Equivalent to setting Qt::PenStyle::Solid with no dash pattern. + case Style::Solid: return {1, 0}; + + // Roughly equivalent to Qt::PenStyle::DashLine but with centering. + case Style::LargeDashes: return getCenteredDashPattern(length, 3.0, 2.0); + + // Roughly equivalent to Qt::PenStyle::DotLine but with centering. + case Style::SmallDashes: return getCenteredDashPattern(length, 1.0, 2.5); + + // Dashes only at intersections, in the shape of a crosshair. + case Style::Crosshairs: { + const qreal crosshairLength = 2.0; + return {crosshairLength / 2, length - crosshairLength, crosshairLength / 2, 0}; + } + + // Dots only at intersections. + case Style::Dots: { + const qreal dotLength = 0.1; + return {dotLength, length - dotLength}; + } + + // Invalid + default: return {}; + } +} + + + +GridSettingsDialog::GridSettingsDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::GridSettingsDialog), + m_settings(new GridSettings), + m_originalSettings(*m_settings) +{ + m_ownedSettings = true; + init(); +} + +GridSettingsDialog::GridSettingsDialog(GridSettings *settings, QWidget *parent) : + QDialog(parent), + ui(new Ui::GridSettingsDialog), + m_settings(settings), + m_originalSettings(*settings) +{ + m_ownedSettings = false; + init(); +} + +void GridSettingsDialog::init() { + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + // Populate the styles combo box + const QSignalBlocker b_Style(ui->comboBox_Style); + ui->comboBox_Style->addItem(GridSettings::getStyleName(GridSettings::Style::Solid)); + ui->comboBox_Style->addItem(GridSettings::getStyleName(GridSettings::Style::LargeDashes)); + ui->comboBox_Style->addItem(GridSettings::getStyleName(GridSettings::Style::SmallDashes)); + ui->comboBox_Style->addItem(GridSettings::getStyleName(GridSettings::Style::Crosshairs)); + ui->comboBox_Style->addItem(GridSettings::getStyleName(GridSettings::Style::Dots)); + + ui->button_LinkDimensions->setChecked(m_dimensionsLinked); + ui->button_LinkOffsets->setChecked(m_offsetsLinked); + + connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &GridSettingsDialog::dialogButtonClicked); + connect(ui->button_LinkDimensions, &QAbstractButton::toggled, [this](bool on) { m_dimensionsLinked = on; }); + connect(ui->button_LinkOffsets, &QAbstractButton::toggled, [this](bool on) { m_offsetsLinked = on; }); + connect(ui->colorInput, &ColorInputWidget::colorChanged, this, &GridSettingsDialog::onColorChanged); + connect(this, &GridSettingsDialog::rejected, [this] { setSettings(m_originalSettings); }); + + updateInput(); +} + +GridSettingsDialog::~GridSettingsDialog() { + delete ui; + if (m_ownedSettings) + delete m_settings; +} + +void GridSettingsDialog::setSettings(const GridSettings &settings) { + if (*m_settings == settings) + return; + *m_settings = settings; + updateInput(); + emit changedGridSettings(); +} + +void GridSettingsDialog::updateInput() { + setWidth(m_settings->width); + setHeight(m_settings->height); + setOffsetX(m_settings->offsetX); + setOffsetY(m_settings->offsetY); + + const QSignalBlocker b_Color(ui->colorInput); + ui->colorInput->setColor(m_settings->color.rgb()); + + const QSignalBlocker b_Style(ui->comboBox_Style); + ui->comboBox_Style->setCurrentText(GridSettings::getStyleName(m_settings->style)); +} + +void GridSettingsDialog::setWidth(int value) { + const QSignalBlocker b(ui->spinBox_Width); + ui->spinBox_Width->setValue(value); + m_settings->width = value; +} + +void GridSettingsDialog::setHeight(int value) { + const QSignalBlocker b(ui->spinBox_Height); + ui->spinBox_Height->setValue(value); + m_settings->height = value; +} + +void GridSettingsDialog::setOffsetX(int value) { + const QSignalBlocker b(ui->spinBox_X); + ui->spinBox_X->setValue(value); + m_settings->offsetX = value; +} + +void GridSettingsDialog::setOffsetY(int value) { + const QSignalBlocker b(ui->spinBox_Y); + ui->spinBox_Y->setValue(value); + m_settings->offsetY = value; +} + +void GridSettingsDialog::on_spinBox_Width_valueChanged(int value) { + setWidth(value); + if (m_dimensionsLinked) + setHeight(value); + + emit changedGridSettings(); +} + +void GridSettingsDialog::on_spinBox_Height_valueChanged(int value) { + setHeight(value); + if (m_dimensionsLinked) + setWidth(value); + + emit changedGridSettings(); +} + +void GridSettingsDialog::on_spinBox_X_valueChanged(int value) { + setOffsetX(value); + if (m_offsetsLinked) + setOffsetY(value); + + emit changedGridSettings(); +} + +void GridSettingsDialog::on_spinBox_Y_valueChanged(int value) { + setOffsetY(value); + if (m_offsetsLinked) + setOffsetX(value); + + emit changedGridSettings(); +} + +void GridSettingsDialog::on_comboBox_Style_currentTextChanged(const QString &text) { + m_settings->style = GridSettings::getStyleFromName(text); + emit changedGridSettings(); +} + +void GridSettingsDialog::onColorChanged(QRgb color) { + m_settings->color = QColor::fromRgb(color); + emit changedGridSettings(); +} + +void GridSettingsDialog::dialogButtonClicked(QAbstractButton *button) { + auto role = ui->buttonBox->buttonRole(button); + if (role == QDialogButtonBox::AcceptRole) { + // "OK" + accept(); + } else if (role == QDialogButtonBox::RejectRole) { + // "Cancel" + reject(); + } else if (role == QDialogButtonBox::ResetRole) { + // "Restore Defaults" + setSettings(m_defaultSettings); + } +} diff --git a/src/ui/mappixmapitem.cpp b/src/ui/layoutpixmapitem.cpp similarity index 66% rename from src/ui/mappixmapitem.cpp rename to src/ui/layoutpixmapitem.cpp index 173403cf0..f53cf275a 100644 --- a/src/ui/mappixmapitem.cpp +++ b/src/ui/layoutpixmapitem.cpp @@ -1,4 +1,4 @@ -#include "mappixmapitem.h" +#include "layoutpixmapitem.h" #include "metatile.h" #include "log.h" #include "scripting.h" @@ -7,8 +7,8 @@ #define SWAP(a, b) do { if (a != b) { a ^= b; b ^= a; a ^= b; } } while (0) -void MapPixmapItem::paint(QGraphicsSceneMouseEvent *event) { - if (map) { +void LayoutPixmapItem::paint(QGraphicsSceneMouseEvent *event) { + if (layout) { if (event->type() == QEvent::GraphicsSceneMouseRelease) { actionId_++; } else { @@ -20,7 +20,7 @@ void MapPixmapItem::paint(QGraphicsSceneMouseEvent *event) { pos = this->adjustCoords(pos); } else { this->prevStraightPathState = false; - this->lockedAxis = MapPixmapItem::Axis::None; + this->lockedAxis = LayoutPixmapItem::Axis::None; } // Paint onto the map. @@ -43,8 +43,8 @@ void MapPixmapItem::paint(QGraphicsSceneMouseEvent *event) { } } -void MapPixmapItem::shift(QGraphicsSceneMouseEvent *event) { - if (map) { +void LayoutPixmapItem::shift(QGraphicsSceneMouseEvent *event) { + if (layout) { if (event->type() == QEvent::GraphicsSceneMouseRelease) { actionId_++; } else { @@ -56,7 +56,7 @@ void MapPixmapItem::shift(QGraphicsSceneMouseEvent *event) { pos = this->adjustCoords(pos); } else { this->prevStraightPathState = false; - this->lockedAxis = MapPixmapItem::Axis::None; + this->lockedAxis = LayoutPixmapItem::Axis::None; } if (event->type() == QEvent::GraphicsSceneMousePress) { @@ -76,32 +76,32 @@ void MapPixmapItem::shift(QGraphicsSceneMouseEvent *event) { } } -void MapPixmapItem::shift(int xDelta, int yDelta, bool fromScriptCall) { - Blockdata oldMetatiles = map->layout->blockdata; +void LayoutPixmapItem::shift(int xDelta, int yDelta, bool fromScriptCall) { + Blockdata oldMetatiles = this->layout->blockdata; - for (int i = 0; i < map->getWidth(); i++) - for (int j = 0; j < map->getHeight(); j++) { + for (int i = 0; i < this->layout->getWidth(); i++) + for (int j = 0; j < this->layout->getHeight(); j++) { int destX = i + xDelta; int destY = j + yDelta; if (destX < 0) - do { destX += map->getWidth(); } while (destX < 0); + do { destX += this->layout->getWidth(); } while (destX < 0); if (destY < 0) - do { destY += map->getHeight(); } while (destY < 0); - destX %= map->getWidth(); - destY %= map->getHeight(); + do { destY += this->layout->getHeight(); } while (destY < 0); + destX %= this->layout->getWidth(); + destY %= this->layout->getHeight(); - int blockIndex = j * map->getWidth() + i; + int blockIndex = j * this->layout->getWidth() + i; Block srcBlock = oldMetatiles.at(blockIndex); - map->setBlock(destX, destY, srcBlock); + this->layout->setBlock(destX, destY, srcBlock); } - if (!fromScriptCall && map->layout->blockdata != oldMetatiles) { - map->editHistory.push(new ShiftMetatiles(map, oldMetatiles, map->layout->blockdata, actionId_)); + if (!fromScriptCall && this->layout->blockdata != oldMetatiles) { + this->layout->editHistory.push(new ShiftMetatiles(this->layout, oldMetatiles, this->layout->blockdata, actionId_)); Scripting::cb_MapShifted(xDelta, yDelta); } } -void MapPixmapItem::paintNormal(int x, int y, bool fromScriptCall) { +void LayoutPixmapItem::paintNormal(int x, int y, bool fromScriptCall) { MetatileSelection selection = this->metatileSelector->getMetatileSelection(); int initialX = fromScriptCall ? x : this->paint_tile_initial_x; int initialY = fromScriptCall ? y : this->paint_tile_initial_y; @@ -117,14 +117,14 @@ void MapPixmapItem::paintNormal(int x, int y, bool fromScriptCall) { y = initialY + (yDiff / selection.dimensions.y()) * selection.dimensions.y(); // for edit history - Blockdata oldMetatiles = !fromScriptCall ? map->layout->blockdata : Blockdata(); + Blockdata oldMetatiles = !fromScriptCall ? this->layout->blockdata : Blockdata(); - for (int i = 0; i < selection.dimensions.x() && i + x < map->getWidth(); i++) - for (int j = 0; j < selection.dimensions.y() && j + y < map->getHeight(); j++) { + for (int i = 0; i < selection.dimensions.x() && i + x < this->layout->getWidth(); i++) + for (int j = 0; j < selection.dimensions.y() && j + y < this->layout->getHeight(); j++) { int actualX = i + x; int actualY = j + y; Block block; - if (map->getBlock(actualX, actualY, &block)) { + if (this->layout->getBlock(actualX, actualY, &block)) { int index = j * selection.dimensions.x() + i; MetatileSelectionItem item = selection.metatileItems.at(index); if (!item.enabled) @@ -135,19 +135,19 @@ void MapPixmapItem::paintNormal(int x, int y, bool fromScriptCall) { block.setCollision(collisionItem.collision); block.setElevation(collisionItem.elevation); } - map->setBlock(actualX, actualY, block, !fromScriptCall); + this->layout->setBlock(actualX, actualY, block, !fromScriptCall); } } - if (!fromScriptCall && map->layout->blockdata != oldMetatiles) { - map->editHistory.push(new PaintMetatile(map, oldMetatiles, map->layout->blockdata, actionId_)); + if (!fromScriptCall && this->layout->blockdata != oldMetatiles) { + this->layout->editHistory.push(new PaintMetatile(this->layout, oldMetatiles, this->layout->blockdata, actionId_)); } } // These are tile offsets from the top-left tile in the 3x3 smart path selection. // Each entry is for one possibility from the marching squares value for a tile. // (Marching Squares: https://en.wikipedia.org/wiki/Marching_squares) -QList MapPixmapItem::smartPathTable = QList({ +QList LayoutPixmapItem::smartPathTable = QList({ 4, // 0000 4, // 0001 4, // 0010 @@ -189,7 +189,7 @@ bool isValidSmartPathSelection(MetatileSelection selection) { return true; } -void MapPixmapItem::paintSmartPath(int x, int y, bool fromScriptCall) { +void LayoutPixmapItem::paintSmartPath(int x, int y, bool fromScriptCall) { MetatileSelection selection = this->metatileSelector->getMetatileSelection(); if (!isValidSmartPathSelection(selection)) return; @@ -206,30 +206,30 @@ void MapPixmapItem::paintSmartPath(int x, int y, bool fromScriptCall) { } // for edit history - Blockdata oldMetatiles = !fromScriptCall ? map->layout->blockdata : Blockdata(); + Blockdata oldMetatiles = !fromScriptCall ? this->layout->blockdata : Blockdata(); // Fill the region with the open tile. for (int i = 0; i <= 1; i++) for (int j = 0; j <= 1; j++) { - if (!map->isWithinBounds(x + i, y + j)) + if (!this->layout->isWithinBounds(x + i, y + j)) continue; int actualX = i + x; int actualY = j + y; Block block; - if (map->getBlock(actualX, actualY, &block)) { + if (this->layout->getBlock(actualX, actualY, &block)) { block.setMetatileId(openMetatileId); if (setCollisions) { block.setCollision(openCollision); block.setElevation(openElevation); } - map->setBlock(actualX, actualY, block, !fromScriptCall); + this->layout->setBlock(actualX, actualY, block, !fromScriptCall); } } // Go back and resolve the edge tiles for (int i = -1; i <= 2; i++) for (int j = -1; j <= 2; j++) { - if (!map->isWithinBounds(x + i, y + j)) + if (!this->layout->isWithinBounds(x + i, y + j)) continue; // Ignore the corners, which can't possible be affected by the smart path. if ((i == -1 && j == -1) || (i == 2 && j == -1) || @@ -240,7 +240,7 @@ void MapPixmapItem::paintSmartPath(int x, int y, bool fromScriptCall) { int actualX = i + x; int actualY = j + y; Block block; - if (!map->getBlock(actualX, actualY, &block) || !isSmartPathTile(selection.metatileItems, block.metatileId())) { + if (!this->layout->getBlock(actualX, actualY, &block) || !isSmartPathTile(selection.metatileItems, block.metatileId())) { continue; } @@ -251,13 +251,13 @@ void MapPixmapItem::paintSmartPath(int x, int y, bool fromScriptCall) { Block left; // Get marching squares value, to determine which tile to use. - if (map->getBlock(actualX, actualY - 1, &top) && isSmartPathTile(selection.metatileItems, top.metatileId())) + if (this->layout->getBlock(actualX, actualY - 1, &top) && isSmartPathTile(selection.metatileItems, top.metatileId())) id += 1; - if (map->getBlock(actualX + 1, actualY, &right) && isSmartPathTile(selection.metatileItems, right.metatileId())) + if (this->layout->getBlock(actualX + 1, actualY, &right) && isSmartPathTile(selection.metatileItems, right.metatileId())) id += 2; - if (map->getBlock(actualX, actualY + 1, &bottom) && isSmartPathTile(selection.metatileItems, bottom.metatileId())) + if (this->layout->getBlock(actualX, actualY + 1, &bottom) && isSmartPathTile(selection.metatileItems, bottom.metatileId())) id += 4; - if (map->getBlock(actualX - 1, actualY, &left) && isSmartPathTile(selection.metatileItems, left.metatileId())) + if (this->layout->getBlock(actualX - 1, actualY, &left) && isSmartPathTile(selection.metatileItems, left.metatileId())) id += 8; block.setMetatileId(selection.metatileItems.at(smartPathTable[id]).metatileId); @@ -266,19 +266,19 @@ void MapPixmapItem::paintSmartPath(int x, int y, bool fromScriptCall) { block.setCollision(collisionItem.collision); block.setElevation(collisionItem.elevation); } - map->setBlock(actualX, actualY, block, !fromScriptCall); + this->layout->setBlock(actualX, actualY, block, !fromScriptCall); } - if (!fromScriptCall && map->layout->blockdata != oldMetatiles) { - map->editHistory.push(new PaintMetatile(map, oldMetatiles, map->layout->blockdata, actionId_)); + if (!fromScriptCall && this->layout->blockdata != oldMetatiles) { + this->layout->editHistory.push(new PaintMetatile(this->layout, oldMetatiles, this->layout->blockdata, actionId_)); } } -void MapPixmapItem::lockNondominantAxis(QGraphicsSceneMouseEvent *event) { +void LayoutPixmapItem::lockNondominantAxis(QGraphicsSceneMouseEvent *event) { /* Return if an axis is already locked, or if the mouse has been released. The mouse release check is necessary - * because MapPixmapItem::mouseReleaseEvent seems to get called before this function, which would unlock the axis + * because LayoutPixmapItem::mouseReleaseEvent seems to get called before this function, which would unlock the axis * and then get immediately re-locked here until the next ctrl-click. */ - if (this->lockedAxis != MapPixmapItem::Axis::None || event->type() == QEvent::GraphicsSceneMouseRelease) + if (this->lockedAxis != LayoutPixmapItem::Axis::None || event->type() == QEvent::GraphicsSceneMouseRelease) return; QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); @@ -293,31 +293,31 @@ void MapPixmapItem::lockNondominantAxis(QGraphicsSceneMouseEvent *event) { int yDiff = pos.y() - this->straight_path_initial_y; if (xDiff || yDiff) { if (abs(xDiff) < abs(yDiff)) { - this->lockedAxis = MapPixmapItem::Axis::X; + this->lockedAxis = LayoutPixmapItem::Axis::X; } else { - this->lockedAxis = MapPixmapItem::Axis::Y; + this->lockedAxis = LayoutPixmapItem::Axis::Y; } } } // Adjust the cooresponding coordinate when it is locked -QPoint MapPixmapItem::adjustCoords(QPoint pos) { - if (this->lockedAxis == MapPixmapItem::Axis::X) { +QPoint LayoutPixmapItem::adjustCoords(QPoint pos) { + if (this->lockedAxis == LayoutPixmapItem::Axis::X) { pos.setX(this->straight_path_initial_x); - } else if (this->lockedAxis == MapPixmapItem::Axis::Y) { + } else if (this->lockedAxis == LayoutPixmapItem::Axis::Y) { pos.setY(this->straight_path_initial_y); } return pos; } -void MapPixmapItem::updateMetatileSelection(QGraphicsSceneMouseEvent *event) { +void LayoutPixmapItem::updateMetatileSelection(QGraphicsSceneMouseEvent *event) { QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - // Snap point to within map bounds. + // Snap point to within layout bounds. if (pos.x() < 0) pos.setX(0); - if (pos.x() >= map->getWidth()) pos.setX(map->getWidth() - 1); + if (pos.x() >= this->layout->getWidth()) pos.setX(this->layout->getWidth() - 1); if (pos.y() < 0) pos.setY(0); - if (pos.y() >= map->getHeight()) pos.setY(map->getHeight() - 1); + if (pos.y() >= this->layout->getHeight()) pos.setY(this->layout->getHeight() - 1); // Update/apply copied metatiles. if (event->type() == QEvent::GraphicsSceneMousePress) { @@ -325,7 +325,7 @@ void MapPixmapItem::updateMetatileSelection(QGraphicsSceneMouseEvent *event) { selection.clear(); selection.append(QPoint(pos.x(), pos.y())); Block block; - if (map->getBlock(pos.x(), pos.y(), &block)) { + if (this->layout->getBlock(pos.x(), pos.y(), &block)) { this->metatileSelector->selectFromMap(block.metatileId(), block.collision(), block.elevation()); } } else if (event->type() == QEvent::GraphicsSceneMouseMove) { @@ -348,11 +348,11 @@ void MapPixmapItem::updateMetatileSelection(QGraphicsSceneMouseEvent *event) { int x = point.x(); int y = point.y(); Block block; - if (map->getBlock(x, y, &block)) { + if (this->layout->getBlock(x, y, &block)) { metatiles.append(block.metatileId()); } - int blockIndex = y * map->getWidth() + x; - block = map->layout->blockdata.at(blockIndex); + int blockIndex = y * this->layout->getWidth() + x; + block = this->layout->blockdata.at(blockIndex); auto collision = block.collision(); auto elevation = block.elevation(); collisions.append(QPair(collision, elevation)); @@ -362,8 +362,8 @@ void MapPixmapItem::updateMetatileSelection(QGraphicsSceneMouseEvent *event) { } } -void MapPixmapItem::floodFill(QGraphicsSceneMouseEvent *event) { - if (map) { +void LayoutPixmapItem::floodFill(QGraphicsSceneMouseEvent *event) { + if (this->layout) { if (event->type() == QEvent::GraphicsSceneMouseRelease) { actionId_++; } else { @@ -371,7 +371,7 @@ void MapPixmapItem::floodFill(QGraphicsSceneMouseEvent *event) { Block block; MetatileSelection selection = this->metatileSelector->getMetatileSelection(); int metatileId = selection.metatileItems.first().metatileId; - if (selection.metatileItems.count() > 1 || (map->getBlock(pos.x(), pos.y(), &block) && block.metatileId() != metatileId)) { + if (selection.metatileItems.count() > 1 || (this->layout->getBlock(pos.x(), pos.y(), &block) && block.metatileId() != metatileId)) { bool smartPathsEnabled = event->modifiers() & Qt::ShiftModifier; if ((this->settings->smartPathsEnabled || smartPathsEnabled) && selection.dimensions.x() == 3 && selection.dimensions.y() == 3) this->floodFillSmartPath(pos.x(), pos.y()); @@ -382,8 +382,8 @@ void MapPixmapItem::floodFill(QGraphicsSceneMouseEvent *event) { } } -void MapPixmapItem::magicFill(QGraphicsSceneMouseEvent *event) { - if (map) { +void LayoutPixmapItem::magicFill(QGraphicsSceneMouseEvent *event) { + if (this->layout) { if (event->type() == QEvent::GraphicsSceneMouseRelease) { actionId_++; } else { @@ -393,18 +393,18 @@ void MapPixmapItem::magicFill(QGraphicsSceneMouseEvent *event) { } } -void MapPixmapItem::magicFill(int x, int y, uint16_t metatileId, bool fromScriptCall) { +void LayoutPixmapItem::magicFill(int x, int y, uint16_t metatileId, bool fromScriptCall) { QPoint selectionDimensions(1, 1); QList selectedMetatiles = QList({MetatileSelectionItem{ true, metatileId }}); this->magicFill(x, y, selectionDimensions, selectedMetatiles, QList(), fromScriptCall); } -void MapPixmapItem::magicFill(int x, int y, bool fromScriptCall) { +void LayoutPixmapItem::magicFill(int x, int y, bool fromScriptCall) { MetatileSelection selection = this->metatileSelector->getMetatileSelection(); this->magicFill(x, y, selection.dimensions, selection.metatileItems, selection.collisionItems, fromScriptCall); } -void MapPixmapItem::magicFill( +void LayoutPixmapItem::magicFill( int initialX, int initialY, QPoint selectionDimensions, @@ -412,18 +412,18 @@ void MapPixmapItem::magicFill( QList selectedCollisions, bool fromScriptCall) { Block block; - if (map->getBlock(initialX, initialY, &block)) { + if (this->layout->getBlock(initialX, initialY, &block)) { if (selectedMetatiles.length() == 1 && selectedMetatiles.at(0).metatileId == block.metatileId()) { return; } - Blockdata oldMetatiles = !fromScriptCall ? map->layout->blockdata : Blockdata(); + Blockdata oldMetatiles = !fromScriptCall ? this->layout->blockdata : Blockdata(); bool setCollisions = selectedCollisions.length() == selectedMetatiles.length(); uint16_t metatileId = block.metatileId(); - for (int y = 0; y < map->getHeight(); y++) { - for (int x = 0; x < map->getWidth(); x++) { - if (map->getBlock(x, y, &block) && block.metatileId() == metatileId) { + for (int y = 0; y < this->layout->getHeight(); y++) { + for (int x = 0; x < this->layout->getWidth(); x++) { + if (this->layout->getBlock(x, y, &block) && block.metatileId() == metatileId) { int xDiff = x - initialX; int yDiff = y - initialY; int i = xDiff % selectionDimensions.x(); @@ -438,30 +438,30 @@ void MapPixmapItem::magicFill( block.setCollision(item.collision); block.setElevation(item.elevation); } - map->setBlock(x, y, block, !fromScriptCall); + this->layout->setBlock(x, y, block, !fromScriptCall); } } } } - if (!fromScriptCall && map->layout->blockdata != oldMetatiles) { - map->editHistory.push(new MagicFillMetatile(map, oldMetatiles, map->layout->blockdata, actionId_)); + if (!fromScriptCall && this->layout->blockdata != oldMetatiles) { + this->layout->editHistory.push(new MagicFillMetatile(this->layout, oldMetatiles, this->layout->blockdata, actionId_)); } } } -void MapPixmapItem::floodFill(int initialX, int initialY, bool fromScriptCall) { +void LayoutPixmapItem::floodFill(int initialX, int initialY, bool fromScriptCall) { MetatileSelection selection = this->metatileSelector->getMetatileSelection(); this->floodFill(initialX, initialY, selection.dimensions, selection.metatileItems, selection.collisionItems, fromScriptCall); } -void MapPixmapItem::floodFill(int initialX, int initialY, uint16_t metatileId, bool fromScriptCall) { +void LayoutPixmapItem::floodFill(int initialX, int initialY, uint16_t metatileId, bool fromScriptCall) { QPoint selectionDimensions(1, 1); QList selectedMetatiles = QList({MetatileSelectionItem{true, metatileId}}); this->floodFill(initialX, initialY, selectionDimensions, selectedMetatiles, QList(), fromScriptCall); } -void MapPixmapItem::floodFill( +void LayoutPixmapItem::floodFill( int initialX, int initialY, QPoint selectionDimensions, @@ -469,7 +469,7 @@ void MapPixmapItem::floodFill( QList selectedCollisions, bool fromScriptCall) { bool setCollisions = selectedCollisions.length() == selectedMetatiles.length(); - Blockdata oldMetatiles = !fromScriptCall ? map->layout->blockdata : Blockdata(); + Blockdata oldMetatiles = !fromScriptCall ? this->layout->blockdata : Blockdata(); QSet visited; QList todo; @@ -479,11 +479,11 @@ void MapPixmapItem::floodFill( int x = point.x(); int y = point.y(); Block block; - if (!map->getBlock(x, y, &block)) { + if (!this->layout->getBlock(x, y, &block)) { continue; } - visited.insert(x + y * map->getWidth()); + visited.insert(x + y * this->layout->getWidth()); int xDiff = x - initialX; int yDiff = y - initialY; int i = xDiff % selectionDimensions.x(); @@ -500,32 +500,32 @@ void MapPixmapItem::floodFill( block.setCollision(item.collision); block.setElevation(item.elevation); } - map->setBlock(x, y, block, !fromScriptCall); + this->layout->setBlock(x, y, block, !fromScriptCall); } - if (!visited.contains(x + 1 + y * map->getWidth()) && map->getBlock(x + 1, y, &block) && block.metatileId() == old_metatileId) { + if (!visited.contains(x + 1 + y * this->layout->getWidth()) && this->layout->getBlock(x + 1, y, &block) && block.metatileId() == old_metatileId) { todo.append(QPoint(x + 1, y)); - visited.insert(x + 1 + y * map->getWidth()); + visited.insert(x + 1 + y * this->layout->getWidth()); } - if (!visited.contains(x - 1 + y * map->getWidth()) && map->getBlock(x - 1, y, &block) && block.metatileId() == old_metatileId) { + if (!visited.contains(x - 1 + y * this->layout->getWidth()) && this->layout->getBlock(x - 1, y, &block) && block.metatileId() == old_metatileId) { todo.append(QPoint(x - 1, y)); - visited.insert(x - 1 + y * map->getWidth()); + visited.insert(x - 1 + y * this->layout->getWidth()); } - if (!visited.contains(x + (y + 1) * map->getWidth()) && map->getBlock(x, y + 1, &block) && block.metatileId() == old_metatileId) { + if (!visited.contains(x + (y + 1) * this->layout->getWidth()) && this->layout->getBlock(x, y + 1, &block) && block.metatileId() == old_metatileId) { todo.append(QPoint(x, y + 1)); - visited.insert(x + (y + 1) * map->getWidth()); + visited.insert(x + (y + 1) * this->layout->getWidth()); } - if (!visited.contains(x + (y - 1) * map->getWidth()) && map->getBlock(x, y - 1, &block) && block.metatileId() == old_metatileId) { + if (!visited.contains(x + (y - 1) * this->layout->getWidth()) && this->layout->getBlock(x, y - 1, &block) && block.metatileId() == old_metatileId) { todo.append(QPoint(x, y - 1)); - visited.insert(x + (y - 1) * map->getWidth()); + visited.insert(x + (y - 1) * this->layout->getWidth()); } } - if (!fromScriptCall && map->layout->blockdata != oldMetatiles) { - map->editHistory.push(new BucketFillMetatile(map, oldMetatiles, map->layout->blockdata, actionId_)); + if (!fromScriptCall && this->layout->blockdata != oldMetatiles) { + this->layout->editHistory.push(new BucketFillMetatile(this->layout, oldMetatiles, this->layout->blockdata, actionId_)); } } -void MapPixmapItem::floodFillSmartPath(int initialX, int initialY, bool fromScriptCall) { +void LayoutPixmapItem::floodFillSmartPath(int initialX, int initialY, bool fromScriptCall) { MetatileSelection selection = this->metatileSelector->getMetatileSelection(); if (!isValidSmartPathSelection(selection)) return; @@ -542,7 +542,7 @@ void MapPixmapItem::floodFillSmartPath(int initialX, int initialY, bool fromScri setCollisions = true; } - Blockdata oldMetatiles = !fromScriptCall ? map->layout->blockdata : Blockdata(); + Blockdata oldMetatiles = !fromScriptCall ? this->layout->blockdata : Blockdata(); // Flood fill the region with the open tile. QList todo; @@ -552,7 +552,7 @@ void MapPixmapItem::floodFillSmartPath(int initialX, int initialY, bool fromScri int x = point.x(); int y = point.y(); Block block; - if (!map->getBlock(x, y, &block)) { + if (!this->layout->getBlock(x, y, &block)) { continue; } @@ -566,17 +566,17 @@ void MapPixmapItem::floodFillSmartPath(int initialX, int initialY, bool fromScri block.setCollision(openCollision); block.setElevation(openElevation); } - map->setBlock(x, y, block, !fromScriptCall); - if (map->getBlock(x + 1, y, &block) && block.metatileId() == old_metatileId) { + this->layout->setBlock(x, y, block, !fromScriptCall); + if (this->layout->getBlock(x + 1, y, &block) && block.metatileId() == old_metatileId) { todo.append(QPoint(x + 1, y)); } - if (map->getBlock(x - 1, y, &block) && block.metatileId() == old_metatileId) { + if (this->layout->getBlock(x - 1, y, &block) && block.metatileId() == old_metatileId) { todo.append(QPoint(x - 1, y)); } - if (map->getBlock(x, y + 1, &block) && block.metatileId() == old_metatileId) { + if (this->layout->getBlock(x, y + 1, &block) && block.metatileId() == old_metatileId) { todo.append(QPoint(x, y + 1)); } - if (map->getBlock(x, y - 1, &block) && block.metatileId() == old_metatileId) { + if (this->layout->getBlock(x, y - 1, &block) && block.metatileId() == old_metatileId) { todo.append(QPoint(x, y - 1)); } } @@ -590,11 +590,11 @@ void MapPixmapItem::floodFillSmartPath(int initialX, int initialY, bool fromScri int x = point.x(); int y = point.y(); Block block; - if (!map->getBlock(x, y, &block)) { + if (!this->layout->getBlock(x, y, &block)) { continue; } - visited.insert(x + y * map->getWidth()); + visited.insert(x + y * this->layout->getWidth()); int id = 0; Block top; Block right; @@ -602,13 +602,13 @@ void MapPixmapItem::floodFillSmartPath(int initialX, int initialY, bool fromScri Block left; // Get marching squares value, to determine which tile to use. - if (map->getBlock(x, y - 1, &top) && isSmartPathTile(selection.metatileItems, top.metatileId())) + if (this->layout->getBlock(x, y - 1, &top) && isSmartPathTile(selection.metatileItems, top.metatileId())) id += 1; - if (map->getBlock(x + 1, y, &right) && isSmartPathTile(selection.metatileItems, right.metatileId())) + if (this->layout->getBlock(x + 1, y, &right) && isSmartPathTile(selection.metatileItems, right.metatileId())) id += 2; - if (map->getBlock(x, y + 1, &bottom) && isSmartPathTile(selection.metatileItems, bottom.metatileId())) + if (this->layout->getBlock(x, y + 1, &bottom) && isSmartPathTile(selection.metatileItems, bottom.metatileId())) id += 4; - if (map->getBlock(x - 1, y, &left) && isSmartPathTile(selection.metatileItems, left.metatileId())) + if (this->layout->getBlock(x - 1, y, &left) && isSmartPathTile(selection.metatileItems, left.metatileId())) id += 8; block.setMetatileId(selection.metatileItems.at(smartPathTable[id]).metatileId); @@ -617,41 +617,41 @@ void MapPixmapItem::floodFillSmartPath(int initialX, int initialY, bool fromScri block.setCollision(item.collision); block.setElevation(item.elevation); } - map->setBlock(x, y, block, !fromScriptCall); + this->layout->setBlock(x, y, block, !fromScriptCall); // Visit neighbors if they are smart-path tiles, and don't revisit any. - if (!visited.contains(x + 1 + y * map->getWidth()) && map->getBlock(x + 1, y, &block) && isSmartPathTile(selection.metatileItems, block.metatileId())) { + if (!visited.contains(x + 1 + y * this->layout->getWidth()) && this->layout->getBlock(x + 1, y, &block) && isSmartPathTile(selection.metatileItems, block.metatileId())) { todo.append(QPoint(x + 1, y)); - visited.insert(x + 1 + y * map->getWidth()); + visited.insert(x + 1 + y * this->layout->getWidth()); } - if (!visited.contains(x - 1 + y * map->getWidth()) && map->getBlock(x - 1, y, &block) && isSmartPathTile(selection.metatileItems, block.metatileId())) { + if (!visited.contains(x - 1 + y * this->layout->getWidth()) && this->layout->getBlock(x - 1, y, &block) && isSmartPathTile(selection.metatileItems, block.metatileId())) { todo.append(QPoint(x - 1, y)); - visited.insert(x - 1 + y * map->getWidth()); + visited.insert(x - 1 + y * this->layout->getWidth()); } - if (!visited.contains(x + (y + 1) * map->getWidth()) && map->getBlock(x, y + 1, &block) && isSmartPathTile(selection.metatileItems, block.metatileId())) { + if (!visited.contains(x + (y + 1) * this->layout->getWidth()) && this->layout->getBlock(x, y + 1, &block) && isSmartPathTile(selection.metatileItems, block.metatileId())) { todo.append(QPoint(x, y + 1)); - visited.insert(x + (y + 1) * map->getWidth()); + visited.insert(x + (y + 1) * this->layout->getWidth()); } - if (!visited.contains(x + (y - 1) * map->getWidth()) && map->getBlock(x, y - 1, &block) && isSmartPathTile(selection.metatileItems, block.metatileId())) { + if (!visited.contains(x + (y - 1) * this->layout->getWidth()) && this->layout->getBlock(x, y - 1, &block) && isSmartPathTile(selection.metatileItems, block.metatileId())) { todo.append(QPoint(x, y - 1)); - visited.insert(x + (y - 1) * map->getWidth()); + visited.insert(x + (y - 1) * this->layout->getWidth()); } } - if (!fromScriptCall && map->layout->blockdata != oldMetatiles) { - map->editHistory.push(new BucketFillMetatile(map, oldMetatiles, map->layout->blockdata, actionId_)); + if (!fromScriptCall && this->layout->blockdata != oldMetatiles) { + this->layout->editHistory.push(new BucketFillMetatile(this->layout, oldMetatiles, this->layout->blockdata, actionId_)); } } -void MapPixmapItem::pick(QGraphicsSceneMouseEvent *event) { +void LayoutPixmapItem::pick(QGraphicsSceneMouseEvent *event) { QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); Block block; - if (map->getBlock(pos.x(), pos.y(), &block)) { + if (this->layout->getBlock(pos.x(), pos.y(), &block)) { this->metatileSelector->selectFromMap(block.metatileId(), block.collision(), block.elevation()); } } -void MapPixmapItem::select(QGraphicsSceneMouseEvent *event) { +void LayoutPixmapItem::select(QGraphicsSceneMouseEvent *event) { QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); if (event->type() == QEvent::GraphicsSceneMousePress) { selection_origin = QPoint(pos.x(), pos.y()); @@ -681,43 +681,47 @@ void MapPixmapItem::select(QGraphicsSceneMouseEvent *event) { } } -void MapPixmapItem::draw(bool ignoreCache) { - if (map) { - map->setMapItem(this); - setPixmap(map->render(ignoreCache)); +void LayoutPixmapItem::draw(bool ignoreCache) { + if (this->layout) { + layout->setLayoutItem(this); + setPixmap(this->layout->render(ignoreCache)); } } -void MapPixmapItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { +void LayoutPixmapItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); if (pos != this->metatilePos) { this->metatilePos = pos; emit this->hoveredMapMetatileChanged(pos); } - if (this->settings->betterCursors && this->paintingMode != MapPixmapItem::PaintMode::Disabled) { + if (this->settings->betterCursors && this->editsEnabled) { setCursor(this->settings->mapCursor); } } -void MapPixmapItem::hoverEnterEvent(QGraphicsSceneHoverEvent * event) { + +void LayoutPixmapItem::hoverEnterEvent(QGraphicsSceneHoverEvent * event) { this->has_mouse = true; QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); emit this->hoveredMapMetatileChanged(pos); } -void MapPixmapItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) { + +void LayoutPixmapItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) { emit this->hoveredMapMetatileCleared(); - if (this->settings->betterCursors && this->paintingMode != MapPixmapItem::PaintMode::Disabled) { + if (this->settings->betterCursors && this->editsEnabled) { unsetCursor(); } this->has_mouse = false; } -void MapPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { + +void LayoutPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); this->paint_tile_initial_x = this->straight_path_initial_x = pos.x(); this->paint_tile_initial_y = this->straight_path_initial_y = pos.y(); emit startPaint(event, this); emit mouseEvent(event, this); } -void MapPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { + +void LayoutPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); if (pos != this->metatilePos) { this->metatilePos = pos; @@ -725,8 +729,9 @@ void MapPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { } emit mouseEvent(event, this); } -void MapPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { - this->lockedAxis = MapPixmapItem::Axis::None; + +void LayoutPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { + this->lockedAxis = LayoutPixmapItem::Axis::None; emit endPaint(event, this); emit mouseEvent(event, this); } diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 65811474c..a7a266295 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -2,8 +2,8 @@ #include "ui_mapimageexporter.h" #include "qgifimage.h" #include "editcommands.h" +#include "filedialog.h" -#include #include #include #include @@ -23,6 +23,19 @@ QString getTitle(ImageExporterMode mode) { return ""; } +QString getDescription(ImageExporterMode mode) { + switch (mode) + { + case ImageExporterMode::Normal: + return "Exports an image of the selected map."; + case ImageExporterMode::Stitch: + return "Exports a combined image of all the maps connected to the selected map."; + case ImageExporterMode::Timelapse: + return "Exports a GIF of the edit history for the selected map."; + } + return ""; +} + MapImageExporter::MapImageExporter(QWidget *parent_, Editor *editor_, ImageExporterMode mode) : QDialog(parent_), ui(new Ui::MapImageExporter) @@ -30,17 +43,22 @@ MapImageExporter::MapImageExporter(QWidget *parent_, Editor *editor_, ImageExpor this->setAttribute(Qt::WA_DeleteOnClose); ui->setupUi(this); this->map = editor_->map; + this->layout = editor_->layout; this->editor = editor_; this->mode = mode; this->setWindowTitle(getTitle(this->mode)); + this->ui->label_Description->setText(getDescription(this->mode)); this->ui->groupBox_Connections->setVisible(this->mode != ImageExporterMode::Stitch); this->ui->groupBox_Timelapse->setVisible(this->mode == ImageExporterMode::Timelapse); - this->ui->comboBox_MapSelection->addItems(editor->project->mapNames); - this->ui->comboBox_MapSelection->setCurrentText(map->name); - this->ui->comboBox_MapSelection->setEnabled(false);// TODO: allow selecting map from drop-down + if (this->map) { + this->ui->comboBox_MapSelection->addItems(editor->project->mapNames); + this->ui->comboBox_MapSelection->setCurrentText(map->name); + this->ui->comboBox_MapSelection->setEnabled(false);// TODO: allow selecting map from drop-down + } - updatePreview(); + connect(ui->pushButton_Save, &QPushButton::pressed, this, &MapImageExporter::saveImage); + connect(ui->pushButton_Cancel, &QPushButton::pressed, this, &MapImageExporter::close); } MapImageExporter::~MapImageExporter() { @@ -48,129 +66,148 @@ MapImageExporter::~MapImageExporter() { delete ui; } +// Allow the window to open before displaying the preview. +void MapImageExporter::showEvent(QShowEvent *event) { + QWidget::showEvent(event); + if (!event->spontaneous()) + QTimer::singleShot(0, this, &MapImageExporter::updatePreview); +} + +void MapImageExporter::resizeEvent(QResizeEvent *event) { + QDialog::resizeEvent(event); + scalePreview(); +} + void MapImageExporter::saveImage() { + // Make sure preview is up-to-date before we save. + if (this->preview.isNull()) + updatePreview(); + if (this->preview.isNull()) + return; + QString title = getTitle(this->mode); QString defaultFilename; switch (this->mode) { case ImageExporterMode::Normal: - defaultFilename = map->name; + defaultFilename = this->map? this->map->name : this->layout->name; break; case ImageExporterMode::Stitch: - defaultFilename = QString("Stitch_From_%1").arg(map->name); + defaultFilename = QString("Stitch_From_%1").arg(this->map? this->map->name : this->layout->name); break; case ImageExporterMode::Timelapse: - defaultFilename = QString("Timelapse_%1").arg(map->name); + defaultFilename = QString("Timelapse_%1").arg(this->map? this->map->name : this->layout->name); break; } QString defaultFilepath = QString("%1/%2.%3") - .arg(editor->project->importExportPath) + .arg(FileDialog::getDirectory()) .arg(defaultFilename) .arg(this->mode == ImageExporterMode::Timelapse ? "gif" : "png"); QString filter = this->mode == ImageExporterMode::Timelapse ? "Image Files (*.gif)" : "Image Files (*.png *.jpg *.bmp)"; - QString filepath = QFileDialog::getSaveFileName(this, title, defaultFilepath, filter); + QString filepath = FileDialog::getSaveFileName(this, title, defaultFilepath, filter); if (!filepath.isEmpty()) { - editor->project->setImportExportPath(filepath); switch (this->mode) { case ImageExporterMode::Normal: + case ImageExporterMode::Stitch: + // Normal and Stitch modes already have the image ready to go in the preview. this->preview.save(filepath); break; - case ImageExporterMode::Stitch: { - QProgressDialog progress("Building map stitch...", "Cancel", 0, 1, this); - progress.setAutoClose(true); - progress.setWindowModality(Qt::WindowModal); - progress.setModal(true); - QPixmap pixmap = this->getStitchedImage(&progress, this->showBorder); - if (progress.wasCanceled()) { - progress.close(); - return; - } - pixmap.save(filepath); - progress.close(); - break; - } case ImageExporterMode::Timelapse: - QProgressDialog progress("Building map timelapse...", "Cancel", 0, 1, this); - progress.setAutoClose(true); - progress.setWindowModality(Qt::WindowModal); - progress.setModal(true); - progress.setMaximum(1); - progress.setValue(0); - - int maxWidth = this->map->getWidth() * 16; - int maxHeight = this->map->getHeight() * 16; - if (showBorder) { - maxWidth += 2 * STITCH_MODE_BORDER_DISTANCE * 16; - maxHeight += 2 * STITCH_MODE_BORDER_DISTANCE * 16; - } - // Rewind to the specified start of the map edit history. - int i = 0; - while (this->map->editHistory.canUndo()) { - progress.setValue(i); - this->map->editHistory.undo(); - int width = this->map->getWidth() * 16; - int height = this->map->getHeight() * 16; - if (showBorder) { - width += 2 * STITCH_MODE_BORDER_DISTANCE * 16; - height += 2 * STITCH_MODE_BORDER_DISTANCE * 16; - } - if (width > maxWidth) { - maxWidth = width; - } - if (height > maxHeight) { - maxHeight = height; - } - i++; - } - QGifImage timelapseImg(QSize(maxWidth, maxHeight)); - timelapseImg.setDefaultDelay(timelapseDelayMs); + // Timelapse will play in order of layout changes then map changes (events) + // TODO: potentially update in the future? + QGifImage timelapseImg; + timelapseImg.setDefaultDelay(this->settings.timelapseDelayMs); timelapseImg.setDefaultTransparentColor(QColor(0, 0, 0)); - // Draw each frame, skpping the specified number of map edits in - // the undo history. - progress.setMaximum(i); - while (i > 0) { - if (progress.wasCanceled()) { - progress.close(); - while (i > 0 && this->map->editHistory.canRedo()) { - i--; - this->map->editHistory.redo(); - } - return; - } - while (this->map->editHistory.canRedo() && - !historyItemAppliesToFrame(this->map->editHistory.command(this->map->editHistory.index()))) { - i--; - this->map->editHistory.redo(); + + // lambda to avoid redundancy + auto generateTimelapseFromHistory = [this, &timelapseImg](QString progressText, QUndoStack &historyStack){ + QProgressDialog progress(progressText, "Cancel", 0, 1, this); + progress.setAutoClose(true); + progress.setWindowModality(Qt::WindowModal); + progress.setModal(true); + progress.setMaximum(1); + progress.setValue(0); + + int maxWidth = this->layout->getWidth() * 16; + int maxHeight = this->layout->getHeight() * 16; + if (this->settings.showBorder) { + maxWidth += 2 * STITCH_MODE_BORDER_DISTANCE * 16; + maxHeight += 2 * STITCH_MODE_BORDER_DISTANCE * 16; } - progress.setValue(progress.maximum() - i); - QPixmap pixmap = this->getFormattedMapPixmap(this->map); - if (pixmap.width() < maxWidth || pixmap.height() < maxHeight) { - QPixmap pixmap2 = QPixmap(maxWidth, maxHeight); - QPainter painter(&pixmap2); - pixmap2.fill(QColor(0, 0, 0)); - painter.drawPixmap(0, 0, pixmap.width(), pixmap.height(), pixmap); - painter.end(); - pixmap = pixmap2; + // Rewind to the specified start of the map edit history. + int i = 0; + while (historyStack.canUndo()) { + progress.setValue(i); + historyStack.undo(); + int width = this->layout->getWidth() * 16; + int height = this->layout->getHeight() * 16; + if (this->settings.showBorder) { + width += 2 * STITCH_MODE_BORDER_DISTANCE * 16; + height += 2 * STITCH_MODE_BORDER_DISTANCE * 16; + } + if (width > maxWidth) { + maxWidth = width; + } + if (height > maxHeight) { + maxHeight = height; + } + i++; } - timelapseImg.addFrame(pixmap.toImage()); - for (int j = 0; j < timelapseSkipAmount; j++) { - if (i > 0) { + + // Draw each frame, skpping the specified number of map edits in + // the undo history. + progress.setMaximum(i); + while (i > 0) { + if (progress.wasCanceled()) { + progress.close(); + while (i > 0 && historyStack.canRedo()) { + i--; + historyStack.redo(); + } + return; + } + while (historyStack.canRedo() && + !historyItemAppliesToFrame(historyStack.command(historyStack.index()))) { i--; - this->map->editHistory.redo(); - while (this->map->editHistory.canRedo() && - !historyItemAppliesToFrame(this->map->editHistory.command(this->map->editHistory.index()))) { + historyStack.redo(); + } + progress.setValue(progress.maximum() - i); + QPixmap pixmap = this->getFormattedMapPixmap(this->map); + if (pixmap.width() < maxWidth || pixmap.height() < maxHeight) { + QPixmap pixmap2 = QPixmap(maxWidth, maxHeight); + QPainter painter(&pixmap2); + pixmap2.fill(QColor(0, 0, 0)); + painter.drawPixmap(0, 0, pixmap.width(), pixmap.height(), pixmap); + painter.end(); + pixmap = pixmap2; + } + timelapseImg.addFrame(pixmap.toImage()); + for (int j = 0; j < this->settings.timelapseSkipAmount; j++) { + if (i > 0) { i--; - this->map->editHistory.redo(); + historyStack.redo(); + while (historyStack.canRedo() && + !historyItemAppliesToFrame(historyStack.command(historyStack.index()))) { + i--; + historyStack.redo(); + } } } } - } - // The latest map state is the last animated frame. - QPixmap pixmap = this->getFormattedMapPixmap(this->map); - timelapseImg.addFrame(pixmap.toImage()); + // The latest map state is the last animated frame. + QPixmap pixmap = this->getFormattedMapPixmap(this->map); + timelapseImg.addFrame(pixmap.toImage()); + progress.close(); + }; + + if (this->layout) + generateTimelapseFromHistory("Building layout timelapse...", this->layout->editHistory); + + if (this->map) + generateTimelapseFromHistory("Building map timelapse...", this->map->editHistory); + timelapseImg.save(filepath); - progress.close(); break; } this->close(); @@ -186,32 +223,32 @@ bool MapImageExporter::historyItemAppliesToFrame(const QUndoCommand *command) { case CommandId::ID_BucketFillMetatile: case CommandId::ID_MagicFillMetatile: case CommandId::ID_ShiftMetatiles: - case CommandId::ID_ResizeMap: - case CommandId::ID_ScriptEditMap: + case CommandId::ID_ResizeLayout: + case CommandId::ID_ScriptEditLayout: return true; case CommandId::ID_PaintCollision: case CommandId::ID_BucketFillCollision: case CommandId::ID_MagicFillCollision: - return this->showCollision; + return this->settings.showCollision; case CommandId::ID_PaintBorder: - return this->showBorder; + return this->settings.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; + return this->settings.showUpConnections || this->settings.showDownConnections || this->settings.showLeftConnections || this->settings.showRightConnections; case CommandId::ID_EventMove: case CommandId::ID_EventShift: case CommandId::ID_EventCreate: case CommandId::ID_EventDelete: case CommandId::ID_EventDuplicate: { bool eventTypeIsApplicable = - (this->showObjects && (command->id() & IDMask_EventType_Object) != 0) - || (this->showWarps && (command->id() & IDMask_EventType_Warp) != 0) - || (this->showBGs && (command->id() & IDMask_EventType_BG) != 0) - || (this->showTriggers && (command->id() & IDMask_EventType_Trigger) != 0) - || (this->showHealSpots && (command->id() & IDMask_EventType_Heal) != 0); + (this->settings.showObjects && (command->id() & IDMask_EventType_Object) != 0) + || (this->settings.showWarps && (command->id() & IDMask_EventType_Warp) != 0) + || (this->settings.showBGs && (command->id() & IDMask_EventType_BG) != 0) + || (this->settings.showTriggers && (command->id() & IDMask_EventType_Trigger) != 0) + || (this->settings.showHealLocations && (command->id() & IDMask_EventType_Heal) != 0); return eventTypeIsApplicable; } default: @@ -309,6 +346,7 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress, bool inclu progress->setMaximum(stitchedMaps.size()); int numDrawn = 0; QPixmap stitchedPixmap((maxX - minX) * 16, (maxY - minY) * 16); + stitchedPixmap.fill(Qt::black); QPainter painter(&stitchedPixmap); for (StitchedMap map : stitchedMaps) { if (progress->wasCanceled()) { @@ -353,51 +391,75 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress, bool inclu } void MapImageExporter::updatePreview() { - if (scene) { - delete scene; - scene = nullptr; + if (this->scene) { + delete this->scene; + this->scene = nullptr; } + this->scene = new QGraphicsScene; + + if (this->mode == ImageExporterMode::Stitch) { + QProgressDialog progress("Building map stitch...", "Cancel", 0, 1, this); + progress.setAutoClose(true); + progress.setWindowModality(Qt::WindowModal); + progress.setModal(true); + progress.setMinimumDuration(1000); + this->preview = getStitchedImage(&progress, this->settings.showBorder); + progress.close(); + } else { + // Timelapse mode doesn't currently have a real preview. It just displays the current map as in Normal mode. + this->preview = getFormattedMapPixmap(this->map); + } + this->scene->addPixmap(this->preview); + ui->graphicsView_Preview->setScene(scene); + scalePreview(); +} - preview = getFormattedMapPixmap(this->map); - scene = new QGraphicsScene; - scene->addPixmap(preview); - this->scene->setSceneRect(this->scene->itemsBoundingRect()); - - this->ui->graphicsView_Preview->setScene(scene); - this->ui->graphicsView_Preview->setFixedSize(scene->itemsBoundingRect().width() + 2, - scene->itemsBoundingRect().height() + 2); +void MapImageExporter::scalePreview() { + if (this->scene && !this->settings.previewActualSize){ + ui->graphicsView_Preview->fitInView(this->scene->sceneRect(), Qt::KeepAspectRatioByExpanding); + } } +// THIS QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { QPixmap pixmap; + Layout *layout; + // draw background layer / base image - map->render(true); - pixmap = map->pixmap; + if (!this->map) { + layout = this->layout; + layout->render(true); + pixmap = layout->pixmap; + } else { + layout = map->layout; + map->layout->render(true); + pixmap = map->layout->pixmap; + } - if (showCollision) { + if (this->settings.showCollision) { QPainter collisionPainter(&pixmap); - map->renderCollision(true); + layout->renderCollision(true); collisionPainter.setOpacity(editor->collisionOpacity); - collisionPainter.drawPixmap(0, 0, map->collision_pixmap); + collisionPainter.drawPixmap(0, 0, layout->collision_pixmap); collisionPainter.end(); } // draw map border // note: this will break when allowing map to be selected from drop down maybe int borderHeight = 0, borderWidth = 0; - if (!ignoreBorder && this->showBorder) { + if (!ignoreBorder && this->settings.showBorder) { int borderDistance = this->mode ? STITCH_MODE_BORDER_DISTANCE : BORDER_DISTANCE; - map->renderBorder(); - int borderHorzDist = editor->getBorderDrawDistance(map->getBorderWidth()); - int borderVertDist = editor->getBorderDrawDistance(map->getBorderHeight()); + layout->renderBorder(); + int borderHorzDist = editor->getBorderDrawDistance(layout->getBorderWidth()); + int borderVertDist = editor->getBorderDrawDistance(layout->getBorderHeight()); borderWidth = borderDistance * 16; borderHeight = borderDistance * 16; - QPixmap newPixmap = QPixmap(map->pixmap.width() + borderWidth * 2, map->pixmap.height() + borderHeight * 2); + QPixmap newPixmap = QPixmap(layout->pixmap.width() + borderWidth * 2, layout->pixmap.height() + borderHeight * 2); QPainter borderPainter(&newPixmap); - for (int y = borderDistance - borderVertDist; y < map->getHeight() + borderVertDist * 2; y += map->getBorderHeight()) { - for (int x = borderDistance - borderHorzDist; x < map->getWidth() + borderHorzDist * 2; x += map->getBorderWidth()) { - borderPainter.drawPixmap(x * 16, y * 16, map->layout->border_pixmap); + for (int y = borderDistance - borderVertDist; y < layout->getHeight() + borderVertDist * 2; y += layout->getBorderHeight()) { + for (int x = borderDistance - borderHorzDist; x < layout->getWidth() + borderHorzDist * 2; x += layout->getBorderWidth()) { + borderPainter.drawPixmap(x * 16, y * 16, layout->border_pixmap); } } borderPainter.drawImage(borderWidth, borderHeight, pixmap.toImage()); @@ -405,15 +467,20 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { pixmap = newPixmap; } - if (!ignoreBorder && (this->showUpConnections || this->showDownConnections || this->showLeftConnections || this->showRightConnections)) { + if (!this->map) { + return pixmap; + } + + if (!ignoreBorder && (this->settings.showUpConnections || this->settings.showDownConnections || this->settings.showLeftConnections || this->settings.showRightConnections)) { // if showing connections, draw on outside of image QPainter connectionPainter(&pixmap); + // TODO: Reading the connections from the editor and not 'map' is incorrect. for (auto connectionItem : editor->connection_items) { const QString direction = connectionItem->connection->direction(); - if ((showUpConnections && direction == "up") - || (showDownConnections && direction == "down") - || (showLeftConnections && direction == "left") - || (showRightConnections && direction == "right")) + if ((this->settings.showUpConnections && direction == "up") + || (this->settings.showDownConnections && direction == "down") + || (this->settings.showLeftConnections && direction == "left") + || (this->settings.showRightConnections && direction == "right")) connectionPainter.drawImage(connectionItem->x() + borderWidth, connectionItem->y() + borderHeight, connectionItem->connection->getPixmap().toImage()); } @@ -421,27 +488,30 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { } // draw events - QPainter eventPainter(&pixmap); - QList events = map->getAllEvents(); - int pixelOffset = 0; - if (!ignoreBorder && this->showBorder) { - pixelOffset = this->mode == ImageExporterMode::Normal ? BORDER_DISTANCE * 16 : STITCH_MODE_BORDER_DISTANCE * 16; - } - for (Event *event : events) { - editor->project->setEventPixmap(event); - Event::Group group = event->getEventGroup(); - if ((showObjects && group == Event::Group::Object) - || (showWarps && group == Event::Group::Warp) - || (showBGs && group == Event::Group::Bg) - || (showTriggers && group == Event::Group::Coord) - || (showHealSpots && group == Event::Group::Heal)) - eventPainter.drawImage(QPoint(event->getPixelX() + pixelOffset, event->getPixelY() + pixelOffset), event->getPixmap().toImage()); + if (this->settings.showObjects || this->settings.showWarps || this->settings.showBGs || this->settings.showTriggers || this->settings.showHealLocations) { + QPainter eventPainter(&pixmap); + int pixelOffset = 0; + if (!ignoreBorder && this->settings.showBorder) { + pixelOffset = this->mode == ImageExporterMode::Normal ? BORDER_DISTANCE * 16 : STITCH_MODE_BORDER_DISTANCE * 16; + } + const QList events = map->getAllEvents(); + for (const auto &event : events) { + Event::Group group = event->getEventGroup(); + if ((this->settings.showObjects && group == Event::Group::Object) + || (this->settings.showWarps && group == Event::Group::Warp) + || (this->settings.showBGs && group == Event::Group::Bg) + || (this->settings.showTriggers && group == Event::Group::Coord) + || (this->settings.showHealLocations && group == Event::Group::Heal)) { + editor->project->setEventPixmap(event); + eventPainter.drawImage(QPoint(event->getPixelX() + pixelOffset, event->getPixelY() + pixelOffset), event->getPixmap().toImage()); + } + } + eventPainter.end(); } - eventPainter.end(); // draw grid directly onto the pixmap // since the last grid lines are outside of the pixmap, add a pixel to the bottom and right - if (showGrid) { + if (this->settings.showGrid) { int addX = 1, addY = 1; if (borderHeight) addY = 0; if (borderWidth) addX = 0; @@ -464,97 +534,161 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { void MapImageExporter::updateShowBorderState() { // If any of the Connections settings are enabled then this setting is locked (it's implicitly enabled) + bool on = (this->settings.showUpConnections || this->settings.showDownConnections || this->settings.showLeftConnections || this->settings.showRightConnections); 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); - } + ui->checkBox_Border->setChecked(on); + ui->checkBox_Border->setDisabled(on); + this->settings.showBorder = on; } void MapImageExporter::on_checkBox_Elevation_stateChanged(int state) { - showCollision = (state == Qt::Checked); + this->settings.showCollision = (state == Qt::Checked); updatePreview(); } void MapImageExporter::on_checkBox_Grid_stateChanged(int state) { - showGrid = (state == Qt::Checked); + this->settings.showGrid = (state == Qt::Checked); updatePreview(); } void MapImageExporter::on_checkBox_Border_stateChanged(int state) { - showBorder = (state == Qt::Checked); + this->settings.showBorder = (state == Qt::Checked); updatePreview(); } void MapImageExporter::on_checkBox_Objects_stateChanged(int state) { - showObjects = (state == Qt::Checked); + this->settings.showObjects = (state == Qt::Checked); updatePreview(); } void MapImageExporter::on_checkBox_Warps_stateChanged(int state) { - showWarps = (state == Qt::Checked); + this->settings.showWarps = (state == Qt::Checked); updatePreview(); } void MapImageExporter::on_checkBox_BGs_stateChanged(int state) { - showBGs = (state == Qt::Checked); + this->settings.showBGs = (state == Qt::Checked); updatePreview(); } void MapImageExporter::on_checkBox_Triggers_stateChanged(int state) { - showTriggers = (state == Qt::Checked); + this->settings.showTriggers = (state == Qt::Checked); updatePreview(); } -void MapImageExporter::on_checkBox_HealSpots_stateChanged(int state) { - showHealSpots = (state == Qt::Checked); +void MapImageExporter::on_checkBox_HealLocations_stateChanged(int state) { + this->settings.showHealLocations = (state == Qt::Checked); + updatePreview(); +} + +// Shortcut setting for enabling all events +void MapImageExporter::on_checkBox_AllEvents_stateChanged(int state) { + bool on = (state == Qt::Checked); + + const QSignalBlocker b_Objects(ui->checkBox_Objects); + ui->checkBox_Objects->setChecked(on); + ui->checkBox_Objects->setDisabled(on); + this->settings.showObjects = on; + + const QSignalBlocker b_Warps(ui->checkBox_Warps); + ui->checkBox_Warps->setChecked(on); + ui->checkBox_Warps->setDisabled(on); + this->settings.showWarps = on; + + const QSignalBlocker b_BGs(ui->checkBox_BGs); + ui->checkBox_BGs->setChecked(on); + ui->checkBox_BGs->setDisabled(on); + this->settings.showBGs = on; + + const QSignalBlocker b_Triggers(ui->checkBox_Triggers); + ui->checkBox_Triggers->setChecked(on); + ui->checkBox_Triggers->setDisabled(on); + this->settings.showTriggers = on; + + const QSignalBlocker b_HealLocations(ui->checkBox_HealLocations); + ui->checkBox_HealLocations->setChecked(on); + ui->checkBox_HealLocations->setDisabled(on); + this->settings.showHealLocations = on; + updatePreview(); } void MapImageExporter::on_checkBox_ConnectionUp_stateChanged(int state) { - showUpConnections = (state == Qt::Checked); + this->settings.showUpConnections = (state == Qt::Checked); updateShowBorderState(); updatePreview(); } void MapImageExporter::on_checkBox_ConnectionDown_stateChanged(int state) { - showDownConnections = (state == Qt::Checked); + this->settings.showDownConnections = (state == Qt::Checked); updateShowBorderState(); updatePreview(); } void MapImageExporter::on_checkBox_ConnectionLeft_stateChanged(int state) { - showLeftConnections = (state == Qt::Checked); + this->settings.showLeftConnections = (state == Qt::Checked); updateShowBorderState(); updatePreview(); } void MapImageExporter::on_checkBox_ConnectionRight_stateChanged(int state) { - showRightConnections = (state == Qt::Checked); + this->settings.showRightConnections = (state == Qt::Checked); + updateShowBorderState(); + updatePreview(); +} + +// Shortcut setting for enabling all connection directions +void MapImageExporter::on_checkBox_AllConnections_stateChanged(int state) { + bool on = (state == Qt::Checked); + + const QSignalBlocker b_Up(ui->checkBox_ConnectionUp); + ui->checkBox_ConnectionUp->setChecked(on); + ui->checkBox_ConnectionUp->setDisabled(on); + this->settings.showUpConnections = on; + + const QSignalBlocker b_Down(ui->checkBox_ConnectionDown); + ui->checkBox_ConnectionDown->setChecked(on); + ui->checkBox_ConnectionDown->setDisabled(on); + this->settings.showDownConnections = on; + + const QSignalBlocker b_Left(ui->checkBox_ConnectionLeft); + ui->checkBox_ConnectionLeft->setChecked(on); + ui->checkBox_ConnectionLeft->setDisabled(on); + this->settings.showLeftConnections = on; + + const QSignalBlocker b_Right(ui->checkBox_ConnectionRight); + ui->checkBox_ConnectionRight->setChecked(on); + ui->checkBox_ConnectionRight->setDisabled(on); + this->settings.showRightConnections = on; + updateShowBorderState(); updatePreview(); } -void MapImageExporter::on_pushButton_Save_pressed() { - saveImage(); +void MapImageExporter::on_checkBox_ActualSize_stateChanged(int state) { + this->settings.previewActualSize = (state == Qt::Checked); + if (this->settings.previewActualSize) { + ui->graphicsView_Preview->resetTransform(); + } else { + scalePreview(); + } } void MapImageExporter::on_pushButton_Reset_pressed() { - for (auto widget : this->findChildren()) + this->settings = {}; + for (auto widget : this->findChildren()) { + const QSignalBlocker b(widget); // Prevent calls to updatePreview widget->setChecked(false); -} - -void MapImageExporter::on_pushButton_Cancel_pressed() { - this->close(); + } + ui->spinBox_TimelapseDelay->setValue(this->settings.timelapseDelayMs); + ui->spinBox_FrameSkip->setValue(this->settings.timelapseSkipAmount); + updatePreview(); } void MapImageExporter::on_spinBox_TimelapseDelay_valueChanged(int delayMs) { - timelapseDelayMs = delayMs; + this->settings.timelapseDelayMs = delayMs; } void MapImageExporter::on_spinBox_FrameSkip_valueChanged(int skip) { - timelapseSkipAmount = skip; + this->settings.timelapseSkipAmount = skip; } diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp new file mode 100644 index 000000000..a270736af --- /dev/null +++ b/src/ui/maplistmodels.cpp @@ -0,0 +1,692 @@ +#include "maplistmodels.h" + +#include +#include + +#include "project.h" +#include "filterchildrenproxymodel.h" + + + +void MapTree::removeSelected() { + while (!this->selectedIndexes().isEmpty()) { + QModelIndex i = this->selectedIndexes().takeLast(); + this->model()->removeRow(i.row(), i.parent()); + } +} + +void MapTree::keyPressEvent(QKeyEvent *event) { + if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace) { + // Delete selected items in the tree + auto selectionModel = this->selectionModel(); + if (!selectionModel->hasSelection()) + return; + + auto model = static_cast(this->model()); + auto sourceModel = static_cast(model->sourceModel()); + + QModelIndexList selectedIndexes = selectionModel->selectedRows(); + QList persistentIndexes; + for (const auto &index : selectedIndexes) { + persistentIndexes.append(model->mapToSource(index)); + } + for (const auto &index : persistentIndexes) { + sourceModel->removeItemAt(index); + } + } else { + QWidget::keyPressEvent(event); + } +} + +void MapListModel::removeItemAt(const QModelIndex &index) { + QStandardItem *item = this->getItem(index)->child(index.row(), index.column()); + if (!item) + return; + + const QString type = item->data(MapListUserRoles::TypeRole).toString(); + if (type == "map_name") { + // TODO: No support for deleting maps + } else { + // TODO: Because there's no support for deleting maps we can only delete empty folders + if (!item->hasChildren()) { + this->removeItem(item); + } + } +} + + + +QWidget *GroupNameDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const { + QLineEdit *editor = new QLineEdit(parent); + static const QRegularExpression expression("[A-Za-z_]+[\\w]*"); + editor->setPlaceholderText("gMapGroup_"); + editor->setValidator(new QRegularExpressionValidator(expression, parent)); + editor->setFrame(false); + return editor; +} + +void GroupNameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { + QString groupName = index.data(Qt::UserRole).toString(); + QLineEdit *le = static_cast(editor); + le->setText(groupName); +} + +void GroupNameDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { + QLineEdit *le = static_cast(editor); + QString groupName = le->text(); + model->setData(index, groupName, Qt::UserRole); +} + +void GroupNameDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &) const { + editor->setGeometry(option.rect); +} + + + +MapGroupModel::MapGroupModel(Project *project, QObject *parent) : MapListModel(parent) { + this->project = project; + this->root = this->invisibleRootItem(); + + initialize(); +} + +Qt::DropActions MapGroupModel::supportedDropActions() const { + return Qt::MoveAction; +} + +QStringList MapGroupModel::mimeTypes() const { + QStringList types; + types << "application/porymap.mapgroupmodel.map" + << "application/porymap.mapgroupmodel.group" + << "application/porymap.mapgroupmodel.source.row" + << "application/porymap.mapgroupmodel.source.column"; + return types; +} + +QMimeData *MapGroupModel::mimeData(const QModelIndexList &indexes) const { + QMimeData *mimeData = QStandardItemModel::mimeData(indexes); + QByteArray encodedData; + + QDataStream stream(&encodedData, QIODevice::WriteOnly); + + // if dropping a selection containing a group(s) and map(s), clear all selection but first group. + for (const QModelIndex &index : indexes) { + if (index.isValid() && data(index, MapListUserRoles::TypeRole).toString() == "map_group") { + QString groupName = data(index, Qt::UserRole).toString(); + stream << groupName; + mimeData->setData("application/porymap.mapgroupmodel.group", encodedData); + mimeData->setData("application/porymap.mapgroupmodel.source.row", QByteArray::number(index.row())); + return mimeData; + } + } + + for (const QModelIndex &index : indexes) { + if (index.isValid()) { + QString mapName = data(index, Qt::UserRole).toString(); + stream << mapName; + } + } + + mimeData->setData("application/porymap.mapgroupmodel.map", encodedData); + return mimeData; +} + +bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int, const QModelIndex &parentIndex) { + if (action == Qt::IgnoreAction) + return true; + + if (!parentIndex.isValid() && !data->hasFormat("application/porymap.mapgroupmodel.group")) + return false; + + int firstRow = 0; + + if (row != -1) { + firstRow = row; + } + else if (parentIndex.isValid()) { + firstRow = rowCount(parentIndex); + } + + if (data->hasFormat("application/porymap.mapgroupmodel.group")) { + if (parentIndex.row() != -1 || parentIndex.column() != -1) { + return false; + } + QByteArray encodedData = data->data("application/porymap.mapgroupmodel.group"); + QDataStream stream(&encodedData, QIODevice::ReadOnly); + QString groupName; + + while (!stream.atEnd()) { + stream >> groupName; + } + + this->insertRow(row, parentIndex); + + // copy children to new node + int sourceRow = data->data("application/porymap.mapgroupmodel.source.row").toInt(); + QModelIndex originIndex = this->index(sourceRow, 0); + QModelIndexList children; + QStringList mapsToMove; + for (int i = 0; i < this->rowCount(originIndex); ++i ) { + children << this->index( i, 0, originIndex); + mapsToMove << this->index( i, 0 , originIndex).data(Qt::UserRole).toString(); + } + + QModelIndex groupIndex = index(row, 0, parentIndex); + QStandardItem *groupItem = this->itemFromIndex(groupIndex); + createGroupItem(groupName, row, groupItem); + + for (QString mapName : mapsToMove) { + QStandardItem *mapItem = createMapItem(mapName); + groupItem->appendRow(mapItem); + } + } + else if (data->hasFormat("application/porymap.mapgroupmodel.map")) { + QByteArray encodedData = data->data("application/porymap.mapgroupmodel.map"); + QDataStream stream(&encodedData, QIODevice::ReadOnly); + QStringList droppedMaps; + int rowCount = 0; + + while (!stream.atEnd()) { + QString mapName; + stream >> mapName; + droppedMaps << mapName; + rowCount++; + } + + QStandardItem *groupItem = this->itemFromIndex(parentIndex); + if (groupItem->hasChildren()) { + this->insertRows(firstRow, rowCount, parentIndex); + for (QString mapName : droppedMaps) { + QModelIndex mapIndex = index(firstRow, 0, parentIndex); + QStandardItem *mapItem = this->itemFromIndex(mapIndex); + createMapItem(mapName, mapItem); + firstRow++; + } + } + // for whatever reason insertRows doesn't work as I expected with childless items + // so just append all the new maps instead + else { + for (QString mapName : droppedMaps) { + QStandardItem *mapItem = createMapItem(mapName); + groupItem->appendRow(mapItem); + firstRow++; + } + } + + } + + emit dragMoveCompleted(); + updateProject(); + + return true; +} + +void MapGroupModel::updateProject() { + if (!this->project) return; + + QStringList groupNames; + QMap mapGroups; + QList groupedMapNames; + QStringList mapNames; + + for (int g = 0; g < this->root->rowCount(); g++) { + QStandardItem *groupItem = this->item(g); + QString groupName = groupItem->data(Qt::UserRole).toString(); + groupNames.append(groupName); + mapGroups[groupName] = g; + QStringList mapsInGroup; + for (int m = 0; m < groupItem->rowCount(); m++) { + QStandardItem *mapItem = groupItem->child(m); + if (!mapItem) { + logError("An error occured while trying to apply updates to map group structure."); + return; + } + QString mapName = mapItem->data(Qt::UserRole).toString(); + mapsInGroup.append(mapName); + mapNames.append(mapName); + } + groupedMapNames.append(mapsInGroup); + } + + this->project->groupNames = groupNames; + this->project->mapGroups = mapGroups; + this->project->groupedMapNames = groupedMapNames; + this->project->mapNames = mapNames; + this->project->hasUnsavedDataChanges = true; +} + +QStandardItem *MapGroupModel::createGroupItem(QString groupName, int groupIndex, QStandardItem *group) { + if (!group) group = new QStandardItem; + group->setText(groupName); + group->setData(groupName, Qt::UserRole); + group->setData("map_group", MapListUserRoles::TypeRole); + group->setData(groupIndex, MapListUserRoles::GroupRole); + group->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsSelectable); + this->groupItems.insert(groupName, group); + return group; +} + +QStandardItem *MapGroupModel::createMapItem(QString mapName, QStandardItem *map) { + if (!map) map = new QStandardItem; + map->setData(mapName, Qt::UserRole); + map->setData("map_name", MapListUserRoles::TypeRole); + map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); + this->mapItems[mapName] = map; + return map; +} + +QStandardItem *MapGroupModel::insertGroupItem(QString groupName) { + QStandardItem *group = createGroupItem(groupName, this->groupItems.size()); + this->root->appendRow(group); + this->updateProject(); + return group; +} + +void MapGroupModel::removeItem(QStandardItem *item) { + this->removeRow(item->row()); + this->updateProject(); +} + +QStandardItem *MapGroupModel::insertMapItem(QString mapName, QString groupName) { + QStandardItem *group = this->groupItems[groupName]; + if (!group) { + group = insertGroupItem(groupName); + } + QStandardItem *map = createMapItem(mapName); + group->appendRow(map); + return map; +} + +void MapGroupModel::initialize() { + this->groupItems.clear(); + this->mapItems.clear(); + for (int i = 0; i < this->project->groupNames.length(); i++) { + QString group_name = this->project->groupNames.value(i); + QStandardItem *group = createGroupItem(group_name, i); + root->appendRow(group); + QStringList names = this->project->groupedMapNames.value(i); + for (int j = 0; j < names.length(); j++) { + QString map_name = names.value(j); + QStandardItem *map = createMapItem(map_name); + group->appendRow(map); + } + } +} + +QStandardItem *MapGroupModel::getItem(const QModelIndex &index) const { + if (index.isValid()) { + QStandardItem *item = static_cast(index.internalPointer()); + if (item) + return item; + } + return this->root; +} + +QModelIndex MapGroupModel::indexOf(QString mapName) const { + if (this->mapItems.contains(mapName)) { + return this->mapItems[mapName]->index(); + } + return QModelIndex(); +} + +QVariant MapGroupModel::data(const QModelIndex &index, int role) const { + if (!index.isValid()) return QVariant(); + + int row = index.row(); + int col = index.column(); + + if (role == Qt::DecorationRole) { + static QIcon mapGrayIcon = QIcon(QStringLiteral(":/icons/map_grayed.ico")); + static QIcon mapIcon = QIcon(QStringLiteral(":/icons/map.ico")); + static QIcon mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico")); + static QIcon mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico")); + + static QIcon mapFolderIcon; + static QIcon folderIcon; + static bool loaded = false; + if (!loaded) { + mapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off); + mapFolderIcon.addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On); + folderIcon.addFile(QStringLiteral(":/icons/folder_closed.ico"), QSize(), QIcon::Normal, QIcon::Off); + folderIcon.addFile(QStringLiteral(":/icons/folder.ico"), QSize(), QIcon::Normal, QIcon::On); + loaded = true; + } + + QStandardItem *item = this->getItem(index)->child(row, col); + QString type = item->data(MapListUserRoles::TypeRole).toString(); + + if (type == "map_group") { + if (!item->hasChildren()) { + return folderIcon; + } + return mapFolderIcon; + } else if (type == "map_name") { + QString mapName = item->data(Qt::UserRole).toString(); + if (mapName == this->openMap) { + return mapOpenedIcon; + } + else if (this->project->mapCache.contains(mapName)) { + if (this->project->mapCache.value(mapName)->hasUnsavedChanges()) { + return mapEditedIcon; + } + else { + return mapIcon; + } + } + return mapGrayIcon; + } + } + else if (role == Qt::DisplayRole) { + QStandardItem *item = this->getItem(index)->child(row, col); + QString type = item->data(MapListUserRoles::TypeRole).toString(); + + if (type == "map_name") { + return QString("[%1.%2] ").arg(this->getItem(index)->row()).arg(row, 2, 10, QLatin1Char('0')) + item->data(Qt::UserRole).toString(); + } + else if (type == "map_group") { + return item->data(Qt::UserRole).toString(); + } + } + + return QStandardItemModel::data(index, role); +} + +bool MapGroupModel::setData(const QModelIndex &index, const QVariant &value, int role) { + if (role == Qt::UserRole && data(index, MapListUserRoles::TypeRole).toString() == "map_group") { + // verify uniqueness of new group name + if (this->project->groupNames.contains(value.toString())) { + return false; + } + } + if (QStandardItemModel::setData(index, value, role)) { + this->updateProject(); + } + return true; +} + + + +MapAreaModel::MapAreaModel(Project *project, QObject *parent) : MapListModel(parent) { + this->project = project; + this->root = this->invisibleRootItem(); + + initialize(); +} + +QStandardItem *MapAreaModel::createAreaItem(QString mapsecName) { + QStandardItem *area = new QStandardItem; + area->setText(mapsecName); + area->setEditable(false); + area->setData(mapsecName, Qt::UserRole); + area->setData("map_section", MapListUserRoles::TypeRole); + // group->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); + this->areaItems.insert(mapsecName, area); + return area; +} + +QStandardItem *MapAreaModel::createMapItem(QString mapName, int, int) { + QStandardItem *map = new QStandardItem; + map->setText(mapName); + map->setEditable(false); + map->setData(mapName, Qt::UserRole); + map->setData("map_name", MapListUserRoles::TypeRole); + // map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); + this->mapItems.insert(mapName, map); + return map; +} + +QStandardItem *MapAreaModel::insertAreaItem(QString areaName) { + this->project->addNewMapsec(areaName); + QStandardItem *item = createAreaItem(areaName); + this->root->appendRow(item); + this->sort(0, Qt::AscendingOrder); + return item; +} + +QStandardItem *MapAreaModel::insertMapItem(QString mapName, QString areaName, int groupIndex) { + QStandardItem *area = this->areaItems[areaName]; + if (!area) { + return nullptr; + } + int mapIndex = area->rowCount(); + QStandardItem *map = createMapItem(mapName, groupIndex, mapIndex); + area->appendRow(map); + return map; +} + +void MapAreaModel::removeItem(QStandardItem *item) { + this->project->removeMapsec(item->data(Qt::UserRole).toString()); + this->removeRow(item->row()); +} + +void MapAreaModel::initialize() { + this->areaItems.clear(); + this->mapItems.clear(); + + for (const auto &idName : this->project->mapSectionIdNames) { + this->root->appendRow(createAreaItem(idName)); + } + + for (int i = 0; i < this->project->groupNames.length(); i++) { + QStringList names = this->project->groupedMapNames.value(i); + for (int j = 0; j < names.length(); j++) { + QString mapName = names.value(j); + QStandardItem *map = createMapItem(mapName, i, j); + QString mapsecName = this->project->readMapLocation(mapName); + if (this->areaItems.contains(mapsecName)) { + this->areaItems[mapsecName]->appendRow(map); + } + } + } + this->sort(0, Qt::AscendingOrder); +} + +QStandardItem *MapAreaModel::getItem(const QModelIndex &index) const { + if (index.isValid()) { + QStandardItem *item = static_cast(index.internalPointer()); + if (item) + return item; + } + return this->root; +} + +QModelIndex MapAreaModel::indexOf(QString mapName) const { + if (this->mapItems.contains(mapName)) { + return this->mapItems[mapName]->index(); + } + return QModelIndex(); +} + +QVariant MapAreaModel::data(const QModelIndex &index, int role) const { + if (!index.isValid()) return QVariant(); + + int row = index.row(); + int col = index.column(); + + if (role == Qt::DecorationRole) { + static QIcon mapGrayIcon = QIcon(QStringLiteral(":/icons/map_grayed.ico")); + static QIcon mapIcon = QIcon(QStringLiteral(":/icons/map.ico")); + static QIcon mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico")); + static QIcon mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico")); + + static QIcon mapFolderIcon; + static QIcon folderIcon; + static bool loaded = false; + if (!loaded) { + mapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off); + mapFolderIcon.addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On); + folderIcon.addFile(QStringLiteral(":/icons/folder_closed.ico"), QSize(), QIcon::Normal, QIcon::Off); + folderIcon.addFile(QStringLiteral(":/icons/folder.ico"), QSize(), QIcon::Normal, QIcon::On); + loaded = true; + } + + QStandardItem *item = this->getItem(index)->child(row, col); + QString type = item->data(MapListUserRoles::TypeRole).toString(); + + if (type == "map_section") { + if (item->hasChildren()) { + return mapFolderIcon; + } + return folderIcon; + } else if (type == "map_name") { + QString mapName = item->data(Qt::UserRole).toString(); + if (mapName == this->openMap) { + return mapOpenedIcon; + } + else if (this->project->mapCache.contains(mapName)) { + if (this->project->mapCache.value(mapName)->hasUnsavedChanges()) { + return mapEditedIcon; + } + else { + return mapIcon; + } + } + return mapGrayIcon; + } + } + else if (role == Qt::DisplayRole) { + QStandardItem *item = this->getItem(index)->child(row, col); + QString type = item->data(MapListUserRoles::TypeRole).toString(); + + if (type == "map_section") { + return item->data(Qt::UserRole).toString(); + } + } + + return QStandardItemModel::data(index, role); +} + + + +LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : MapListModel(parent) { + this->project = project; + this->root = this->invisibleRootItem(); + + initialize(); +} + +QStandardItem *LayoutTreeModel::createLayoutItem(QString layoutId) { + QStandardItem *layout = new QStandardItem; + layout->setText(this->project->layoutIdsToNames[layoutId]); + layout->setEditable(false); + layout->setData(layoutId, Qt::UserRole); + layout->setData("map_layout", MapListUserRoles::TypeRole); + // // group->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); + this->layoutItems.insert(layoutId, layout); + return layout; +} + +QStandardItem *LayoutTreeModel::createMapItem(QString mapName) { + QStandardItem *map = new QStandardItem; + map->setText(mapName); + map->setEditable(false); + map->setData(mapName, Qt::UserRole); + map->setData("map_name", MapListUserRoles::TypeRole); + map->setFlags(Qt::NoItemFlags | Qt::ItemNeverHasChildren); + this->mapItems.insert(mapName, map); + return map; +} + +QStandardItem *LayoutTreeModel::insertLayoutItem(QString layoutId) { + QStandardItem *layoutItem = this->createLayoutItem(layoutId); + this->root->appendRow(layoutItem); + this->sort(0, Qt::AscendingOrder); + return layoutItem; +} + +QStandardItem *LayoutTreeModel::insertMapItem(QString mapName, QString layoutId) { + QStandardItem *layout = nullptr; + if (this->layoutItems.contains(layoutId)) { + layout = this->layoutItems[layoutId]; + } + else { + layout = createLayoutItem(layoutId); + this->root->appendRow(layout); + } + if (!layout) { + return nullptr; + } + QStandardItem *map = createMapItem(mapName); + layout->appendRow(map); + return map; +} + +void LayoutTreeModel::removeItem(QStandardItem *) { + // TODO: Deleting layouts not supported +} + + +void LayoutTreeModel::initialize() { + this->layoutItems.clear(); + this->mapItems.clear(); + for (int i = 0; i < this->project->mapLayoutsTable.length(); i++) { + QString layoutId = project->mapLayoutsTable.value(i); + QStandardItem *layoutItem = createLayoutItem(layoutId); + this->root->appendRow(layoutItem); + } + + for (auto mapList : this->project->groupedMapNames) { + for (auto mapName : mapList) { + QString layoutId = project->readMapLayoutId(mapName); + QStandardItem *map = createMapItem(mapName); + this->layoutItems[layoutId]->appendRow(map); + } + } + this->sort(0, Qt::AscendingOrder); +} + +QStandardItem *LayoutTreeModel::getItem(const QModelIndex &index) const { + if (index.isValid()) { + QStandardItem *item = static_cast(index.internalPointer()); + if (item) + return item; + } + return this->root; +} + +QModelIndex LayoutTreeModel::indexOf(QString layoutName) const { + if (this->layoutItems.contains(layoutName)) { + return this->layoutItems[layoutName]->index(); + } + return QModelIndex(); +} + +QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const { + if (!index.isValid()) return QVariant(); + + int row = index.row(); + int col = index.column(); + + if (role == Qt::DecorationRole) { + static QIcon mapGrayIcon = QIcon(QStringLiteral(":/icons/map_grayed.ico")); + static QIcon mapIcon = QIcon(QStringLiteral(":/icons/map.ico")); + static QIcon mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico")); + static QIcon mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico")); + + QStandardItem *item = this->getItem(index)->child(row, col); + QString type = item->data(MapListUserRoles::TypeRole).toString(); + + if (type == "map_layout") { + QString layoutId = item->data(Qt::UserRole).toString(); + if (layoutId == this->openLayout) { + return mapOpenedIcon; + } + else if (this->project->mapLayouts.contains(layoutId)) { + if (this->project->mapLayouts.value(layoutId)->hasUnsavedChanges()) { + return mapEditedIcon; + } + else if (!this->project->mapLayouts[layoutId]->loaded) { + return mapGrayIcon; + } + } + return mapIcon; + } + else if (type == "map_name") { + return QVariant(); + } + + return QVariant(); + } + + return QStandardItemModel::data(index, role); +} diff --git a/src/ui/maplisttoolbar.cpp b/src/ui/maplisttoolbar.cpp new file mode 100644 index 000000000..d03aa8384 --- /dev/null +++ b/src/ui/maplisttoolbar.cpp @@ -0,0 +1,141 @@ +#include "maplisttoolbar.h" +#include "ui_maplisttoolbar.h" +#include "editor.h" + +#include + +/* + TODO: The button states for each tool bar (just the two toggleable buttons, hide empty folders and allow editing) + should be saved in the config. This will be cleaner/easier once the config is JSON, so holding off on that for now. +*/ + +MapListToolBar::MapListToolBar(QWidget *parent) + : QFrame(parent) + , ui(new Ui::MapListToolBar) +{ + ui->setupUi(this); + + ui->button_ToggleEmptyFolders->setChecked(!m_emptyFoldersVisible); + ui->button_ToggleEdit->setChecked(m_editsAllowed); + + connect(ui->button_AddFolder, &QAbstractButton::clicked, this, &MapListToolBar::addFolderClicked); // TODO: Tool tip + connect(ui->button_ExpandAll, &QAbstractButton::clicked, this, &MapListToolBar::expandList); + connect(ui->button_CollapseAll, &QAbstractButton::clicked, this, &MapListToolBar::collapseList); + connect(ui->button_ToggleEdit, &QAbstractButton::clicked, this, &MapListToolBar::toggleEditsAllowed); + connect(ui->lineEdit_filterBox, &QLineEdit::textChanged, this, &MapListToolBar::applyFilter); + connect(ui->button_ToggleEmptyFolders, &QAbstractButton::clicked, [this] { + toggleEmptyFolders(); + + // Display message to let user know what just happened (if there are no empty folders visible it's not obvious). + const QString message = QString("%1 empty folders!").arg(m_emptyFoldersVisible ? "Showing" : "Hiding"); + QToolTip::showText(ui->button_ToggleEmptyFolders->mapToGlobal(QPoint(0, 0)), message); + }); +} + +MapListToolBar::~MapListToolBar() +{ + delete ui; +} + +void MapListToolBar::setList(MapTree *list) { + m_list = list; + + // Sync list with current settings + setEditsAllowed(m_editsAllowed); + setEmptyFoldersVisible(m_emptyFoldersVisible); +} + +void MapListToolBar::setEditsAllowedButtonVisible(bool visible) { + ui->button_ToggleEdit->setVisible(visible); +} + +void MapListToolBar::toggleEditsAllowed() { + if (m_list) { + m_list->clearSelection(); + } + setEditsAllowed(!m_editsAllowed); +} + +void MapListToolBar::setEditsAllowed(bool allowed) { + m_editsAllowed = allowed; + + const QSignalBlocker b(ui->button_ToggleEdit); + ui->button_ToggleEdit->setChecked(allowed); + + if (!m_list) + return; + + if (allowed) { + m_list->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_list->setDragEnabled(true); + m_list->setAcceptDrops(true); + m_list->setDropIndicatorShown(true); + m_list->setDragDropMode(QAbstractItemView::InternalMove); + m_list->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed); + } else { + m_list->setSelectionMode(QAbstractItemView::NoSelection); + m_list->setDragEnabled(false); + m_list->setAcceptDrops(false); + m_list->setDropIndicatorShown(false); + m_list->setDragDropMode(QAbstractItemView::NoDragDrop); + m_list->setEditTriggers(QAbstractItemView::NoEditTriggers); + } +} + +void MapListToolBar::toggleEmptyFolders() { + setEmptyFoldersVisible(!m_emptyFoldersVisible); +} + +void MapListToolBar::setEmptyFoldersVisible(bool visible) { + m_emptyFoldersVisible = visible; + + if (m_list) { + auto model = static_cast(m_list->model()); + if (model) { + model->setHideEmpty(!visible); + model->setFilterRegularExpression(ui->lineEdit_filterBox->text()); + } + } + + // Update tool tip to reflect what will happen if the button is pressed. + const QString toolTip = QString("%1 empty folders in the list.").arg(visible ? "Hide" : "Show"); + ui->button_ToggleEmptyFolders->setToolTip(toolTip); + + const QSignalBlocker b(ui->button_ToggleEmptyFolders); + ui->button_ToggleEmptyFolders->setChecked(!visible); +} + +void MapListToolBar::expandList() { + if (m_list) + m_list->expandToDepth(0); +} + +void MapListToolBar::collapseList() { + if (m_list) { + m_list->collapseAll(); + } +} + +void MapListToolBar::applyFilter(const QString &filterText) { + if (m_filterLocked) + return; + + const QSignalBlocker b(ui->lineEdit_filterBox); + ui->lineEdit_filterBox->setText(filterText); + + if (m_list) { + auto model = static_cast(m_list->model()); + if (model) model->setFilterRegularExpression(QRegularExpression(filterText, QRegularExpression::CaseInsensitiveOption)); + + if (filterText.isEmpty()) { + m_list->collapseAll(); + emit filterCleared(m_list); + } else { + m_list->expandToDepth(0); + } + } +} + +void MapListToolBar::clearFilter() { + applyFilter(""); +} diff --git a/src/ui/mapsceneeventfilter.cpp b/src/ui/mapsceneeventfilter.cpp deleted file mode 100644 index f8ae14cb5..000000000 --- a/src/ui/mapsceneeventfilter.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "mapsceneeventfilter.h" -#include -#include - -MapSceneEventFilter::MapSceneEventFilter(QObject *parent) : QObject(parent) -{ - -} - -bool MapSceneEventFilter::eventFilter(QObject*, QEvent *event) -{ - if (event->type() == QEvent::GraphicsSceneWheel) - { - QGraphicsSceneWheelEvent *wheelEvent = static_cast(event); - if (wheelEvent->modifiers() & Qt::ControlModifier) - { - emit wheelZoom(wheelEvent->delta() > 0 ? 1 : -1); - event->accept(); - return true; - } - } - return false; -} diff --git a/src/ui/metatilelayersitem.cpp b/src/ui/metatilelayersitem.cpp index e4a1e74e9..3050e7613 100644 --- a/src/ui/metatilelayersitem.cpp +++ b/src/ui/metatilelayersitem.cpp @@ -24,7 +24,7 @@ void MetatileLayersItem::draw() { QPainter painter(&pixmap); // Draw tile images - int numTiles = projectConfig.getNumTilesInMetatile(); + int numTiles = qMin(projectConfig.getNumTilesInMetatile(), this->metatile ? this->metatile->tiles.length() : 0); for (int i = 0; i < numTiles; i++) { Tile tile = this->metatile->tiles.at(i); QImage tileImage = getPalettedTileImage(tile.tileId, this->primaryTileset, this->secondaryTileset, tile.palette, true) diff --git a/src/ui/metatileselector.cpp b/src/ui/metatileselector.cpp index 074b77582..1214e2894 100644 --- a/src/ui/metatileselector.cpp +++ b/src/ui/metatileselector.cpp @@ -14,8 +14,8 @@ void MetatileSelector::draw() { this->setPixmap(QPixmap()); } - int primaryLength = this->primaryTileset->metatiles.length(); - int length_ = primaryLength + this->secondaryTileset->metatiles.length(); + int primaryLength = this->primaryTileset->numMetatiles(); + int length_ = primaryLength + this->secondaryTileset->numMetatiles(); int height_ = length_ / this->numMetatilesWide; if (length_ % this->numMetatilesWide != 0) { height_++; @@ -28,7 +28,7 @@ void MetatileSelector::draw() { if (i >= primaryLength) { tile += Project::getNumMetatilesPrimary() - primaryLength; } - QImage metatile_image = getMetatileImage(tile, this->primaryTileset, this->secondaryTileset, map->metatileLayerOrder, map->metatileLayerOpacity); + QImage metatile_image = getMetatileImage(tile, this->primaryTileset, this->secondaryTileset, layout->metatileLayerOrder, layout->metatileLayerOpacity); int map_y = i / this->numMetatilesWide; int map_x = i % this->numMetatilesWide; QPoint metatile_origin = QPoint(map_x * 16, map_y * 16); @@ -199,10 +199,10 @@ void MetatileSelector::updateExternalSelectedMetatiles() { uint16_t MetatileSelector::getMetatileId(int x, int y) const { int index = y * this->numMetatilesWide + x; - if (index < this->primaryTileset->metatiles.length()) { + if (index < this->primaryTileset->numMetatiles()) { return static_cast(index); } else { - return static_cast(Project::getNumMetatilesPrimary() + index - this->primaryTileset->metatiles.length()); + return static_cast(Project::getNumMetatilesPrimary() + index - this->primaryTileset->numMetatiles()); } } @@ -215,7 +215,7 @@ QPoint MetatileSelector::getMetatileIdCoords(uint16_t metatileId) { int index = metatileId < Project::getNumMetatilesPrimary() ? metatileId - : metatileId - Project::getNumMetatilesPrimary() + this->primaryTileset->metatiles.length(); + : metatileId - Project::getNumMetatilesPrimary() + this->primaryTileset->numMetatiles(); return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide); } @@ -226,6 +226,6 @@ QPoint MetatileSelector::getMetatileIdCoordsOnWidget(uint16_t metatileId) { return pos; } -void MetatileSelector::setMap(Map *map) { - this->map = map; +void MetatileSelector::setLayout(Layout *layout) { + this->layout = layout; } diff --git a/src/ui/montabwidget.cpp b/src/ui/montabwidget.cpp index 74c52dc5d..7b196c75e 100644 --- a/src/ui/montabwidget.cpp +++ b/src/ui/montabwidget.cpp @@ -3,6 +3,7 @@ #include "editor.h" #include "encountertablemodel.h" #include "encountertabledelegates.h" +#include "eventfilters.h" @@ -11,20 +12,13 @@ static WildMonInfo encounterClipboard; MonTabWidget::MonTabWidget(Editor *editor, QWidget *parent) : QTabWidget(parent) { this->editor = editor; populate(); - this->tabBar()->installEventFilter(this); + this->tabBar()->installEventFilter(new WheelFilter(this)); } MonTabWidget::~MonTabWidget() { } -bool MonTabWidget::eventFilter(QObject *, QEvent *event) { - if (event->type() == QEvent::Wheel) { - return true; - } - return false; -} - void MonTabWidget::populate() { EncounterFields fields = editor->project->wildMonFields; activeTabs.resize(fields.size()); diff --git a/src/ui/newmappopup.cpp b/src/ui/newmappopup.cpp index be6499e62..def485c2a 100644 --- a/src/ui/newmappopup.cpp +++ b/src/ui/newmappopup.cpp @@ -8,6 +8,8 @@ #include #include +// TODO: Convert to modal dialog (among other things, this means we wouldn't need to worry about changes to the map list while this is open) + struct NewMapPopup::Settings NewMapPopup::settings = {}; NewMapPopup::NewMapPopup(QWidget *parent, Project *project) : @@ -27,14 +29,18 @@ NewMapPopup::~NewMapPopup() delete ui; } -void NewMapPopup::init() { +void NewMapPopup::initUi() { // Populate combo boxes ui->comboBox_NewMap_Primary_Tileset->addItems(project->primaryTilesetLabels); ui->comboBox_NewMap_Secondary_Tileset->addItems(project->secondaryTilesetLabels); ui->comboBox_NewMap_Group->addItems(project->groupNames); ui->comboBox_NewMap_Song->addItems(project->songNames); ui->comboBox_NewMap_Type->addItems(project->mapTypes); - ui->comboBox_NewMap_Location->addItems(project->mapSectionNameToValue.keys()); + ui->comboBox_NewMap_Location->addItems(project->mapSectionIdNames); + + const QSignalBlocker b(ui->comboBox_Layout); + ui->comboBox_Layout->addItems(project->mapLayoutsTable); + this->layoutId = project->mapLayoutsTable.first(); // Set spin box limits ui->spinBox_NewMap_Width->setMinimum(1); @@ -67,6 +73,10 @@ void NewMapPopup::init() { ui->spinBox_NewMap_Floor_Number->setVisible(hasFloorNumber); ui->label_NewMap_Floor_Number->setVisible(hasFloorNumber); + this->updateGeometry(); +} + +void NewMapPopup::init() { // Restore previous settings ui->lineEdit_NewMap_Name->setText(project->getNewMapName()); ui->comboBox_NewMap_Group->setTextItem(settings.group); @@ -94,29 +104,32 @@ void NewMapPopup::init() { } // Creating new map by right-clicking in the map list -void NewMapPopup::init(MapSortOrder type, QVariant data) { - switch (type) +void NewMapPopup::init(int tabIndex, QString fieldName) { + initUi(); + switch (tabIndex) { - case MapSortOrder::Group: - settings.group = project->groupNames.at(data.toInt()); + case MapListTab::Groups: + settings.group = fieldName; break; - case MapSortOrder::Area: - settings.location = data.toString(); + case MapListTab::Areas: + settings.location = fieldName; break; - case MapSortOrder::Layout: - useLayout(data.toString()); + case MapListTab::Layouts: + this->ui->checkBox_UseExistingLayout->setCheckState(Qt::Checked); + useLayout(fieldName); break; } init(); } // Creating new map from AdvanceMap import -void NewMapPopup::init(MapLayout *mapLayout) { +void NewMapPopup::init(Layout *mapLayout) { + initUi(); this->importedMap = true; useLayoutSettings(mapLayout); this->map = new Map(); - this->map->layout = new MapLayout(); + this->map->layout = new Layout(); this->map->layout->blockdata = mapLayout->blockdata; if (!mapLayout->border.isEmpty()) { @@ -175,7 +188,7 @@ void NewMapPopup::setDefaultSettings(Project *project) { settings.primaryTilesetLabel = project->getDefaultPrimaryTilesetLabel(); settings.secondaryTilesetLabel = project->getDefaultSecondaryTilesetLabel(); settings.type = project->mapTypes.value(0, "0"); - settings.location = project->mapSectionNameToValue.keys().value(0, "0"); + settings.location = project->mapSectionIdNames.value(0, "0"); settings.song = project->defaultSong; settings.canFlyTo = false; settings.showLocationName = true; @@ -204,28 +217,60 @@ void NewMapPopup::saveSettings() { settings.floorNumber = ui->spinBox_NewMap_Floor_Number->value(); } -void NewMapPopup::useLayoutSettings(MapLayout *layout) { +void NewMapPopup::useLayoutSettings(Layout *layout) { if (!layout) return; + settings.width = layout->width; + ui->spinBox_NewMap_Width->setValue(layout->width); + settings.height = layout->height; + ui->spinBox_NewMap_Height->setValue(layout->height); + settings.borderWidth = layout->border_width; + ui->spinBox_NewMap_BorderWidth->setValue(layout->border_width); + settings.borderHeight = layout->border_height; + ui->spinBox_NewMap_BorderWidth->setValue(layout->border_height); + settings.primaryTilesetLabel = layout->tileset_primary_label; + ui->comboBox_NewMap_Primary_Tileset->setCurrentIndex(ui->comboBox_NewMap_Primary_Tileset->findText(layout->tileset_primary_label)); + settings.secondaryTilesetLabel = layout->tileset_secondary_label; + ui->comboBox_NewMap_Secondary_Tileset->setCurrentIndex(ui->comboBox_NewMap_Secondary_Tileset->findText(layout->tileset_secondary_label)); } void NewMapPopup::useLayout(QString layoutId) { this->existingLayout = true; this->layoutId = layoutId; + + this->ui->comboBox_Layout->setCurrentIndex(this->ui->comboBox_Layout->findText(layoutId)); + useLayoutSettings(project->mapLayouts.value(this->layoutId)); +} + +void NewMapPopup::on_checkBox_UseExistingLayout_stateChanged(int state) { + bool layoutEditsEnabled = (state == Qt::Unchecked); + + this->ui->comboBox_Layout->setEnabled(!layoutEditsEnabled); + + this->ui->spinBox_NewMap_Width->setEnabled(layoutEditsEnabled); + this->ui->spinBox_NewMap_Height->setEnabled(layoutEditsEnabled); + this->ui->spinBox_NewMap_BorderWidth->setEnabled(layoutEditsEnabled); + this->ui->spinBox_NewMap_BorderWidth->setEnabled(layoutEditsEnabled); + this->ui->comboBox_NewMap_Primary_Tileset->setEnabled(layoutEditsEnabled); + this->ui->comboBox_NewMap_Secondary_Tileset->setEnabled(layoutEditsEnabled); - // Dimensions and tilesets can't be changed for new maps using an existing layout - ui->spinBox_NewMap_Width->setDisabled(true); - ui->spinBox_NewMap_Height->setDisabled(true); - ui->spinBox_NewMap_BorderWidth->setDisabled(true); - ui->spinBox_NewMap_BorderHeight->setDisabled(true); - ui->comboBox_NewMap_Primary_Tileset->setDisabled(true); - ui->comboBox_NewMap_Secondary_Tileset->setDisabled(true); + if (!layoutEditsEnabled) { + useLayout(this->layoutId);//this->ui->comboBox_Layout->currentText()); + } else { + this->existingLayout = false; + } +} + +void NewMapPopup::on_comboBox_Layout_currentTextChanged(const QString &text) { + if (this->project->mapLayoutsTable.contains(text)) { + useLayout(text); + } } void NewMapPopup::on_lineEdit_NewMap_Name_textChanged(const QString &text) { @@ -242,7 +287,7 @@ void NewMapPopup::on_pushButton_NewMap_Accept_clicked() { return; } Map *newMap = new Map; - MapLayout *layout; + Layout *layout; // If map name is not unique, use default value. Also use only valid characters. // After stripping invalid characters, strip any leading digits. @@ -267,8 +312,8 @@ void NewMapPopup::on_pushButton_NewMap_Accept_clicked() { layout = this->project->mapLayouts.value(this->layoutId); newMap->needsLayoutDir = false; } else { - layout = new MapLayout; - layout->id = MapLayout::layoutConstantFromName(newMapName); + layout = new Layout; + layout->id = Layout::layoutConstantFromName(newMapName); layout->name = QString("%1_Layout").arg(newMap->name); layout->width = this->ui->spinBox_NewMap_Width->value(); layout->height = this->ui->spinBox_NewMap_Height->value(); diff --git a/src/ui/newtilesetdialog.cpp b/src/ui/newtilesetdialog.cpp index fa9518791..e9eee9464 100644 --- a/src/ui/newtilesetdialog.cpp +++ b/src/ui/newtilesetdialog.cpp @@ -1,6 +1,5 @@ #include "newtilesetdialog.h" #include "ui_newtilesetdialog.h" -#include #include "project.h" NewTilesetDialog::NewTilesetDialog(Project* project, QWidget *parent) : diff --git a/src/ui/noscrollcombobox.cpp b/src/ui/noscrollcombobox.cpp index 3db2a9567..e6e21a4e1 100644 --- a/src/ui/noscrollcombobox.cpp +++ b/src/ui/noscrollcombobox.cpp @@ -2,6 +2,7 @@ #include #include +#include NoScrollComboBox::NoScrollComboBox(QWidget *parent) : QComboBox(parent) @@ -19,7 +20,7 @@ NoScrollComboBox::NoScrollComboBox(QWidget *parent) this->completer()->setFilterMode(Qt::MatchContains); static const QRegularExpression re("[^\\s]*"); - QValidator *validator = new QRegularExpressionValidator(re); + QValidator *validator = new QRegularExpressionValidator(re, this); this->setValidator(validator); } @@ -39,8 +40,11 @@ void NoScrollComboBox::wheelEvent(QWheelEvent *event) { // 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()) + if (this->focusedScrollingEnabled && hasFocus()) { QComboBox::wheelEvent(event); + } else { + event->ignore(); + } } void NoScrollComboBox::setFocusedScrollingEnabled(bool enabled) diff --git a/src/ui/noscrollspinbox.cpp b/src/ui/noscrollspinbox.cpp index 144b0e912..f8d1d4441 100644 --- a/src/ui/noscrollspinbox.cpp +++ b/src/ui/noscrollspinbox.cpp @@ -1,4 +1,5 @@ #include "noscrollspinbox.h" +#include unsigned actionId = 0xffff; @@ -12,8 +13,11 @@ NoScrollSpinBox::NoScrollSpinBox(QWidget *parent) void NoScrollSpinBox::wheelEvent(QWheelEvent *event) { // Only allow scrolling to modify contents when it explicitly has focus. - if (hasFocus()) + if (hasFocus()) { QSpinBox::wheelEvent(event); + } else { + event->ignore(); + } } void NoScrollSpinBox::focusOutEvent(QFocusEvent *event) { diff --git a/src/ui/overlay.cpp b/src/ui/overlay.cpp index 4642afa1f..d1ba4ef89 100644 --- a/src/ui/overlay.cpp +++ b/src/ui/overlay.cpp @@ -16,8 +16,8 @@ void OverlayPath::render(QPainter *painter) { painter->drawPath(this->path); } -void OverlayImage::render(QPainter *painter) { - painter->drawImage(this->x, this->y, this->image); +void OverlayPixmap::render(QPainter *painter) { + painter->drawPixmap(this->x, this->y, this->pixmap); } void Overlay::renderItems(QPainter *painter) { @@ -244,7 +244,7 @@ bool Overlay::addImage(int x, int y, QString filepath, bool useCache, int width, if (setTransparency) image.setColor(0, qRgba(0, 0, 0, 0)); - this->items.append(new OverlayImage(x, y, image)); + this->items.append(new OverlayPixmap(x, y, QPixmap::fromImage(image))); return true; } @@ -253,6 +253,6 @@ bool Overlay::addImage(int x, int y, QImage image) { logError(QString("Failed to load custom image")); return false; } - this->items.append(new OverlayImage(x, y, image)); + this->items.append(new OverlayPixmap(x, y, QPixmap::fromImage(image))); return true; } diff --git a/src/ui/paletteeditor.cpp b/src/ui/paletteeditor.cpp index 8dcd2a7a4..d570f540b 100644 --- a/src/ui/paletteeditor.cpp +++ b/src/ui/paletteeditor.cpp @@ -1,19 +1,12 @@ #include "paletteeditor.h" #include "ui_paletteeditor.h" -#include "colorpicker.h" #include "paletteutil.h" #include "config.h" #include "log.h" +#include "filedialog.h" -#include -#include #include -static inline int rgb5(int rgb) { return round(static_cast(rgb * 31) / 255.0); } -static inline int rgb8(int rgb) { return round(rgb * 255. / 31.); } -static inline int gbaRed(int rgb) { return rgb & 0x1f; } -static inline int gbaGreen(int rgb) { return (rgb >> 5) & 0x1f; } -static inline int gbaBlue(int rgb) { return (rgb >> 10) & 0x1f; } PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset *secondaryTileset, int paletteId, QWidget *parent) : QMainWindow(parent), @@ -26,55 +19,14 @@ PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset this->ui->spinBox_PaletteId->setMinimum(0); this->ui->spinBox_PaletteId->setMaximum(Project::getNumPalettesTotal() - 1); - this->sliders.clear(); - for (int i = 0; i < 16; i++) { - QList rgbSliders; - rgbSliders.append(this->ui->container->findChild("slider_red_" + QString::number(i))); - rgbSliders.append(this->ui->container->findChild("slider_green_" + QString::number(i))); - rgbSliders.append(this->ui->container->findChild("slider_blue_" + QString::number(i))); - this->sliders.append(rgbSliders); - - connect(this->sliders[i][0], &QSlider::valueChanged, [=](int) { setRgbFromSliders(i); }); - connect(this->sliders[i][1], &QSlider::valueChanged, [=](int) { setRgbFromSliders(i); }); - connect(this->sliders[i][2], &QSlider::valueChanged, [=](int) { setRgbFromSliders(i); }); - } - - this->spinners.clear(); - for (int i = 0; i < 16; i++) { - QList rgbSpinners; - rgbSpinners.append(this->ui->container->findChild("spin_red_" + QString::number(i))); - rgbSpinners.append(this->ui->container->findChild("spin_green_" + QString::number(i))); - rgbSpinners.append(this->ui->container->findChild("spin_blue_" + QString::number(i))); - this->spinners.append(rgbSpinners); - - connect(this->spinners[i][0], QOverload::of(&QSpinBox::valueChanged), [=](int) { setRgbFromSpinners(i); }); - connect(this->spinners[i][1], QOverload::of(&QSpinBox::valueChanged), [=](int) { setRgbFromSpinners(i); }); - connect(this->spinners[i][2], QOverload::of(&QSpinBox::valueChanged), [=](int) { setRgbFromSpinners(i); }); - } - - this->frames.clear(); - for (int i = 0; i < 16; i++) { - this->frames.append(this->ui->container->findChild("colorFrame_" + QString::number(i))); - this->frames[i]->setFrameStyle(QFrame::NoFrame); - } - - this->pickButtons.clear(); - for (int i = 0; i < 16; i++) { - this->pickButtons.append(this->ui->container->findChild("pick_" + QString::number(i))); - } - - this->hexValidator = new HexCodeValidator; - this->hexEdits.clear(); - for (int i = 0; i < 16; i++) { - this->hexEdits.append(this->ui->container->findChild("hex_" + QString::number(i))); - this->hexEdits[i]->setValidator(hexValidator); - } - - // Connect to function that will update color when hex edit is changed - for (int i = 0; i < this->hexEdits.length(); i++) { - connect(this->hexEdits[i], &QLineEdit::textEdited, [this, i](QString text){ - if ((this->bitDepth == 24 && text.length() == 6) || (this->bitDepth == 15 && text.length() == 4)) setRgbFromHexEdit(i); - }); + this->colorInputs.clear(); + const int numColorsPerRow = 4; + for (int i = 0; i < this->numColors; i++) { + auto colorInput = new ColorInputWidget(QString("Color %1").arg(i)); + connect(colorInput, &ColorInputWidget::colorChanged, [this, i](QRgb color) { setRgb(i, color); }); + connect(colorInput, &ColorInputWidget::editingFinished, [this] { commitEditHistory(); }); + this->colorInputs.append(colorInput); + ui->layout_Colors->addWidget(colorInput, i / numColorsPerRow, i % numColorsPerRow); } // Setup edit-undo history for each of the palettes. @@ -82,11 +34,6 @@ PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset this->palettesHistory.append(History()); } - // Connect the color picker's selection to the correct color index - for (int i = 0; i < 16; i++) { - connect(this->pickButtons[i], &QToolButton::clicked, [this, i](){ this->pickColor(i); }); - } - int bitDepth = porymapConfig.paletteEditorBitDepth; if (bitDepth == 15) { this->ui->bit_depth_15->setChecked(true); @@ -100,246 +47,87 @@ PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset connect(this->ui->bit_depth_24, &QRadioButton::toggled, [this](bool checked){ if (checked) this->setBitDepth(24); }); this->setPaletteId(paletteId); - this->commitEditHistory(this->ui->spinBox_PaletteId->value()); + this->commitEditHistory(); this->restoreWindowState(); } PaletteEditor::~PaletteEditor() { delete ui; - delete this->hexValidator; -} - -void PaletteEditor::updateColorUi(int colorIndex, QRgb rgb) { - setSignalsEnabled(false); - - int red = qRed(rgb); - int green = qGreen(rgb); - int blue = qBlue(rgb); - - if (this->bitDepth == 15) { - // sliders - this->sliders[colorIndex][0]->setValue(rgb5(red)); - this->sliders[colorIndex][1]->setValue(rgb5(green)); - this->sliders[colorIndex][2]->setValue(rgb5(blue)); - - // hex - int hex15 = (rgb5(blue) << 10) | (rgb5(green) << 5) | rgb5(red); - QString hexcode = QString("%1").arg(hex15, 4, 16, QLatin1Char('0')).toUpper(); - this->hexEdits[colorIndex]->setText(hexcode); - - // spinners - this->spinners[colorIndex][0]->setValue(rgb5(red)); - this->spinners[colorIndex][1]->setValue(rgb5(green)); - this->spinners[colorIndex][2]->setValue(rgb5(blue)); - } else { - // sliders - this->sliders[colorIndex][0]->setValue(red); - this->sliders[colorIndex][1]->setValue(green); - this->sliders[colorIndex][2]->setValue(blue); - - // hex - QColor color(red, green, blue); - QString hexcode = color.name().remove(0, 1).toUpper(); - this->hexEdits[colorIndex]->setText(hexcode); - - // spinners - this->spinners[colorIndex][0]->setValue(red); - this->spinners[colorIndex][1]->setValue(green); - this->spinners[colorIndex][2]->setValue(blue); - } - - // frame - QString stylesheet = QString("background-color: rgb(%1, %2, %3);").arg(red).arg(green).arg(blue); - this->frames[colorIndex]->setStyleSheet(stylesheet); - - setSignalsEnabled(true); } -void PaletteEditor::setSignalsEnabled(bool enabled) { - // spinners, sliders, hexbox - for (int i = 0; i < this->sliders.length(); i++) { - this->sliders.at(i).at(0)->blockSignals(!enabled); - this->sliders.at(i).at(1)->blockSignals(!enabled); - this->sliders.at(i).at(2)->blockSignals(!enabled); - } - - for (int i = 0; i < this->spinners.length(); i++) { - this->spinners.at(i).at(0)->blockSignals(!enabled); - this->spinners.at(i).at(1)->blockSignals(!enabled); - this->spinners.at(i).at(2)->blockSignals(!enabled); - } - - for (int i = 0; i < this->hexEdits.length(); i++) { - this->hexEdits.at(i)->blockSignals(!enabled); - } +Tileset* PaletteEditor::getTileset(int paletteId) { + return (paletteId < Project::getNumPalettesPrimary()) + ? this->primaryTileset + : this->secondaryTileset; } void PaletteEditor::setBitDepth(int bits) { - setSignalsEnabled(false); - switch (bits) { - case 15: - for (int i = 0; i < 16; i++) { - // sliders ranged [0, 31] with 1 single step and 4 page step - this->sliders[i][0]->setSingleStep(1); - this->sliders[i][1]->setSingleStep(1); - this->sliders[i][2]->setSingleStep(1); - this->sliders[i][0]->setPageStep(4); - this->sliders[i][1]->setPageStep(4); - this->sliders[i][2]->setPageStep(4); - this->sliders[i][0]->setMaximum(31); - this->sliders[i][1]->setMaximum(31); - this->sliders[i][2]->setMaximum(31); - - // spinners limited [0, 31] with 1 step - this->spinners[i][0]->setSingleStep(1); - this->spinners[i][1]->setSingleStep(1); - this->spinners[i][2]->setSingleStep(1); - this->spinners[i][0]->setMaximum(31); - this->spinners[i][1]->setMaximum(31); - this->spinners[i][2]->setMaximum(31); - - // hex box now 4 digits - this->hexEdits[i]->setInputMask("HHHH"); - this->hexEdits[i]->setMaxLength(4); - } - break; - case 24: - default: - for (int i = 0; i < 16; i++) { - // sliders ranged [0, 31] with 1 single step and 4 page step - this->sliders[i][0]->setSingleStep(8); - this->sliders[i][1]->setSingleStep(8); - this->sliders[i][2]->setSingleStep(8); - this->sliders[i][0]->setPageStep(24); - this->sliders[i][1]->setPageStep(24); - this->sliders[i][2]->setPageStep(24); - this->sliders[i][0]->setMaximum(255); - this->sliders[i][1]->setMaximum(255); - this->sliders[i][2]->setMaximum(255); - - // spinners limited [0, 31] with 1 step - this->spinners[i][0]->setSingleStep(8); - this->spinners[i][1]->setSingleStep(8); - this->spinners[i][2]->setSingleStep(8); - this->spinners[i][0]->setMaximum(255); - this->spinners[i][1]->setMaximum(255); - this->spinners[i][2]->setMaximum(255); - - // hex box now 4 digits - this->hexEdits[i]->setInputMask("HHHHHH"); - this->hexEdits[i]->setMaxLength(6); - } - break; - } this->bitDepth = bits; porymapConfig.paletteEditorBitDepth = bits; - refreshColorUis(); - setSignalsEnabled(true); + for (const auto &colorInput : this->colorInputs) { + colorInput->setBitDepth(bits); + } } void PaletteEditor::setRgb(int colorIndex, QRgb rgb) { - int paletteNum = this->ui->spinBox_PaletteId->value(); + const int paletteId = this->ui->spinBox_PaletteId->value(); - Tileset *tileset = paletteNum < Project::getNumPalettesPrimary() - ? this->primaryTileset - : this->secondaryTileset; - tileset->palettes[paletteNum][colorIndex] = rgb; - tileset->palettePreviews[paletteNum][colorIndex] = rgb; + Tileset *tileset = getTileset(paletteId); + tileset->palettes[paletteId][colorIndex] = rgb; + tileset->palettePreviews[paletteId][colorIndex] = rgb; - this->updateColorUi(colorIndex, rgb); - - this->commitEditHistory(paletteNum); - emit this->changedPaletteColor(); -} - -void PaletteEditor::setRgbFromSliders(int colorIndex) { - if (this->bitDepth == 15) { - setRgb(colorIndex, qRgb(rgb8(this->sliders[colorIndex][0]->value()), - rgb8(this->sliders[colorIndex][1]->value()), - rgb8(this->sliders[colorIndex][2]->value()))); - } else { - setRgb(colorIndex, qRgb(this->sliders[colorIndex][0]->value(), - this->sliders[colorIndex][1]->value(), - this->sliders[colorIndex][2]->value())); - } -} - -void PaletteEditor::setRgbFromHexEdit(int colorIndex) { - QString text = this->hexEdits[colorIndex]->text(); - bool ok = false; - int rgb = text.toInt(&ok, 16); - if (!ok) rgb = 0xFFFFFFFF; - if (this->bitDepth == 15) { - int rc = gbaRed(rgb); - int gc = gbaGreen(rgb); - int bc = gbaBlue(rgb); - setRgb(colorIndex, qRgb(rgb8(rc), rgb8(gc), rgb8(bc))); - } else { - setRgb(colorIndex, qRgb(qRed(rgb), qGreen(rgb), qBlue(rgb))); - } + emit changedPaletteColor(); } -void PaletteEditor::setRgbFromSpinners(int colorIndex) { - if (this->bitDepth == 15) { - setRgb(colorIndex, qRgb(rgb8(this->spinners[colorIndex][0]->value()), - rgb8(this->spinners[colorIndex][1]->value()), - rgb8(this->spinners[colorIndex][2]->value()))); - } else { - setRgb(colorIndex, qRgb(this->spinners[colorIndex][0]->value(), - this->spinners[colorIndex][1]->value(), - this->spinners[colorIndex][2]->value())); +void PaletteEditor::setPalette(int paletteId, const QList &palette) { + Tileset *tileset = getTileset(paletteId); + for (int i = 0; i < this->numColors; i++) { + tileset->palettes[paletteId][i] = palette.at(i); + tileset->palettePreviews[paletteId][i] = palette.at(i); } + refreshColorInputs(); + emit changedPaletteColor(); } -void PaletteEditor::refreshColorUis() { - int paletteNum = this->ui->spinBox_PaletteId->value(); - for (int i = 0; i < 16; i++) { - QRgb color; - if (paletteNum < Project::getNumPalettesPrimary()) { - color = this->primaryTileset->palettes.at(paletteNum).at(i); - } else { - color = this->secondaryTileset->palettes.at(paletteNum).at(i); - } - - this->updateColorUi(i, color); +void PaletteEditor::refreshColorInputs() { + const int paletteId = ui->spinBox_PaletteId->value(); + Tileset *tileset = getTileset(paletteId); + for (int i = 0; i < this->numColors; i++) { + auto colorInput = this->colorInputs.at(i); + const QSignalBlocker b(colorInput); + colorInput->setColor(tileset->palettes.at(paletteId).at(i)); } } void PaletteEditor::setPaletteId(int paletteId) { - this->ui->spinBox_PaletteId->blockSignals(true); + const QSignalBlocker b(ui->spinBox_PaletteId); this->ui->spinBox_PaletteId->setValue(paletteId); - this->refreshColorUis(); - this->ui->spinBox_PaletteId->blockSignals(false); + this->refreshColorInputs(); } void PaletteEditor::setTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) { this->primaryTileset = primaryTileset; this->secondaryTileset = secondaryTileset; - this->refreshColorUis(); -} - -void PaletteEditor::pickColor(int index) { - ColorPicker picker(this); - if (picker.exec() == QDialog::Accepted) { - QColor c = picker.getColor(); - this->setRgb(index, c.rgb()); - } - return; + this->refreshColorInputs(); } void PaletteEditor::on_spinBox_PaletteId_valueChanged(int paletteId) { - this->refreshColorUis(); + this->refreshColorInputs(); if (!this->palettesHistory[paletteId].current()) { this->commitEditHistory(paletteId); } emit this->changedPalette(paletteId); } +void PaletteEditor::commitEditHistory() { + commitEditHistory(ui->spinBox_PaletteId->value()); +} + void PaletteEditor::commitEditHistory(int paletteId) { QList colors; - for (int i = 0; i < 16; i++) { - colors.append(qRgb(this->spinners[i][0]->value(), this->spinners[i][1]->value(), this->spinners[i][2]->value())); + for (int i = 0; i < this->numColors; i++) { + colors.append(this->colorInputs.at(i)->color()); } PaletteHistoryItem *commit = new PaletteHistoryItem(colors); this->palettesHistory[paletteId].push(commit); @@ -356,44 +144,24 @@ void PaletteEditor::on_actionUndo_triggered() { int paletteId = this->ui->spinBox_PaletteId->value(); PaletteHistoryItem *prev = this->palettesHistory[paletteId].back(); - this->setColorsFromHistory(prev, paletteId); + if (prev) + setPalette(paletteId, prev->colors); } void PaletteEditor::on_actionRedo_triggered() { int paletteId = this->ui->spinBox_PaletteId->value(); PaletteHistoryItem *next = this->palettesHistory[paletteId].next(); - this->setColorsFromHistory(next, paletteId); -} - -void PaletteEditor::setColorsFromHistory(PaletteHistoryItem *history, int paletteId) { - if (!history) return; - - for (int i = 0; i < 16; i++) { - if (paletteId < Project::getNumPalettesPrimary()) { - this->primaryTileset->palettes[paletteId][i] = history->colors.at(i); - this->primaryTileset->palettePreviews[paletteId][i] = history->colors.at(i); - } else { - this->secondaryTileset->palettes[paletteId][i] = history->colors.at(i); - this->secondaryTileset->palettePreviews[paletteId][i] = history->colors.at(i); - } - } - - this->refreshColorUis(); - emit this->changedPaletteColor(); + if (next) + setPalette(paletteId, next->colors); } void PaletteEditor::on_actionImport_Palette_triggered() { - QString filepath = QFileDialog::getOpenFileName( - this, - QString("Import Tileset Palette"), - this->project->importExportPath, - "Palette Files (*.pal *.act *tpl *gpl)"); + QString filepath = FileDialog::getOpenFileName(this, "Import Tileset Palette", "", "Palette Files (*.pal *.act *tpl *gpl)"); if (filepath.isEmpty()) { return; } - this->project->setImportExportPath(filepath); bool error = false; QList palette = PaletteUtil::parse(filepath, &error); if (error) { @@ -407,10 +175,12 @@ void PaletteEditor::on_actionImport_Palette_triggered() return; } - if (palette.length() < 16) { + if (palette.length() < this->numColors) { QMessageBox msgBox(this); msgBox.setText("Failed to import palette."); - QString message = QString("The palette file has %1 colors, but it must have 16 colors.").arg(palette.length()); + QString message = QString("The palette file has %1 colors, but it must have %2 colors.") + .arg(palette.length()) + .arg(this->numColors); msgBox.setInformativeText(message); msgBox.setDefaultButton(QMessageBox::Ok); msgBox.setIcon(QMessageBox::Icon::Critical); @@ -418,20 +188,9 @@ void PaletteEditor::on_actionImport_Palette_triggered() return; } - int paletteId = this->ui->spinBox_PaletteId->value(); - for (int i = 0; i < 16; i++) { - if (paletteId < Project::getNumPalettesPrimary()) { - this->primaryTileset->palettes[paletteId][i] = palette.at(i); - this->primaryTileset->palettePreviews[paletteId][i] = palette.at(i); - } else { - this->secondaryTileset->palettes[paletteId][i] = palette.at(i); - this->secondaryTileset->palettePreviews[paletteId][i] = palette.at(i); - } - } - - this->refreshColorUis(); - this->commitEditHistory(paletteId); - emit this->changedPaletteColor(); + const int paletteId = ui->spinBox_PaletteId->value(); + setPalette(paletteId, palette); + commitEditHistory(paletteId); } void PaletteEditor::closeEvent(QCloseEvent*) { diff --git a/src/ui/prefab.cpp b/src/ui/prefab.cpp index 594e811ae..106421787 100644 --- a/src/ui/prefab.cpp +++ b/src/ui/prefab.cpp @@ -158,32 +158,32 @@ QList Prefab::getPrefabsForTilesets(QString primaryTileset, QString return filteredPrefabs; } -void Prefab::initPrefabUI(MetatileSelector *selector, QWidget *prefabWidget, QLabel *emptyPrefabLabel, Map *map) { +void Prefab::initPrefabUI(MetatileSelector *selector, QWidget *prefabWidget, QLabel *emptyPrefabLabel, Layout *layout) { this->selector = selector; this->prefabWidget = prefabWidget; this->emptyPrefabLabel = emptyPrefabLabel; this->loadPrefabs(); - this->updatePrefabUi(map); + this->updatePrefabUi(layout); } // This function recreates the UI state for the prefab tab. // We completely delete all the prefab widgets, and recreate new widgets // from the relevant list of prefab items. // This is not very efficient, but it gets the job done. -void Prefab::updatePrefabUi(Map *map) { +void Prefab::updatePrefabUi(Layout *layout) { if (!this->selector) return; // Cleanup the PrefabFrame to have a clean slate. - auto layout = this->prefabWidget->layout(); - while (layout && layout->count() > 1) { - auto child = layout->takeAt(1); + auto uiLayout = this->prefabWidget->layout(); + while (uiLayout && uiLayout->count() > 1) { + auto child = uiLayout->takeAt(1); if (child->widget()) { delete child->widget(); } delete child; } - QList prefabs = this->getPrefabsForTilesets(map->layout->tileset_primary_label, map->layout->tileset_secondary_label); + QList prefabs = this->getPrefabsForTilesets(layout->tileset_primary_label, layout->tileset_secondary_label); if (prefabs.isEmpty()) { emptyPrefabLabel->setVisible(true); return; @@ -202,7 +202,7 @@ void Prefab::updatePrefabUi(Map *map) { frame->ui->label_Name->setText(item.name); auto scene = new QGraphicsScene; - scene->addPixmap(drawMetatileSelection(item.selection, map)); + scene->addPixmap(drawMetatileSelection(item.selection, layout)); scene->setSceneRect(scene->itemsBoundingRect()); frame->ui->graphicsView_Prefab->setScene(scene); frame->ui->graphicsView_Prefab->setFixedSize(scene->itemsBoundingRect().width() + 2, @@ -216,7 +216,7 @@ void Prefab::updatePrefabUi(Map *map) { }); // Clicking the delete button removes it from the list of known prefabs and updates the UI. - QObject::connect(frame->ui->pushButton_DeleteItem, &QPushButton::clicked, [this, item, map](){ + QObject::connect(frame->ui->pushButton_DeleteItem, &QPushButton::clicked, [this, item, layout](){ for (int i = 0; i < this->items.size(); i++) { if (this->items[i].id == item.id) { QMessageBox msgBox; @@ -234,7 +234,7 @@ void Prefab::updatePrefabUi(Map *map) { if (msgBox.clickedButton() == deleteButton) { this->items.removeAt(i); this->savePrefabs(); - this->updatePrefabUi(map); + this->updatePrefabUi(layout); } break; } @@ -246,7 +246,7 @@ void Prefab::updatePrefabUi(Map *map) { prefabWidget->layout()->addItem(verticalSpacer); } -void Prefab::addPrefab(MetatileSelection selection, Map *map, QString name) { +void Prefab::addPrefab(MetatileSelection selection, Layout *layout, QString name) { // First, determine which tilesets are actually used in this new prefab, // based on the metatile ids. bool usesPrimaryTileset = false; @@ -264,12 +264,12 @@ void Prefab::addPrefab(MetatileSelection selection, Map *map, QString name) { this->items.append(PrefabItem{ QUuid::createUuid(), name, - usesPrimaryTileset ? map->layout->tileset_primary_label : "", - usesSecondaryTileset ? map->layout->tileset_secondary_label: "", + usesPrimaryTileset ? layout->tileset_primary_label : "", + usesSecondaryTileset ? layout->tileset_secondary_label: "", selection }); this->savePrefabs(); - this->updatePrefabUi(map); + this->updatePrefabUi(layout); } bool Prefab::tryImportDefaultPrefabs(QWidget * parent, BaseGameVersion version, QString filepath) { diff --git a/src/ui/prefabcreationdialog.cpp b/src/ui/prefabcreationdialog.cpp index 976a53a15..a7b3029c2 100644 --- a/src/ui/prefabcreationdialog.cpp +++ b/src/ui/prefabcreationdialog.cpp @@ -6,16 +6,16 @@ #include -PrefabCreationDialog::PrefabCreationDialog(QWidget *parent, MetatileSelector *metatileSelector, Map *map) : +PrefabCreationDialog::PrefabCreationDialog(QWidget *parent, MetatileSelector *metatileSelector, Layout *layout) : QDialog(parent), ui(new Ui::PrefabCreationDialog) { ui->setupUi(this); - this->map = map; + this->layout = layout; this->selection = metatileSelector->getMetatileSelection(); QGraphicsScene *scene = new QGraphicsScene; - QGraphicsPixmapItem *pixmapItem = scene->addPixmap(drawMetatileSelection(this->selection, map)); + QGraphicsPixmapItem *pixmapItem = scene->addPixmap(drawMetatileSelection(this->selection, layout)); scene->setSceneRect(scene->itemsBoundingRect()); this->ui->graphicsView_Prefab->setScene(scene); this->ui->graphicsView_Prefab->setFixedSize(scene->itemsBoundingRect().width() + 2, @@ -35,7 +35,7 @@ PrefabCreationDialog::PrefabCreationDialog(QWidget *parent, MetatileSelector *me if (this->selection.hasCollision) { this->selection.collisionItems[index].enabled = toggledState; } - pixmapItem->setPixmap(drawMetatileSelection(this->selection, map)); + pixmapItem->setPixmap(drawMetatileSelection(this->selection, layout)); }); } @@ -45,5 +45,5 @@ PrefabCreationDialog::~PrefabCreationDialog() } void PrefabCreationDialog::savePrefab() { - prefab.addPrefab(this->selection, this->map, this->ui->lineEdit_PrefabName->text()); + prefab.addPrefab(this->selection, this->layout, this->ui->lineEdit_PrefabName->text()); } diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index d84ef56b9..346591f49 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -2,6 +2,7 @@ #include "config.h" #include "noscrollcombobox.h" #include "prefab.h" +#include "filedialog.h" #include #include @@ -36,6 +37,8 @@ ProjectSettingsEditor::~ProjectSettingsEditor() } void ProjectSettingsEditor::connectSignals() { + connect(ui->button_HelpFiles, &QAbstractButton::clicked, this, &ProjectSettingsEditor::openFilesHelp); + connect(ui->button_HelpIdentifiers, &QAbstractButton::clicked, this, &ProjectSettingsEditor::openIdentifiersHelp); connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &ProjectSettingsEditor::dialogButtonClicked); connect(ui->button_ImportDefaultPrefabs, &QAbstractButton::clicked, this, &ProjectSettingsEditor::importDefaultPrefabsClicked); connect(ui->comboBox_BaseGameVersion, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::promptRestoreDefaults); @@ -383,10 +386,10 @@ QString ProjectSettingsEditor::chooseProjectFile(const QString &defaultFilepath) QString path; if (defaultFilepath.endsWith("/")){ // Default filepath is a folder, choose a new folder - path = QFileDialog::getExistingDirectory(this, "Choose Project File Folder", startDir) + QDir::separator(); + path = FileDialog::getExistingDirectory(this, "Choose Project File Folder", startDir) + QDir::separator(); } else{ // Default filepath is not a folder, choose a new file - path = QFileDialog::getOpenFileName(this, "Choose Project File", startDir); + path = FileDialog::getOpenFileName(this, "Choose Project File", startDir); } if (!path.startsWith(this->baseDir)){ @@ -573,10 +576,9 @@ void ProjectSettingsEditor::chooseImageFile(QLineEdit * filepathEdit) { } void ProjectSettingsEditor::chooseFile(QLineEdit * filepathEdit, const QString &description, const QString &extensions) { - QString filepath = QFileDialog::getOpenFileName(this, description, this->project->importExportPath, extensions); + QString filepath = FileDialog::getOpenFileName(this, description, "", extensions); if (filepath.isEmpty()) return; - this->project->setImportExportPath(filepath); if (filepathEdit) filepathEdit->setText(this->stripProjectDir(filepath)); @@ -658,6 +660,16 @@ void ProjectSettingsEditor::dialogButtonClicked(QAbstractButton *button) { } } +void ProjectSettingsEditor::openFilesHelp() { + static const QUrl url("https://huderlem.github.io/porymap/manual/project-files.html#files"); + QDesktopServices::openUrl(url); +} + +void ProjectSettingsEditor::openIdentifiersHelp() { + static const QUrl url("https://huderlem.github.io/porymap/manual/project-files.html#identifiers"); + QDesktopServices::openUrl(url); +} + // Close event triggered by a project reload. User doesn't need any prompts, just close the window. void ProjectSettingsEditor::closeQuietly() { // Turn off flags that trigger prompts diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index 0a5451a7e..c6485d519 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -30,7 +29,6 @@ RegionMapEditor::RegionMapEditor(QWidget *parent, Project *project) : this->ui->setupUi(this); this->project = project; this->configFilepath = QString("%1/%2").arg(this->project->root).arg(projectConfig.getFilePath(ProjectFilePath::json_region_porymap_cfg)); - this->mapSectionFilepath = QString("%1/%2").arg(this->project->root).arg(projectConfig.getFilePath(ProjectFilePath::json_region_map_entries)); this->initShortcuts(); this->restoreWindowState(); } @@ -111,67 +109,13 @@ void RegionMapEditor::applyUserShortcuts() { } bool RegionMapEditor::loadRegionMapEntries() { - this->region_map_entries.clear(); - - ParseUtil parser; - QJsonDocument sectionsDoc; - if (!parser.tryParseJsonFile(§ionsDoc, this->mapSectionFilepath)) { - logError(QString("Failed to read map data from %1").arg(this->mapSectionFilepath)); - return false; - } - - // for some unknown reason, the OrderedJson class would not parse this properly - // perhaps updating nlohmann/json here would fix it, but that also requires using C++17 - QJsonObject object = sectionsDoc.object(); - - for (auto entryRef : object["map_sections"].toArray()) { - QJsonObject entryObject = entryRef.toObject(); - QString entryMapSection = ParseUtil::jsonToQString(entryObject["map_section"]); - MapSectionEntry entry; - entry.name = ParseUtil::jsonToQString(entryObject["name"]); - entry.x = ParseUtil::jsonToInt(entryObject["x"]); - entry.y = ParseUtil::jsonToInt(entryObject["y"]); - entry.width = ParseUtil::jsonToInt(entryObject["width"]); - entry.height = ParseUtil::jsonToInt(entryObject["height"]); - entry.valid = true; - this->region_map_entries[entryMapSection] = entry; - } - + this->region_map_entries = this->project->regionMapEntries; return true; } bool RegionMapEditor::saveRegionMapEntries() { - QFile sectionsFile(this->mapSectionFilepath); - if (!sectionsFile.open(QIODevice::WriteOnly)) { - logError(QString("Could not open %1 for writing").arg(this->mapSectionFilepath)); - return false; - } - - OrderedJson::object object; - OrderedJson::array mapSectionArray; - - for (auto pair : this->region_map_entries) { - QString section = pair.first; - MapSectionEntry entry = pair.second; - - OrderedJson::object entryObject; - entryObject["map_section"] = section; - entryObject["name"] = entry.name; - entryObject["x"] = entry.x; - entryObject["y"] = entry.y; - entryObject["width"] = entry.width; - entryObject["height"] = entry.height; - - mapSectionArray.append(entryObject); - } - - object["map_sections"] = mapSectionArray; - - OrderedJson sectionsJson(object); - OrderedJsonDoc jsonDoc(§ionsJson); - jsonDoc.dump(§ionsFile); - sectionsFile.close(); - + this->project->regionMapEntries = this->region_map_entries; + this->project->saveRegionMapSections(); return true; } @@ -709,7 +653,7 @@ void RegionMapEditor::displayRegionMapLayoutOptions() { this->ui->comboBox_RM_ConnectedMap->blockSignals(true); this->ui->comboBox_RM_ConnectedMap->clear(); - this->ui->comboBox_RM_ConnectedMap->addItems(this->project->mapSectionValueToName.values()); + this->ui->comboBox_RM_ConnectedMap->addItems(this->project->mapSectionIdNames); this->ui->comboBox_RM_ConnectedMap->blockSignals(false); this->ui->frame_RM_Options->setEnabled(true); @@ -776,7 +720,7 @@ void RegionMapEditor::displayRegionMapEntryOptions() { if (!this->region_map->layoutEnabled()) return; this->ui->comboBox_RM_Entry_MapSection->clear(); - this->ui->comboBox_RM_Entry_MapSection->addItems(this->project->mapSectionValueToName.values()); + this->ui->comboBox_RM_Entry_MapSection->addItems(this->project->mapSectionIdNames); this->ui->spinBox_RM_Entry_x->setMaximum(128); this->ui->spinBox_RM_Entry_y->setMaximum(128); this->ui->spinBox_RM_Entry_width->setMinimum(1); @@ -788,17 +732,13 @@ void RegionMapEditor::displayRegionMapEntryOptions() { void RegionMapEditor::updateRegionMapEntryOptions(QString section) { if (!this->region_map->layoutEnabled()) return; - bool isSpecialSection = (section == this->region_map->default_map_section - || section == this->region_map->count_map_section); - - bool enabled = (!isSpecialSection && this->region_map_entries.contains(section)); - + bool enabled = (section != this->region_map->default_map_section) && this->region_map_entries.contains(section); this->ui->lineEdit_RM_MapName->setEnabled(enabled); this->ui->spinBox_RM_Entry_x->setEnabled(enabled); this->ui->spinBox_RM_Entry_y->setEnabled(enabled); this->ui->spinBox_RM_Entry_width->setEnabled(enabled); this->ui->spinBox_RM_Entry_height->setEnabled(enabled); - this->ui->pushButton_entryActivate->setEnabled(!isSpecialSection); + this->ui->pushButton_entryActivate->setEnabled(section != this->region_map->default_map_section); this->ui->pushButton_entryActivate->setText(enabled ? "Remove" : "Add"); this->ui->lineEdit_RM_MapName->blockSignals(true); @@ -903,14 +843,8 @@ void RegionMapEditor::onRegionMapEntryDragged(int new_x, int new_y) { } void RegionMapEditor::onRegionMapLayoutSelectedTileChanged(int index) { - QString message = QString(); this->currIndex = index; this->region_map_layout_item->highlightedTile = index; - if (this->region_map->squareHasMap(index)) { - message = QString("\t %1").arg(this->project->mapSecToMapHoverName.value( - this->region_map->squareMapSection(index))).remove("{NAME_END}"); - } - this->ui->statusbar->showMessage(message); updateRegionMapLayoutOptions(index); this->region_map_layout_item->draw(); @@ -923,8 +857,7 @@ void RegionMapEditor::onRegionMapLayoutHoveredTileChanged(int index) { if (x >= 0 && y >= 0) { message = QString("(%1, %2)").arg(x).arg(y); if (this->region_map->squareHasMap(index)) { - message += QString("\t %1").arg(this->project->mapSecToMapHoverName.value( - this->region_map->squareMapSection(index))).remove("{NAME_END}"); + message += QString("\t %1").arg(this->region_map->squareMapSection(index)); } } this->ui->statusbar->showMessage(message); @@ -1009,7 +942,6 @@ void RegionMapEditor::on_tabWidget_Region_Map_currentChanged(int index) { } void RegionMapEditor::on_comboBox_RM_ConnectedMap_textActivated(const QString &mapsec) { - QString layer = this->region_map->getLayer(); this->region_map->setSquareMapSection(this->currIndex, mapsec); onRegionMapLayoutSelectedTileChanged(this->currIndex);// re-draw layout image @@ -1096,7 +1028,7 @@ void RegionMapEditor::on_spinBox_RM_LayoutWidth_valueChanged(int value) { int newHeight = this->region_map->layoutHeight(); QMap> newLayouts = this->region_map->getAllLayouts(); - ResizeLayout *commit = new ResizeLayout(this->region_map, oldWidth, oldHeight, newWidth, newHeight, oldLayouts, newLayouts); + ResizeRMLayout *commit = new ResizeRMLayout(this->region_map, oldWidth, oldHeight, newWidth, newHeight, oldLayouts, newLayouts); this->region_map->editHistory.push(commit); } } @@ -1113,7 +1045,7 @@ void RegionMapEditor::on_spinBox_RM_LayoutHeight_valueChanged(int value) { int newHeight = this->region_map->layoutHeight(); QMap> newLayouts = this->region_map->getAllLayouts(); - ResizeLayout *commit = new ResizeLayout(this->region_map, oldWidth, oldHeight, newWidth, newHeight, oldLayouts, newLayouts); + ResizeRMLayout *commit = new ResizeRMLayout(this->region_map, oldWidth, oldHeight, newWidth, newHeight, oldLayouts, newLayouts); this->region_map->editHistory.push(commit); } } @@ -1205,10 +1137,10 @@ void RegionMapEditor::on_action_Swap_triggered() { QFormLayout form(&popup); QComboBox *oldSecBox = new QComboBox(); - oldSecBox->addItems(this->project->mapSectionValueToName.values()); + oldSecBox->addItems(this->project->mapSectionIdNames); form.addRow(new QLabel("Map Section 1:"), oldSecBox); QComboBox *newSecBox = new QComboBox(); - newSecBox->addItems(this->project->mapSectionValueToName.values()); + newSecBox->addItems(this->project->mapSectionIdNames); form.addRow(new QLabel("Map Section 2:"), newSecBox); QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &popup); @@ -1244,10 +1176,10 @@ void RegionMapEditor::on_action_Replace_triggered() { QFormLayout form(&popup); QComboBox *oldSecBox = new QComboBox(); - oldSecBox->addItems(this->project->mapSectionValueToName.values()); + oldSecBox->addItems(this->project->mapSectionIdNames); form.addRow(new QLabel("Old Map Section:"), oldSecBox); QComboBox *newSecBox = new QComboBox(); - newSecBox->addItems(this->project->mapSectionValueToName.values()); + newSecBox->addItems(this->project->mapSectionIdNames); form.addRow(new QLabel("New Map Section:"), newSecBox); QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &popup); diff --git a/src/ui/regionmappropertiesdialog.cpp b/src/ui/regionmappropertiesdialog.cpp index 9c49def0d..10ce3c4a4 100644 --- a/src/ui/regionmappropertiesdialog.cpp +++ b/src/ui/regionmappropertiesdialog.cpp @@ -1,6 +1,7 @@ #include "project.h" #include "regionmappropertiesdialog.h" #include "ui_regionmappropertiesdialog.h" +#include "filedialog.h" RegionMapPropertiesDialog::RegionMapPropertiesDialog(QWidget *parent) : QDialog(parent), @@ -30,13 +31,9 @@ void RegionMapPropertiesDialog::hideMessages() { this->adjustSize(); } -QString RegionMapPropertiesDialog::browse(QString filter, QFileDialog::FileMode mode) { +QString RegionMapPropertiesDialog::browse(QString filter) { if (!this->project) return QString(); - QFileDialog browser; - browser.setFileMode(mode); - QString filepath = browser.getOpenFileName(this, "Select a File", this->project->importExportPath, filter); - if (!filepath.isEmpty()) - this->project->setImportExportPath(filepath); + QString filepath = FileDialog::getOpenFileName(this, "Select a File", "", filter); // remove the project root from the filepath return filepath.replace(this->project->root + "/", ""); @@ -107,21 +104,21 @@ poryjson::Json RegionMapPropertiesDialog::saveToJson() { } void RegionMapPropertiesDialog::on_browse_tilesetImagePath_clicked() { - QString path = browse("Images (*.png *.bmp)", QFileDialog::ExistingFile); + QString path = browse("Images (*.png *.bmp)"); if (!path.isEmpty()) { ui->config_tilemapImagePath->setText(path); } } void RegionMapPropertiesDialog::on_browse_tilemapBinPath_clicked() { - QString path = browse("Binary (*.bin *.tilemap *.4bpp *.8bpp)", QFileDialog::AnyFile); + QString path = browse("Binary (*.bin *.tilemap *.4bpp *.8bpp)"); if (!path.isEmpty()) { ui->config_tilemapBinPath->setText(path); } } void RegionMapPropertiesDialog::on_browse_tilemapPalettePath_clicked() { - QString path = browse("Text (*.pal)", QFileDialog::AnyFile); + QString path = browse("Text (*.pal)"); if (!path.isEmpty()) { ui->config_tilemapPalettePath->setText(path); } @@ -129,12 +126,12 @@ void RegionMapPropertiesDialog::on_browse_tilemapPalettePath_clicked() { void RegionMapPropertiesDialog::on_browse_layoutPath_clicked() { if (ui->config_layoutFormat->currentIndex() == 0) { - QString path = browse("Text File (*.h *.c *.inc *.txt)", QFileDialog::AnyFile); + QString path = browse("Text File (*.h *.c *.inc *.txt)"); if (!path.isEmpty()) { ui->config_layoutPath->setText(path); } } else { - QString path = browse("Binary (*.bin)", QFileDialog::AnyFile); + QString path = browse("Binary (*.bin)"); if (!path.isEmpty()) { ui->config_layoutPath->setText(path); } diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 9ef50cc6e..e709bed1b 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -7,21 +7,21 @@ #include "imageexport.h" #include "config.h" #include "shortcut.h" -#include +#include "filedialog.h" #include #include #include #include -TilesetEditor::TilesetEditor(Project *project, Map *map, QWidget *parent) : +TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent) : QMainWindow(parent), ui(new Ui::TilesetEditor), project(project), - map(map), + layout(layout), hasUnsavedChanges(false) { this->setAttribute(Qt::WA_DeleteOnClose); - this->setTilesets(this->map->layout->tileset_primary_label, this->map->layout->tileset_secondary_label); + this->setTilesets(this->layout->tileset_primary_label, this->layout->tileset_secondary_label); this->initUi(); } @@ -32,7 +32,6 @@ TilesetEditor::~TilesetEditor() delete tileSelector; delete metatileLayersItem; delete paletteEditor; - delete metatile; delete primaryTileset; delete secondaryTileset; delete metatilesScene; @@ -41,16 +40,17 @@ TilesetEditor::~TilesetEditor() delete selectedTileScene; delete metatileLayersScene; delete copiedMetatile; + this->metatileHistory.clear(); } -void TilesetEditor::update(Map *map, QString primaryTilesetLabel, QString secondaryTilesetLabel) { - this->updateMap(map); +void TilesetEditor::update(Layout *layout, QString primaryTilesetLabel, QString secondaryTilesetLabel) { + this->updateLayout(layout); this->updateTilesets(primaryTilesetLabel, secondaryTilesetLabel); } -void TilesetEditor::updateMap(Map *map) { - this->map = map; - this->metatileSelector->map = map; +void TilesetEditor::updateLayout(Layout *layout) { + this->layout = layout; + this->metatileSelector->layout = layout; } void TilesetEditor::updateTilesets(QString primaryTilesetLabel, QString secondaryTilesetLabel) { @@ -180,7 +180,7 @@ void TilesetEditor::setMetatileLabelValidator() { void TilesetEditor::initMetatileSelector() { - this->metatileSelector = new TilesetEditorMetatileSelector(this->primaryTileset, this->secondaryTileset, this->map); + this->metatileSelector = new TilesetEditorMetatileSelector(this->primaryTileset, this->secondaryTileset, this->layout); connect(this->metatileSelector, &TilesetEditorMetatileSelector::hoveredMetatileChanged, this, &TilesetEditor::onHoveredMetatileChanged); connect(this->metatileSelector, &TilesetEditorMetatileSelector::hoveredMetatileCleared, @@ -387,7 +387,7 @@ void TilesetEditor::onSelectedMetatileChanged(uint16_t metatileId) { // The Tileset Editor (if open) needs to reflect these changes when the metatile is next displayed. if (this->metatileReloadQueue.contains(metatileId)) { this->metatileReloadQueue.remove(metatileId); - Metatile *updatedMetatile = Tileset::getMetatile(metatileId, this->map->layout->tileset_primary, this->map->layout->tileset_secondary); + Metatile *updatedMetatile = Tileset::getMetatile(metatileId, this->layout->tileset_primary, this->layout->tileset_secondary); if (updatedMetatile) *this->metatile = *updatedMetatile; } @@ -637,15 +637,11 @@ void TilesetEditor::importTilesetTiles(Tileset *tileset, bool primary) { QString descriptor = primary ? "primary" : "secondary"; QString descriptorCaps = primary ? "Primary" : "Secondary"; - QString filepath = QFileDialog::getOpenFileName( - this, - QString("Import %1 Tileset Tiles Image").arg(descriptorCaps), - this->project->importExportPath, - "Image Files (*.png *.bmp *.jpg *.dib)"); + QString filepath = FileDialog::getOpenFileName(this, QString("Import %1 Tileset Tiles Image").arg(descriptorCaps), "", "Image Files (*.png *.bmp *.jpg *.dib)"); if (filepath.isEmpty()) { return; } - this->project->setImportExportPath(filepath); + logInfo(QString("Importing %1 tileset tiles '%2'").arg(descriptor).arg(filepath)); // Read image data from buffer so that the built-in QImage doesn't try to detect file format @@ -698,15 +694,11 @@ void TilesetEditor::importTilesetTiles(Tileset *tileset, bool primary) { msgBox.setIcon(QMessageBox::Icon::Warning); msgBox.exec(); - QString filepath = QFileDialog::getOpenFileName( - this, - QString("Select Palette for Tiles Image").arg(descriptorCaps), - this->project->importExportPath, - "Palette Files (*.pal *.act *tpl *gpl)"); + QString filepath = FileDialog::getOpenFileName(this, "Select Palette for Tiles Image", "", "Palette Files (*.pal *.act *tpl *gpl)"); if (filepath.isEmpty()) { return; } - this->project->setImportExportPath(filepath); + bool error = false; QList palette = PaletteUtil::parse(filepath, &error); if (error) { @@ -789,8 +781,8 @@ void TilesetEditor::on_actionChange_Metatiles_Count_triggered() secondarySpinBox->setMinimum(1); primarySpinBox->setMaximum(Project::getNumMetatilesPrimary()); secondarySpinBox->setMaximum(Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary()); - primarySpinBox->setValue(this->primaryTileset->metatiles.length()); - secondarySpinBox->setValue(this->secondaryTileset->metatiles.length()); + primarySpinBox->setValue(this->primaryTileset->numMetatiles()); + secondarySpinBox->setValue(this->secondaryTileset->numMetatiles()); form.addRow(new QLabel("Primary Tileset"), primarySpinBox); form.addRow(new QLabel("Secondary Tileset"), secondarySpinBox); @@ -800,22 +792,8 @@ void TilesetEditor::on_actionChange_Metatiles_Count_triggered() form.addRow(&buttonBox); if (dialog.exec() == QDialog::Accepted) { - int numPrimaryMetatiles = primarySpinBox->value(); - int numSecondaryMetatiles = secondarySpinBox->value(); - int numTiles = projectConfig.getNumTilesInMetatile(); - while (this->primaryTileset->metatiles.length() > numPrimaryMetatiles) { - delete this->primaryTileset->metatiles.takeLast(); - } - while (this->primaryTileset->metatiles.length() < numPrimaryMetatiles) { - this->primaryTileset->metatiles.append(new Metatile(numTiles)); - } - while (this->secondaryTileset->metatiles.length() > numSecondaryMetatiles) { - delete this->secondaryTileset->metatiles.takeLast(); - } - while (this->secondaryTileset->metatiles.length() < numSecondaryMetatiles) { - this->secondaryTileset->metatiles.append(new Metatile(numTiles)); - } - + this->primaryTileset->resizeMetatiles(primarySpinBox->value()); + this->secondaryTileset->resizeMetatiles(secondarySpinBox->value()); this->metatileSelector->updateSelectedMetatile(); this->refresh(); this->hasUnsavedChanges = true; @@ -939,10 +917,9 @@ void TilesetEditor::pasteMetatile(const Metatile * toPaste, QString newLabel) void TilesetEditor::on_actionExport_Primary_Tiles_Image_triggered() { QString defaultName = QString("%1_Tiles_Pal%2").arg(this->primaryTileset->name).arg(this->paletteId); - QString defaultFilepath = QString("%1/%2.png").arg(this->project->importExportPath).arg(defaultName); - QString filepath = QFileDialog::getSaveFileName(this, "Export Primary Tiles Image", defaultFilepath, "Image Files (*.png)"); + QString defaultFilepath = QString("%1/%2.png").arg(FileDialog::getDirectory()).arg(defaultName); + QString filepath = FileDialog::getSaveFileName(this, "Export Primary Tiles Image", defaultFilepath, "Image Files (*.png)"); if (!filepath.isEmpty()) { - this->project->setImportExportPath(filepath); QImage image = this->tileSelector->buildPrimaryTilesIndexedImage(); exportIndexed4BPPPng(image, filepath); } @@ -951,10 +928,9 @@ void TilesetEditor::on_actionExport_Primary_Tiles_Image_triggered() void TilesetEditor::on_actionExport_Secondary_Tiles_Image_triggered() { QString defaultName = QString("%1_Tiles_Pal%2").arg(this->secondaryTileset->name).arg(this->paletteId); - QString defaultFilepath = QString("%1/%2.png").arg(this->project->importExportPath).arg(defaultName); - QString filepath = QFileDialog::getSaveFileName(this, "Export Secondary Tiles Image", defaultFilepath, "Image Files (*.png)"); + QString defaultFilepath = QString("%1/%2.png").arg(FileDialog::getDirectory()).arg(defaultName); + QString filepath = FileDialog::getSaveFileName(this, "Export Secondary Tiles Image", defaultFilepath, "Image Files (*.png)"); if (!filepath.isEmpty()) { - this->project->setImportExportPath(filepath); QImage image = this->tileSelector->buildSecondaryTilesIndexedImage(); exportIndexed4BPPPng(image, filepath); } @@ -963,10 +939,9 @@ void TilesetEditor::on_actionExport_Secondary_Tiles_Image_triggered() void TilesetEditor::on_actionExport_Primary_Metatiles_Image_triggered() { QString defaultName = QString("%1_Metatiles").arg(this->primaryTileset->name); - QString defaultFilepath = QString("%1/%2.png").arg(this->project->importExportPath).arg(defaultName); - QString filepath = QFileDialog::getSaveFileName(this, "Export Primary Metatiles Image", defaultFilepath, "Image Files (*.png)"); + QString defaultFilepath = QString("%1/%2.png").arg(FileDialog::getDirectory()).arg(defaultName); + QString filepath = FileDialog::getSaveFileName(this, "Export Primary Metatiles Image", defaultFilepath, "Image Files (*.png)"); if (!filepath.isEmpty()) { - this->project->setImportExportPath(filepath); QImage image = this->metatileSelector->buildPrimaryMetatilesImage(); image.save(filepath, "PNG"); } @@ -975,10 +950,9 @@ void TilesetEditor::on_actionExport_Primary_Metatiles_Image_triggered() void TilesetEditor::on_actionExport_Secondary_Metatiles_Image_triggered() { QString defaultName = QString("%1_Metatiles").arg(this->secondaryTileset->name); - QString defaultFilepath = QString("%1/%2.png").arg(this->project->importExportPath).arg(defaultName); - QString filepath = QFileDialog::getSaveFileName(this, "Export Secondary Metatiles Image", defaultFilepath, "Image Files (*.png)"); + QString defaultFilepath = QString("%1/%2.png").arg(FileDialog::getDirectory()).arg(defaultName); + QString filepath = FileDialog::getSaveFileName(this, "Export Secondary Metatiles Image", defaultFilepath, "Image Files (*.png)"); if (!filepath.isEmpty()) { - this->project->setImportExportPath(filepath); QImage image = this->metatileSelector->buildSecondaryMetatilesImage(); image.save(filepath, "PNG"); } @@ -996,18 +970,13 @@ void TilesetEditor::on_actionImport_Secondary_Metatiles_triggered() void TilesetEditor::importTilesetMetatiles(Tileset *tileset, bool primary) { - QString descriptor = primary ? "primary" : "secondary"; QString descriptorCaps = primary ? "Primary" : "Secondary"; - QString filepath = QFileDialog::getOpenFileName( - this, - QString("Import %1 Tileset Metatiles from Advance Map 1.92").arg(descriptorCaps), - this->project->importExportPath, - "Advance Map 1.92 Metatile Files (*.bvd)"); + QString filepath = FileDialog::getOpenFileName(this, QString("Import %1 Tileset Metatiles from Advance Map 1.92").arg(descriptorCaps), "", "Advance Map 1.92 Metatile Files (*.bvd)"); if (filepath.isEmpty()) { return; } - this->project->setImportExportPath(filepath); + bool error = false; QList metatiles = MetatileParser::parse(filepath, &error, primary); if (error) { @@ -1025,20 +994,20 @@ void TilesetEditor::importTilesetMetatiles(Tileset *tileset, bool primary) // Revisit this when tiles and num metatiles are added to tileset editory history. int metatileIdBase = primary ? 0 : Project::getNumMetatilesPrimary(); for (int i = 0; i < metatiles.length(); i++) { - if (i >= tileset->metatiles.length()) { + if (i >= tileset->numMetatiles()) { break; } uint16_t metatileId = static_cast(metatileIdBase + i); QString prevLabel = Tileset::getOwnedMetatileLabel(metatileId, this->primaryTileset, this->secondaryTileset); - Metatile *prevMetatile = new Metatile(*tileset->metatiles.at(i)); + Metatile *prevMetatile = new Metatile(*tileset->metatileAt(i)); MetatileHistoryItem *commit = new MetatileHistoryItem(metatileId, prevMetatile, new Metatile(*metatiles.at(i)), prevLabel, prevLabel); metatileHistory.push(commit); } - tileset->metatiles = metatiles; + tileset->setMetatiles(metatiles); this->refresh(); this->hasUnsavedChanges = true; } @@ -1142,9 +1111,9 @@ void TilesetEditor::countTileUsage() { // check primary tilesets that are used with this secondary tileset for // reference to secondary tiles in primary metatiles - for (Tileset *tileset : primaryTilesets) { - for (Metatile *metatile : tileset->metatiles) { - for (Tile tile : metatile->tiles) { + for (const auto &tileset : primaryTilesets) { + for (const auto &metatile : tileset->metatiles()) { + for (const auto &tile : metatile->tiles) { if (tile.tileId >= Project::getNumTilesPrimary()) this->tileSelector->usedTiles[tile.tileId]++; } @@ -1153,8 +1122,8 @@ void TilesetEditor::countTileUsage() { // do the opposite for primary tiles in secondary metatiles for (Tileset *tileset : secondaryTilesets) { - for (Metatile *metatile : tileset->metatiles) { - for (Tile tile : metatile->tiles) { + for (const auto &metatile : tileset->metatiles()) { + for (const auto &tile : metatile->tiles) { if (tile.tileId < Project::getNumTilesPrimary()) this->tileSelector->usedTiles[tile.tileId]++; } @@ -1162,15 +1131,15 @@ void TilesetEditor::countTileUsage() { } // check this primary tileset metatiles - for (Metatile *metatile : this->primaryTileset->metatiles) { - for (Tile tile : metatile->tiles) { + for (const auto &metatile : this->primaryTileset->metatiles()) { + for (const auto &tile : metatile->tiles) { this->tileSelector->usedTiles[tile.tileId]++; } } // and the secondary metatiles - for (Metatile *metatile : this->secondaryTileset->metatiles) { - for (Tile tile : metatile->tiles) { + for (const auto &metatile : this->secondaryTileset->metatiles()) { + for (const auto &tile : metatile->tiles) { this->tileSelector->usedTiles[tile.tileId]++; } } diff --git a/src/ui/tileseteditormetatileselector.cpp b/src/ui/tileseteditormetatileselector.cpp index 92d07e3a3..778fde9c9 100644 --- a/src/ui/tileseteditormetatileselector.cpp +++ b/src/ui/tileseteditormetatileselector.cpp @@ -3,11 +3,11 @@ #include "project.h" #include -TilesetEditorMetatileSelector::TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Map *map) +TilesetEditorMetatileSelector::TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Layout *layout) : SelectablePixmapItem(32, 32, 1, 1) { this->setTilesets(primaryTileset, secondaryTileset, false); this->numMetatilesWide = 8; - this->map = map; + this->layout = layout; setAcceptHoverEvents(true); this->usedMetatiles.resize(Project::getNumMetatilesTotal()); } @@ -22,24 +22,24 @@ int TilesetEditorMetatileSelector::numRows(int numMetatiles) { } int TilesetEditorMetatileSelector::numRows() { - return this->numRows(this->primaryTileset->metatiles.length() + this->secondaryTileset->metatiles.length()); + return this->numRows(this->primaryTileset->numMetatiles() + this->secondaryTileset->numMetatiles()); } QImage TilesetEditorMetatileSelector::buildAllMetatilesImage() { - return this->buildImage(0, this->primaryTileset->metatiles.length() + this->secondaryTileset->metatiles.length()); + return this->buildImage(0, this->primaryTileset->numMetatiles() + this->secondaryTileset->numMetatiles()); } QImage TilesetEditorMetatileSelector::buildPrimaryMetatilesImage() { - return this->buildImage(0, this->primaryTileset->metatiles.length()); + return this->buildImage(0, this->primaryTileset->numMetatiles()); } QImage TilesetEditorMetatileSelector::buildSecondaryMetatilesImage() { - return this->buildImage(Project::getNumMetatilesPrimary(), this->secondaryTileset->metatiles.length()); + return this->buildImage(Project::getNumMetatilesPrimary(), this->secondaryTileset->numMetatiles()); } QImage TilesetEditorMetatileSelector::buildImage(int metatileIdStart, int numMetatiles) { int numMetatilesHigh = this->numRows(numMetatiles); - int numPrimary = this->primaryTileset->metatiles.length(); + int numPrimary = this->primaryTileset->numMetatiles(); int maxPrimary = Project::getNumMetatilesPrimary(); bool includesPrimary = metatileIdStart < maxPrimary; @@ -54,8 +54,8 @@ QImage TilesetEditorMetatileSelector::buildImage(int metatileIdStart, int numMet metatileId, this->primaryTileset, this->secondaryTileset, - map->metatileLayerOrder, - map->metatileLayerOpacity, + this->layout->metatileLayerOrder, + this->layout->metatileLayerOpacity, true) .scaled(32, 32); int map_y = i / this->numMetatilesWide; @@ -96,7 +96,7 @@ void TilesetEditorMetatileSelector::updateSelectedMetatile() { if (Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset)) this->selectedMetatile = metatileId; else - this->selectedMetatile = Project::getNumMetatilesPrimary() + this->secondaryTileset->metatiles.length() - 1; + this->selectedMetatile = Project::getNumMetatilesPrimary() + this->secondaryTileset->numMetatiles() - 1; emit selectedMetatileChanged(this->selectedMetatile); } @@ -106,10 +106,10 @@ uint16_t TilesetEditorMetatileSelector::getSelectedMetatileId() { uint16_t TilesetEditorMetatileSelector::getMetatileId(int x, int y) { int index = y * this->numMetatilesWide + x; - if (index < this->primaryTileset->metatiles.length()) { + if (index < this->primaryTileset->numMetatiles()) { return static_cast(index); } else { - return static_cast(Project::getNumMetatilesPrimary() + index - this->primaryTileset->metatiles.length()); + return static_cast(Project::getNumMetatilesPrimary() + index - this->primaryTileset->numMetatiles()); } } @@ -155,7 +155,7 @@ QPoint TilesetEditorMetatileSelector::getMetatileIdCoords(uint16_t metatileId) { } int index = metatileId < Project::getNumMetatilesPrimary() ? metatileId - : metatileId - Project::getNumMetatilesPrimary() + this->primaryTileset->metatiles.length(); + : metatileId - Project::getNumMetatilesPrimary() + this->primaryTileset->numMetatiles(); return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide); } @@ -232,12 +232,8 @@ void TilesetEditorMetatileSelector::drawUnused() { QPainter unusedPainter(&metatilesPixmap); unusedPainter.setOpacity(0.5); - int primaryLength = this->primaryTileset->metatiles.length(); - int length_ = primaryLength + this->secondaryTileset->metatiles.length(); - int height_ = length_ / this->numMetatilesWide; - if (length_ % this->numMetatilesWide != 0) { - height_++; - } + int primaryLength = this->primaryTileset->numMetatiles(); + int length_ = primaryLength + this->secondaryTileset->numMetatiles(); for (int i = 0; i < length_; i++) { int tile = i; @@ -275,12 +271,8 @@ void TilesetEditorMetatileSelector::drawCounts() { whitePen.setWidth(1); countPainter.setPen(whitePen); - int primaryLength = this->primaryTileset->metatiles.length(); - int length_ = primaryLength + this->secondaryTileset->metatiles.length(); - int height_ = length_ / this->numMetatilesWide; - if (length_ % this->numMetatilesWide != 0) { - height_++; - } + int primaryLength = this->primaryTileset->numMetatiles(); + int length_ = primaryLength + this->secondaryTileset->numMetatiles(); for (int i = 0; i < length_; i++) { int tile = i; diff --git a/src/ui/tileseteditortileselector.cpp b/src/ui/tileseteditortileselector.cpp index 99cb38f41..7431e928f 100644 --- a/src/ui/tileseteditortileselector.cpp +++ b/src/ui/tileseteditortileselector.cpp @@ -219,66 +219,32 @@ QPoint TilesetEditorTileSelector::getTileCoordsOnWidget(uint16_t tile) { } QImage TilesetEditorTileSelector::buildPrimaryTilesIndexedImage() { - if (!this->primaryTileset || !this->secondaryTileset) { + if (!this->primaryTileset) return QImage(); - } - - int primaryLength = this->primaryTileset->tiles.length(); - int height = qCeil(primaryLength / static_cast(this->numTilesWide)); - QImage image(this->numTilesWide * 8, height * 8, QImage::Format_RGBA8888); - - QPainter painter(&image); - for (uint16_t tile = 0; tile < primaryLength; tile++) { - QImage tileImage; - if (tile < primaryLength) { - tileImage = getGreyscaleTileImage(tile, this->primaryTileset, this->secondaryTileset); - } else { - tileImage = QImage(8, 8, QImage::Format_RGBA8888); - tileImage.fill(qRgb(0, 0, 0)); - } - - int y = tile / this->numTilesWide; - int x = tile % this->numTilesWide; - QPoint origin = QPoint(x * 8, y * 8); - painter.drawImage(origin, tileImage); - } - - painter.end(); - // Image is first converted using greyscale so that palettes with duplicate colors - // are properly represented in the final image. - QImage indexedImage = image.convertToFormat(QImage::Format::Format_Indexed8, greyscalePalette.toVector()); - QList palette = Tileset::getPalette(this->paletteId, this->primaryTileset, this->secondaryTileset, true); - indexedImage.setColorTable(palette.toVector()); - return indexedImage; + return buildImage(0, this->primaryTileset->tiles.length()); } QImage TilesetEditorTileSelector::buildSecondaryTilesIndexedImage() { - if (!this->primaryTileset || !this->secondaryTileset) { + if (!this->secondaryTileset) return QImage(); - } - int secondaryLength = this->secondaryTileset->tiles.length(); - int height = qCeil(secondaryLength / static_cast(this->numTilesWide)); + return buildImage(Project::getNumTilesPrimary(), this->secondaryTileset->tiles.length()); +} + +QImage TilesetEditorTileSelector::buildImage(int tileIdStart, int numTiles) { + int height = qCeil(numTiles / static_cast(this->numTilesWide)); QImage image(this->numTilesWide * 8, height * 8, QImage::Format_RGBA8888); + image.fill(0); QPainter painter(&image); - uint16_t primaryLength = static_cast(Project::getNumTilesPrimary()); - for (uint16_t tile = 0; tile < secondaryLength; tile++) { - QImage tileImage; - if (tile < secondaryLength) { - tileImage = getGreyscaleTileImage(tile + primaryLength, this->primaryTileset, this->secondaryTileset); - } else { - tileImage = QImage(8, 8, QImage::Format_RGBA8888); - tileImage.fill(qRgb(0, 0, 0)); - } - - int y = tile / this->numTilesWide; - int x = tile % this->numTilesWide; + for (int i = 0; i < numTiles; i++) { + QImage tileImage = getGreyscaleTileImage(tileIdStart + i, this->primaryTileset, this->secondaryTileset); + int y = i / this->numTilesWide; + int x = i % this->numTilesWide; QPoint origin = QPoint(x * 8, y * 8); painter.drawImage(origin, tileImage); } - painter.end(); // Image is first converted using greyscale so that palettes with duplicate colors @@ -294,8 +260,6 @@ void TilesetEditorTileSelector::drawUnused() { QPixmap redX(16, 16); redX.fill(Qt::transparent); - QBitmap mask(16, 16); - QPen whitePen(Qt::white); whitePen.setWidth(1); QPen pinkPen(Qt::magenta); diff --git a/src/ui/uintspinbox.cpp b/src/ui/uintspinbox.cpp index 789a16622..53f6df781 100644 --- a/src/ui/uintspinbox.cpp +++ b/src/ui/uintspinbox.cpp @@ -1,4 +1,5 @@ #include "uintspinbox.h" +#include UIntSpinBox::UIntSpinBox(QWidget *parent) : QAbstractSpinBox(parent) @@ -178,8 +179,11 @@ QAbstractSpinBox::StepEnabled UIntSpinBox::stepEnabled() const { void UIntSpinBox::wheelEvent(QWheelEvent *event) { // Only allow scrolling to modify contents when it explicitly has focus. - if (hasFocus()) + if (hasFocus()) { QAbstractSpinBox::wheelEvent(event); + } else { + event->ignore(); + } } void UIntSpinBox::focusOutEvent(QFocusEvent *event) { diff --git a/src/ui/updatepromoter.cpp b/src/ui/updatepromoter.cpp index 7b9d39d67..f9331a168 100644 --- a/src/ui/updatepromoter.cpp +++ b/src/ui/updatepromoter.cpp @@ -33,6 +33,10 @@ UpdatePromoter::UpdatePromoter(QWidget *parent, NetworkAccessManager *manager) this->resetDialog(); } +UpdatePromoter::~UpdatePromoter() { + delete ui; +} + void UpdatePromoter::resetDialog() { this->button_Downloads->setEnabled(false); diff --git a/src/ui/wildmonchart.cpp b/src/ui/wildmonchart.cpp index 4594e2bd7..32ff9bc92 100644 --- a/src/ui/wildmonchart.cpp +++ b/src/ui/wildmonchart.cpp @@ -31,6 +31,8 @@ WildMonChart::WildMonChart(QWidget *parent, const EncounterTableModel *table) : connect(ui->comboBox_Species, &QComboBox::currentTextChanged, this, &WildMonChart::refreshLevelDistributionChart); connect(ui->comboBox_Group, &QComboBox::currentTextChanged, this, &WildMonChart::refreshLevelDistributionChart); + connect(ui->tabWidget, &QTabWidget::currentChanged, this, &WildMonChart::limitChartAnimation); + // Set up Theme combo box for (auto i : themes) ui->comboBox_Theme->addItem(i.first, i.second); @@ -80,6 +82,7 @@ void WildMonChart::clearTableData() { ui->comboBox_Species->clear(); ui->comboBox_Group->clear(); ui->comboBox_Group->setEnabled(false); + ui->label_Group->setEnabled(false); } // Extract all the data from the table that we need for the charts @@ -150,7 +153,9 @@ void WildMonChart::readTable() { ui->comboBox_Species->addItems(getSpeciesNamesAlphabetical()); ui->comboBox_Group->clear(); ui->comboBox_Group->addItems(this->groupNames); - ui->comboBox_Group->setEnabled(usesGroupLabels()); + bool enableGroupSelection = usesGroupLabels(); + ui->comboBox_Group->setEnabled(enableGroupSelection); + ui->label_Group->setEnabled(enableGroupSelection); } void WildMonChart::refresh() { @@ -176,14 +181,16 @@ void WildMonChart::refreshSpeciesDistributionChart() { if (ui->chartView_SpeciesDistribution->chart()) ui->chartView_SpeciesDistribution->chart()->deleteLater(); ui->chartView_SpeciesDistribution->setChart(createSpeciesDistributionChart()); - limitChartAnimation(ui->chartView_SpeciesDistribution->chart()); + if (ui->tabWidget->currentWidget() == ui->tabSpecies) + limitChartAnimation(); } void WildMonChart::refreshLevelDistributionChart() { if (ui->chartView_LevelDistribution->chart()) ui->chartView_LevelDistribution->chart()->deleteLater(); ui->chartView_LevelDistribution->setChart(createLevelDistributionChart()); - limitChartAnimation(ui->chartView_LevelDistribution->chart()); + if (ui->tabWidget->currentWidget() == ui->tabLevels) + limitChartAnimation(); } QStringList WildMonChart::getSpeciesNamesAlphabetical() const { @@ -218,7 +225,7 @@ bool WildMonChart::usesGroupLabels() const { QChart* WildMonChart::createSpeciesDistributionChart() { QList barSets; - for (const auto species : getSpeciesNamesAlphabetical()) { + for (const auto &species : getSpeciesNamesAlphabetical()) { // Add encounter chance data auto set = new QBarSet(species); for (auto groupName : this->groupNamesReversed) @@ -326,7 +333,7 @@ QChart* WildMonChart::createLevelDistributionChart() { levelRange = getLevelRange(species, groupName); } else { // Species box is inactive, we display data for all species in the table. - for (const auto species : this->speciesInLegendOrder) + for (const auto &species : this->speciesInLegendOrder) barSets.append(createLevelDistributionBarSet(species, groupName, false)); levelRange = this->groupedLevelRanges.value(groupName); } @@ -408,39 +415,59 @@ void WildMonChart::applySpeciesColors(const QList &barSets) { set->setColor(this->speciesToColor.value(set->label())); } -// Turn off the animation once it's played, otherwise it replays any time the window changes size. -void WildMonChart::limitChartAnimation(QChart *chart) { +// Turn off the chart animation once it's played, otherwise it replays any time the window changes size. +// The animation only begins when it's first displayed, so we'll only ever consider the chart for the current tab, +// and when the tab changes we'll call this again. +void WildMonChart::limitChartAnimation() { // Chart may be destroyed before the animation finishes - QPointer safeChart = chart; + QPointer chart; + if (ui->tabWidget->currentWidget() == ui->tabSpecies) { + chart = ui->chartView_SpeciesDistribution->chart(); + } else if (ui->tabWidget->currentWidget() == ui->tabLevels) { + chart = ui->chartView_LevelDistribution->chart(); + } + + if (!chart || chart->animationOptions() == QChart::NoAnimation) + return; // QChart has no signal for when the animation is finished, so we use a timer to stop the animation. // It is technically possible to get the chart to freeze mid-animation by resizing the window after // the timer starts but before it finishes, but 1. animations are short so this is difficult to do, // and 2. the issue resolves if the window is resized afterwards, so it's probably fine. - QTimer::singleShot(chart->animationDuration() + 500, [safeChart] { - if (safeChart) safeChart->setAnimationOptions(QChart::NoAnimation); + QTimer::singleShot(chart->animationDuration(), Qt::PreciseTimer, [chart] { + if (chart) chart->setAnimationOptions(QChart::NoAnimation); }); } void WildMonChart::showHelpDialog() { static const QString text = "This window provides some visualizations of the data in your current Wild Pokémon tab"; - static const QString informative = - "The Species Distribution tab shows the cumulative encounter chance for each species " + + // Describe the Species Distribution tab + static const QString speciesTabInfo = + "The Species Distribution tab shows the cumulative encounter chance for each species " "in the table. In other words, it answers the question \"What is the likelihood of encountering " - "each species in a single encounter?\"" - "

" + "each species in a single encounter?\""; + + // Describe the Level Distribution tab + static const QString levelTabInfo = "The Level Distribution tab shows the chance of encountering each species at a particular level. " "In the top left under Group you can select which encounter group to show data for. " - "In the top right under Species you can select which species to show data for. " + "In the top right you can enable Individual Mode. When enabled data will be shown for only the selected species." "

" - "Individual Mode on the Level Distribution tab toggles whether data is shown for all species in the table. " - "The percentages will update to reflect whether you're showing all species or just that individual species. " "In other words, while Individual Mode is checked the chart is answering the question \"If a species x " "is encountered, what is the likelihood that it will be level y\", and while Individual Mode is not checked, " "it answers the question \"For a single encounter, what is the likelihood of encountering a species x at level y.\""; + + QString informativeText; + if (ui->tabWidget->currentWidget() == ui->tabSpecies) { + informativeText = speciesTabInfo; + } else if (ui->tabWidget->currentWidget() == ui->tabLevels) { + informativeText = levelTabInfo; + } + QMessageBox msgBox(QMessageBox::Information, "porymap", text, QMessageBox::Close, this); msgBox.setTextFormat(Qt::RichText); - msgBox.setInformativeText(informative); + msgBox.setInformativeText(informativeText); msgBox.exec(); } diff --git a/src/vendor/QtGifImage/gifimage/qgifimage.cpp b/src/vendor/QtGifImage/gifimage/qgifimage.cpp index 46748d945..b607c7e0b 100644 --- a/src/vendor/QtGifImage/gifimage/qgifimage.cpp +++ b/src/vendor/QtGifImage/gifimage/qgifimage.cpp @@ -136,7 +136,7 @@ bool QGifImagePrivate::load(QIODevice *device) int error; GifFileType *gifFile = DGifOpen(device, readFromIODevice, &error); if (!gifFile) { - qWarning(GifErrorString(error)); + qWarning("%s", GifErrorString(error)); return false; } @@ -228,7 +228,7 @@ bool QGifImagePrivate::save(QIODevice *device) const int error; GifFileType *gifFile = EGifOpen(device, writeToIODevice, &error); if (!gifFile) { - qWarning(GifErrorString(error)); + qWarning("%s", GifErrorString(error)); return false; }