From 4b99b6e6ea8e87c8bb9dc370cc8922ec426a0c43 Mon Sep 17 00:00:00 2001 From: Adam Belis Date: Thu, 24 Jul 2025 19:18:09 +0000 Subject: [PATCH 01/12] Update 2 files - /src/ui/dialog/align-and-distribute.cpp - /src/ui/dialog/align-and-distribute.h --- src/ui/dialog/align-and-distribute.cpp | 223 ++++++++++++++++++++++++- src/ui/dialog/align-and-distribute.h | 123 ++++++++------ 2 files changed, 289 insertions(+), 57 deletions(-) diff --git a/src/ui/dialog/align-and-distribute.cpp b/src/ui/dialog/align-and-distribute.cpp index 25521d6515..2a9d348ed9 100644 --- a/src/ui/dialog/align-and-distribute.cpp +++ b/src/ui/dialog/align-and-distribute.cpp @@ -32,6 +32,9 @@ #include "ui/builder-utils.h" #include "ui/dialog/dialog-base.h" // Tool switching. #include "ui/util.h" +#include "message-stack.h" // For status messages +#include "object/sp-item.h" // For SPItem cast +#include "util/cast.h" // For cast function namespace Inkscape::UI::Dialog { @@ -122,8 +125,19 @@ AlignAndDistribute::AlignAndDistribute(Inkscape::UI::Dialog::DialogBase *dlg) for (auto align_button : align_buttons) { auto &button = get_widget(builder, align_button.first); + + // Connect click handler button.signal_clicked().connect( sigc::bind(sigc::mem_fun(*this, &AlignAndDistribute::on_align_clicked), align_button.second)); + + // Connect hover handlers for preview + button.signal_enter_notify_event().connect( + sigc::bind(sigc::mem_fun(*this, &AlignAndDistribute::on_button_hover_enter), align_button.second)); + button.signal_leave_notify_event().connect( + sigc::mem_fun(*this, &AlignAndDistribute::on_button_hover_leave)); + + // Enable events on button + button.add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK); } // ------------ Remove overlap ------------- @@ -165,6 +179,24 @@ AlignAndDistribute::AlignAndDistribute(Inkscape::UI::Dialog::DialogBase *dlg) // dialog based icon sizes, perhaps done via css instead. _icon_sizes_changed = prefs->createObserver("/toolbox/tools/iconsize", set_icon_size_prefs); set_icon_size_prefs(); + + // Initialize hover preview preference if not set + if (!prefs->getEntry("/dialogs/align/enable-hover-preview")) { + prefs->setBool("/dialogs/align/enable-hover-preview", true); + } +} + +AlignAndDistribute::~AlignAndDistribute() +{ + // Clean up any active preview + if (_preview_active) { + end_preview(); + } + + // Disconnect timeout connection + if (_preview_timeout_connection.connected()) { + _preview_timeout_connection.disconnect(); + } } void @@ -232,8 +264,27 @@ AlignAndDistribute::on_align_relative_node_changed() void AlignAndDistribute::on_align_clicked(std::string const &align_to) { + // If preview is active, we just confirm it (transforms already applied) + if (_preview_active) { + _preview_active = false; + + // Clear preview data but don't restore (we want to keep the alignment) + _original_transforms.clear(); + _preview_objects.clear(); + + // Clear status message + auto win = InkscapeApplication::instance()->get_active_window(); + if (win) { + if (auto desktop = win->get_desktop()) { + desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, ""); + } + } + + return; // Alignment is already applied, just confirm it + } + + // Normal operation (no preview was active) Glib::ustring argument = align_to; - argument += " " + align_relative_object.get_active_id(); if (align_move_as_group.get_active()) { @@ -280,6 +331,174 @@ AlignAndDistribute::on_align_node_clicked(std::string const &direction) } } +// ================== HOVER PREVIEW METHODS ================== + +bool +AlignAndDistribute::on_button_hover_enter(GdkEventCrossing* event, const std::string& action) +{ + // Check if preview is enabled in preferences + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool preview_enabled = prefs->getBool("/dialogs/align/enable-hover-preview", true); + + if (!preview_enabled) { + return false; + } + + // Cancel any existing timeout + if (_preview_timeout_connection.connected()) { + _preview_timeout_connection.disconnect(); + } + + // Store the action for the timeout + _preview_action = action; + + // Start preview after 300ms delay to avoid flickering + _preview_timeout_connection = Glib::signal_timeout().connect( + sigc::mem_fun(*this, &AlignAndDistribute::start_preview_timeout), + 300 + ); + + return false; +} + +bool +AlignAndDistribute::on_button_hover_leave(GdkEventCrossing* event) +{ + // Cancel pending preview + if (_preview_timeout_connection.connected()) { + _preview_timeout_connection.disconnect(); + } + + // End current preview + end_preview(); + + return false; +} + +void +AlignAndDistribute::start_preview_timeout() +{ + start_preview(_preview_action); + _preview_timeout_connection.disconnect(); +} + +void +AlignAndDistribute::start_preview(const std::string& action) +{ + // Get current window and desktop + auto win = InkscapeApplication::instance()->get_active_window(); + if (!win) return; + + auto desktop = win->get_desktop(); + if (!desktop) return; + + auto selection = desktop->getSelection(); + if (!selection || selection->isEmpty()) { + return; + } + + // If preview is already active, end it first + if (_preview_active) { + end_preview(); + } + + // Store original transforms + store_original_transforms(); + + // Perform preview alignment (without committing to undo stack) + Glib::ustring argument = action; + argument += " " + align_relative_object.get_active_id(); + + if (align_move_as_group.get_active()) { + argument += " group"; + } + + // Create the action variant + auto variant = Glib::Variant::create(argument); + auto app = Gio::Application::get_default(); + + // Set preview flag + _preview_active = true; + + // Execute alignment action + if (action.find("vertical") != Glib::ustring::npos || action.find("horizontal") != Glib::ustring::npos) { + app->activate_action("object-align-text", variant); + } else { + app->activate_action("object-align", variant); + } + + // Update status + desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, + _("Preview active - click to confirm, move mouse away to cancel")); +} + +void +AlignAndDistribute::end_preview() +{ + if (!_preview_active) { + return; + } + + // Restore original transforms + restore_original_transforms(); + + _preview_active = false; + + // Clear status message + auto win = InkscapeApplication::instance()->get_active_window(); + if (win) { + if (auto desktop = win->get_desktop()) { + desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, ""); + } + } +} + +void +AlignAndDistribute::store_original_transforms() +{ + _original_transforms.clear(); + _preview_objects.clear(); + + auto win = InkscapeApplication::instance()->get_active_window(); + if (!win) return; + + auto desktop = win->get_desktop(); + if (!desktop) return; + + auto selection = desktop->getSelection(); + if (!selection) return; + + auto items = selection->itemList(); + for (auto item : items) { + _preview_objects.push_back(item); + _original_transforms.push_back(item->getTransform()); + } +} + +void +AlignAndDistribute::restore_original_transforms() +{ + auto win = InkscapeApplication::instance()->get_active_window(); + if (!win) return; + + auto desktop = win->get_desktop(); + if (!desktop) return; + + // Restore transforms + for (size_t i = 0; i < _preview_objects.size() && i < _original_transforms.size(); ++i) { + if (auto item = cast(_preview_objects[i])) { + item->setTransform(_original_transforms[i]); + } + } + + // Force canvas update + desktop->getCanvas()->redraw_all(); + + // Clear stored data + _original_transforms.clear(); + _preview_objects.clear(); +} + } // namespace Inkscape::UI::Dialog /* @@ -291,4 +510,4 @@ AlignAndDistribute::on_align_node_clicked(std::string const &direction) fill-column:99 End: */ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : \ No newline at end of file diff --git a/src/ui/dialog/align-and-distribute.h b/src/ui/dialog/align-and-distribute.h index 3503cf5a3a..85a437ada3 100644 --- a/src/ui/dialog/align-and-distribute.h +++ b/src/ui/dialog/align-and-distribute.h @@ -16,90 +16,103 @@ * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ -#ifndef INKSCAPE_UI_WIDGET_ALIGN_AND_DISTRIBUTE_H -#define INKSCAPE_UI_WIDGET_ALIGN_AND_DISTRIBUTE_H +#ifndef INKSCAPE_UI_DIALOG_ALIGN_AND_DISTRIBUTE_H +#define INKSCAPE_UI_DIALOG_ALIGN_AND_DISTRIBUTE_H + +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include "2geom/affine.h" #include "preferences.h" - -namespace Gtk { -class Builder; -class Button; -class ComboBox; -class Frame; -class SpinButton; -class ToggleButton; -} // namespace Gtk +#include "ui/tools/tool-base.h" class SPDesktop; +class SPObject; -namespace Inkscape { -namespace UI { - -namespace Tools { -class ToolBase; -} +namespace Inkscape::UI::Dialog { -namespace Dialog { class DialogBase; class AlignAndDistribute : public Gtk::Box { public: - AlignAndDistribute(Inkscape::UI::Dialog::DialogBase* dlg); - ~AlignAndDistribute() override = default; + AlignAndDistribute(Inkscape::UI::Dialog::DialogBase *dlg); + ~AlignAndDistribute(); void desktop_changed(SPDesktop* desktop); - void tool_changed(SPDesktop* desktop); // Need to show different widgets for node vs. other tools. - void tool_changed_callback(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* tool); private: + void tool_changed(SPDesktop* desktop); + void tool_changed_callback(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* tool); - // ********* Widgets ********** // + void on_align_as_group_clicked(); + void on_align_relative_object_changed(); + void on_align_relative_node_changed(); + void on_align_clicked(std::string const &align_to); + void on_remove_overlap_clicked(); + void on_align_node_clicked(std::string const &direction); + + // Preview functionality + bool _preview_active = false; + std::string _preview_action; + std::vector _original_transforms; + std::vector _preview_objects; + sigc::connection _preview_timeout_connection; + + // Preview methods + void start_preview_timeout(); + void start_preview(const std::string& action); + void end_preview(); + void store_original_transforms(); + void restore_original_transforms(); + bool on_button_hover_enter(GdkEventCrossing* event, const std::string& action); + bool on_button_hover_leave(GdkEventCrossing* event); + + // UI Glib::RefPtr builder; - Gtk::Box &align_and_distribute_box; - Gtk::Box &align_and_distribute_object; // Hidden when node tool active. - Gtk::Frame &remove_overlap_frame; // Hidden when node tool active. - Gtk::Box &align_and_distribute_node; // Visible when node tool active. + Gtk::Box &align_and_distribute_object; + Gtk::Frame &remove_overlap_frame; + Gtk::Box &align_and_distribute_node; - // Align + // Object align + Gtk::ComboBox &align_relative_object; Gtk::ToggleButton &align_move_as_group; - Gtk::ComboBox &align_relative_object; - Gtk::ComboBox &align_relative_node; // Remove overlap - Gtk::Button &remove_overlap_button; - Gtk::SpinButton &remove_overlap_hgap; - Gtk::SpinButton &remove_overlap_vgap; - - // Valid relative alignment entries for single selection. - std::set single_selection_relative_categories = {"drawing", "page"}; - Glib::ustring single_selection_align_to = "page"; - Glib::ustring multi_selection_align_to; - bool single_item = false; + Gtk::Button &remove_overlap_button; + Gtk::SpinButton &remove_overlap_hgap; + Gtk::SpinButton &remove_overlap_vgap; - // ********* Signal handlers ********** // - - void on_align_as_group_clicked(); - void on_align_relative_object_changed(); - void on_align_relative_node_changed(); - - void on_align_clicked (std::string const &align_to); - void on_remove_overlap_clicked(); - void on_align_node_clicked (std::string const &align_to); + // Node + Gtk::ComboBox &align_relative_node; + // State sigc::connection tool_connection; - sigc::scoped_connection sel_changed; - Inkscape::PrefObserver _icon_sizes_changed; + sigc::connection sel_changed; + sigc::connection _icon_sizes_changed; + + bool single_item = false; + std::string single_selection_align_to = "first"; + std::string multi_selection_align_to = "selection"; + + std::set single_selection_relative_categories = { + "first", "last", "biggest", "smallest" + }; }; -} // namespace Dialog -} // namespace UI -} // namespace Inkscape +} // namespace Inkscape::UI::Dialog -#endif // INKSCAPE_UI_WIDGET_ALIGN_AND_DISTRIBUTE_H +#endif // INKSCAPE_UI_DIALOG_ALIGN_AND_DISTRIBUTE_H /* Local Variables: @@ -110,4 +123,4 @@ private: fill-column:99 End: */ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : \ No newline at end of file -- GitLab From 216c605a824f9737f8243f3c1ad4b936e4cc11d8 Mon Sep 17 00:00:00 2001 From: Adam Belis Date: Thu, 24 Jul 2025 19:25:22 +0000 Subject: [PATCH 02/12] Update 2 files - /src/ui/dialog/align-and-distribute.h - /src/ui/dialog/align-and-distribute.cpp --- src/ui/dialog/align-and-distribute.cpp | 3 ++- src/ui/dialog/align-and-distribute.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ui/dialog/align-and-distribute.cpp b/src/ui/dialog/align-and-distribute.cpp index 2a9d348ed9..686b386ea9 100644 --- a/src/ui/dialog/align-and-distribute.cpp +++ b/src/ui/dialog/align-and-distribute.cpp @@ -375,11 +375,12 @@ AlignAndDistribute::on_button_hover_leave(GdkEventCrossing* event) return false; } -void +bool AlignAndDistribute::start_preview_timeout() { start_preview(_preview_action); _preview_timeout_connection.disconnect(); + return false; // Return false to disconnect the timeout } void diff --git a/src/ui/dialog/align-and-distribute.h b/src/ui/dialog/align-and-distribute.h index 85a437ada3..62e11daf5c 100644 --- a/src/ui/dialog/align-and-distribute.h +++ b/src/ui/dialog/align-and-distribute.h @@ -69,7 +69,7 @@ private: sigc::connection _preview_timeout_connection; // Preview methods - void start_preview_timeout(); + bool start_preview_timeout(); void start_preview(const std::string& action); void end_preview(); void store_original_transforms(); -- GitLab From 2aad0d3dbb5f4b2bd1af7ef6f73e82b32783eeae Mon Sep 17 00:00:00 2001 From: Adam Belis Date: Thu, 24 Jul 2025 19:40:57 +0000 Subject: [PATCH 03/12] Update 2 files - /src/ui/dialog/align-and-distribute.h - /src/ui/dialog/align-and-distribute.cpp --- src/ui/dialog/align-and-distribute.cpp | 35 +++++++++++++------------- src/ui/dialog/align-and-distribute.h | 8 +++--- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/ui/dialog/align-and-distribute.cpp b/src/ui/dialog/align-and-distribute.cpp index 686b386ea9..4dc86e0eaa 100644 --- a/src/ui/dialog/align-and-distribute.cpp +++ b/src/ui/dialog/align-and-distribute.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "actions/actions-tools.h" // Tool switching. #include "desktop.h" // Tool switching. @@ -35,6 +36,7 @@ #include "message-stack.h" // For status messages #include "object/sp-item.h" // For SPItem cast #include "util/cast.h" // For cast function +#include "ui/widget/canvas.h" // For canvas access namespace Inkscape::UI::Dialog { @@ -130,14 +132,17 @@ AlignAndDistribute::AlignAndDistribute(Inkscape::UI::Dialog::DialogBase *dlg) button.signal_clicked().connect( sigc::bind(sigc::mem_fun(*this, &AlignAndDistribute::on_align_clicked), align_button.second)); + // Create motion controller for hover events (GTK4 way) + auto motion_controller = Gtk::EventControllerMotion::create(); + // Connect hover handlers for preview - button.signal_enter_notify_event().connect( + motion_controller->signal_enter().connect( sigc::bind(sigc::mem_fun(*this, &AlignAndDistribute::on_button_hover_enter), align_button.second)); - button.signal_leave_notify_event().connect( + motion_controller->signal_leave().connect( sigc::mem_fun(*this, &AlignAndDistribute::on_button_hover_leave)); - // Enable events on button - button.add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK); + // Add controller to button + button.add_controller(motion_controller); } // ------------ Remove overlap ------------- @@ -181,7 +186,7 @@ AlignAndDistribute::AlignAndDistribute(Inkscape::UI::Dialog::DialogBase *dlg) set_icon_size_prefs(); // Initialize hover preview preference if not set - if (!prefs->getEntry("/dialogs/align/enable-hover-preview")) { + if (prefs->getEntry("/dialogs/align/enable-hover-preview").isEmpty()) { prefs->setBool("/dialogs/align/enable-hover-preview", true); } } @@ -333,15 +338,15 @@ AlignAndDistribute::on_align_node_clicked(std::string const &direction) // ================== HOVER PREVIEW METHODS ================== -bool -AlignAndDistribute::on_button_hover_enter(GdkEventCrossing* event, const std::string& action) +void +AlignAndDistribute::on_button_hover_enter(const std::string& action) { // Check if preview is enabled in preferences Inkscape::Preferences *prefs = Inkscape::Preferences::get(); bool preview_enabled = prefs->getBool("/dialogs/align/enable-hover-preview", true); if (!preview_enabled) { - return false; + return; } // Cancel any existing timeout @@ -357,12 +362,10 @@ AlignAndDistribute::on_button_hover_enter(GdkEventCrossing* event, const std::st sigc::mem_fun(*this, &AlignAndDistribute::start_preview_timeout), 300 ); - - return false; } -bool -AlignAndDistribute::on_button_hover_leave(GdkEventCrossing* event) +void +AlignAndDistribute::on_button_hover_leave() { // Cancel pending preview if (_preview_timeout_connection.connected()) { @@ -371,8 +374,6 @@ AlignAndDistribute::on_button_hover_leave(GdkEventCrossing* event) // End current preview end_preview(); - - return false; } bool @@ -430,7 +431,7 @@ AlignAndDistribute::start_preview(const std::string& action) // Update status desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, - _("Preview active - click to confirm, move mouse away to cancel")); + "Preview active - click to confirm, move mouse away to cancel"); } void @@ -469,7 +470,7 @@ AlignAndDistribute::store_original_transforms() auto selection = desktop->getSelection(); if (!selection) return; - auto items = selection->itemList(); + auto items = selection->items(); for (auto item : items) { _preview_objects.push_back(item); _original_transforms.push_back(item->getTransform()); @@ -488,7 +489,7 @@ AlignAndDistribute::restore_original_transforms() // Restore transforms for (size_t i = 0; i < _preview_objects.size() && i < _original_transforms.size(); ++i) { if (auto item = cast(_preview_objects[i])) { - item->setTransform(_original_transforms[i]); + item->set_transform(_original_transforms[i]); } } diff --git a/src/ui/dialog/align-and-distribute.h b/src/ui/dialog/align-and-distribute.h index 62e11daf5c..eebda49cd9 100644 --- a/src/ui/dialog/align-and-distribute.h +++ b/src/ui/dialog/align-and-distribute.h @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include #include @@ -74,8 +76,8 @@ private: void end_preview(); void store_original_transforms(); void restore_original_transforms(); - bool on_button_hover_enter(GdkEventCrossing* event, const std::string& action); - bool on_button_hover_leave(GdkEventCrossing* event); + void on_button_hover_enter(const std::string& action); + void on_button_hover_leave(); // UI Glib::RefPtr builder; @@ -99,7 +101,7 @@ private: // State sigc::connection tool_connection; sigc::connection sel_changed; - sigc::connection _icon_sizes_changed; + std::unique_ptr _icon_sizes_changed; bool single_item = false; std::string single_selection_align_to = "first"; -- GitLab From 54b702e2e44cff02315949ab8b1336983878b8a5 Mon Sep 17 00:00:00 2001 From: Adam Belis Date: Thu, 24 Jul 2025 19:50:58 +0000 Subject: [PATCH 04/12] Update file align-and-distribute.cpp --- src/ui/dialog/align-and-distribute.cpp | 73 +++++++++++--------------- 1 file changed, 30 insertions(+), 43 deletions(-) diff --git a/src/ui/dialog/align-and-distribute.cpp b/src/ui/dialog/align-and-distribute.cpp index 4dc86e0eaa..7ed54fe021 100644 --- a/src/ui/dialog/align-and-distribute.cpp +++ b/src/ui/dialog/align-and-distribute.cpp @@ -135,11 +135,36 @@ AlignAndDistribute::AlignAndDistribute(Inkscape::UI::Dialog::DialogBase *dlg) // Create motion controller for hover events (GTK4 way) auto motion_controller = Gtk::EventControllerMotion::create(); - // Connect hover handlers for preview - motion_controller->signal_enter().connect( - sigc::bind(sigc::mem_fun(*this, &AlignAndDistribute::on_button_hover_enter), align_button.second)); - motion_controller->signal_leave().connect( - sigc::mem_fun(*this, &AlignAndDistribute::on_button_hover_leave)); + // Store action string with controller for later use + std::string action(align_button.second); + _motion_controllers[action] = motion_controller; + + // Connect hover handlers - GTK4 style (no parameters from signals) + motion_controller->signal_enter().connect([this, action]() { + // Check if preview is enabled + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool preview_enabled = prefs->getBool("/dialogs/align/enable-hover-preview", true); + if (!preview_enabled) return; + + // Cancel any existing timeout + if (_preview_timeout_connection.connected()) { + _preview_timeout_connection.disconnect(); + } + + // Store action and start timeout + _preview_action = action; + _preview_timeout_connection = Glib::signal_timeout().connect( + sigc::mem_fun(*this, &AlignAndDistribute::start_preview_timeout), 300); + }); + + motion_controller->signal_leave().connect([this]() { + // Cancel pending preview + if (_preview_timeout_connection.connected()) { + _preview_timeout_connection.disconnect(); + } + // End current preview + end_preview(); + }); // Add controller to button button.add_controller(motion_controller); @@ -338,44 +363,6 @@ AlignAndDistribute::on_align_node_clicked(std::string const &direction) // ================== HOVER PREVIEW METHODS ================== -void -AlignAndDistribute::on_button_hover_enter(const std::string& action) -{ - // Check if preview is enabled in preferences - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - bool preview_enabled = prefs->getBool("/dialogs/align/enable-hover-preview", true); - - if (!preview_enabled) { - return; - } - - // Cancel any existing timeout - if (_preview_timeout_connection.connected()) { - _preview_timeout_connection.disconnect(); - } - - // Store the action for the timeout - _preview_action = action; - - // Start preview after 300ms delay to avoid flickering - _preview_timeout_connection = Glib::signal_timeout().connect( - sigc::mem_fun(*this, &AlignAndDistribute::start_preview_timeout), - 300 - ); -} - -void -AlignAndDistribute::on_button_hover_leave() -{ - // Cancel pending preview - if (_preview_timeout_connection.connected()) { - _preview_timeout_connection.disconnect(); - } - - // End current preview - end_preview(); -} - bool AlignAndDistribute::start_preview_timeout() { -- GitLab From 70daf0b1b64a8da0159f2dfb4b6b206cb1f96337 Mon Sep 17 00:00:00 2001 From: Adam Belis Date: Fri, 25 Jul 2025 09:53:09 +0000 Subject: [PATCH 05/12] Update 2 files - /src/ui/dialog/align-and-distribute.h - /src/ui/dialog/align-and-distribute.cpp --- src/ui/dialog/align-and-distribute.cpp | 9 +++++---- src/ui/dialog/align-and-distribute.h | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/ui/dialog/align-and-distribute.cpp b/src/ui/dialog/align-and-distribute.cpp index 7ed54fe021..ccde7ff2e2 100644 --- a/src/ui/dialog/align-and-distribute.cpp +++ b/src/ui/dialog/align-and-distribute.cpp @@ -139,8 +139,8 @@ AlignAndDistribute::AlignAndDistribute(Inkscape::UI::Dialog::DialogBase *dlg) std::string action(align_button.second); _motion_controllers[action] = motion_controller; - // Connect hover handlers - GTK4 style (no parameters from signals) - motion_controller->signal_enter().connect([this, action]() { + // Connect hover handlers - GTK4 style with proper lambda signatures + motion_controller->signal_enter().connect([this, action](double /*x*/, double /*y*/) { // Check if preview is enabled Inkscape::Preferences *prefs = Inkscape::Preferences::get(); bool preview_enabled = prefs->getBool("/dialogs/align/enable-hover-preview", true); @@ -211,7 +211,8 @@ AlignAndDistribute::AlignAndDistribute(Inkscape::UI::Dialog::DialogBase *dlg) set_icon_size_prefs(); // Initialize hover preview preference if not set - if (prefs->getEntry("/dialogs/align/enable-hover-preview").isEmpty()) { + auto entry = prefs->getEntry("/dialogs/align/enable-hover-preview"); + if (!entry.isValid() || entry.getString().empty()) { prefs->setBool("/dialogs/align/enable-hover-preview", true); } } @@ -460,7 +461,7 @@ AlignAndDistribute::store_original_transforms() auto items = selection->items(); for (auto item : items) { _preview_objects.push_back(item); - _original_transforms.push_back(item->getTransform()); + _original_transforms.push_back(item->transform); } } diff --git a/src/ui/dialog/align-and-distribute.h b/src/ui/dialog/align-and-distribute.h index eebda49cd9..6e900c4b73 100644 --- a/src/ui/dialog/align-and-distribute.h +++ b/src/ui/dialog/align-and-distribute.h @@ -31,6 +31,7 @@ #include #include +#include #include #include "2geom/affine.h" @@ -69,6 +70,7 @@ private: std::vector _original_transforms; std::vector _preview_objects; sigc::connection _preview_timeout_connection; + std::map> _motion_controllers; // Preview methods bool start_preview_timeout(); @@ -76,8 +78,6 @@ private: void end_preview(); void store_original_transforms(); void restore_original_transforms(); - void on_button_hover_enter(const std::string& action); - void on_button_hover_leave(); // UI Glib::RefPtr builder; @@ -101,7 +101,7 @@ private: // State sigc::connection tool_connection; sigc::connection sel_changed; - std::unique_ptr _icon_sizes_changed; + std::unique_ptr _icon_sizes_changed; bool single_item = false; std::string single_selection_align_to = "first"; -- GitLab From 660b0993e29f14dc3c6a5f99dd319c833fa8ec10 Mon Sep 17 00:00:00 2001 From: Adam Belis Date: Fri, 25 Jul 2025 10:04:01 +0000 Subject: [PATCH 06/12] Update file align-and-distribute.cpp --- src/ui/dialog/align-and-distribute.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ui/dialog/align-and-distribute.cpp b/src/ui/dialog/align-and-distribute.cpp index ccde7ff2e2..42e39cffbc 100644 --- a/src/ui/dialog/align-and-distribute.cpp +++ b/src/ui/dialog/align-and-distribute.cpp @@ -211,8 +211,7 @@ AlignAndDistribute::AlignAndDistribute(Inkscape::UI::Dialog::DialogBase *dlg) set_icon_size_prefs(); // Initialize hover preview preference if not set - auto entry = prefs->getEntry("/dialogs/align/enable-hover-preview"); - if (!entry.isValid() || entry.getString().empty()) { + if (prefs->getString("/dialogs/align/enable-hover-preview", "").empty()) { prefs->setBool("/dialogs/align/enable-hover-preview", true); } } -- GitLab From ddc2700f200b21343f28156a625c5cd794cb1c8a Mon Sep 17 00:00:00 2001 From: Adam Belis Date: Fri, 25 Jul 2025 20:27:34 +0000 Subject: [PATCH 07/12] Update 2 files - /src/ui/dialog/align-and-distribute.cpp - /src/ui/dialog/align-and-distribute.h --- src/ui/dialog/align-and-distribute.cpp | 431 ++++++++++++++++++++----- src/ui/dialog/align-and-distribute.h | 8 +- 2 files changed, 346 insertions(+), 93 deletions(-) diff --git a/src/ui/dialog/align-and-distribute.cpp b/src/ui/dialog/align-and-distribute.cpp index 42e39cffbc..c1f724aa93 100644 --- a/src/ui/dialog/align-and-distribute.cpp +++ b/src/ui/dialog/align-and-distribute.cpp @@ -37,6 +37,8 @@ #include "object/sp-item.h" // For SPItem cast #include "util/cast.h" // For cast function #include "ui/widget/canvas.h" // For canvas access +#include "object/algorithms/removeoverlap.h" // For remove overlap preview +#include "2geom/rect.h" // For bounding box calculations namespace Inkscape::UI::Dialog { @@ -126,54 +128,44 @@ AlignAndDistribute::AlignAndDistribute(Inkscape::UI::Dialog::DialogBase *dlg) // clang-format on for (auto align_button : align_buttons) { - auto &button = get_widget(builder, align_button.first); - - // Connect click handler - button.signal_clicked().connect( - sigc::bind(sigc::mem_fun(*this, &AlignAndDistribute::on_align_clicked), align_button.second)); - - // Create motion controller for hover events (GTK4 way) - auto motion_controller = Gtk::EventControllerMotion::create(); - - // Store action string with controller for later use - std::string action(align_button.second); - _motion_controllers[action] = motion_controller; - - // Connect hover handlers - GTK4 style with proper lambda signatures - motion_controller->signal_enter().connect([this, action](double /*x*/, double /*y*/) { - // Check if preview is enabled - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - bool preview_enabled = prefs->getBool("/dialogs/align/enable-hover-preview", true); - if (!preview_enabled) return; - - // Cancel any existing timeout - if (_preview_timeout_connection.connected()) { - _preview_timeout_connection.disconnect(); - } - - // Store action and start timeout - _preview_action = action; - _preview_timeout_connection = Glib::signal_timeout().connect( - sigc::mem_fun(*this, &AlignAndDistribute::start_preview_timeout), 300); - }); - - motion_controller->signal_leave().connect([this]() { - // Cancel pending preview - if (_preview_timeout_connection.connected()) { - _preview_timeout_connection.disconnect(); - } - // End current preview - end_preview(); - }); - - // Add controller to button - button.add_controller(motion_controller); + setup_hover_preview_for_button(align_button.first, align_button.second, + [this](const std::string& action) { preview_align(action); }); } - // ------------ Remove overlap ------------- + // ------------ Distribution buttons ------------- + std::vector> distribute_buttons = { + {"distribute-horizontal-left", "distribute-left" }, + {"distribute-horizontal-center", "distribute-hcenter" }, + {"distribute-horizontal-right", "distribute-right" }, + {"distribute-horizontal-gaps", "distribute-hgaps" }, + {"distribute-vertical-top", "distribute-top" }, + {"distribute-vertical-center", "distribute-vcenter" }, + {"distribute-vertical-bottom", "distribute-bottom" }, + {"distribute-vertical-gaps", "distribute-vgaps" } + }; + + for (auto distribute_button : distribute_buttons) { + setup_hover_preview_for_button(distribute_button.first, distribute_button.second, + [this](const std::string& action) { preview_distribute(action); }); + } - remove_overlap_button.signal_clicked().connect( - sigc::mem_fun(*this, &AlignAndDistribute::on_remove_overlap_clicked)); + // ------------ Rearrange buttons ------------- + std::vector> rearrange_buttons = { + {"rearrange-graph", "rearrange-graph" }, + {"exchange-positions", "exchange-positions" }, + {"exchange-positions-clockwise", "exchange-clockwise" }, + {"exchange-positions-random", "exchange-random" }, + {"unclump", "unclump" } + }; + + for (auto rearrange_button : rearrange_buttons) { + setup_hover_preview_for_button(rearrange_button.first, rearrange_button.second, + [this](const std::string& action) { preview_rearrange(action); }); + } + + // ------------ Remove overlap ------------- + setup_hover_preview_for_button("remove-overlap-button", "remove-overlap", + [this](const std::string& action) { preview_remove_overlap(); }); // ------------ Node Align ------------- @@ -229,6 +221,55 @@ AlignAndDistribute::~AlignAndDistribute() } } +void +AlignAndDistribute::setup_hover_preview_for_button(const char* button_id, const char* action_name, + std::function preview_func) +{ + auto &button = get_widget(builder, button_id); + + // Connect click handler + button.signal_clicked().connect( + sigc::bind(sigc::mem_fun(*this, &AlignAndDistribute::on_button_clicked), std::string(action_name))); + + // Create motion controller for hover events + auto motion_controller = Gtk::EventControllerMotion::create(); + + // Store action string with controller + std::string action(action_name); + _motion_controllers[action] = motion_controller; + + // Connect hover handlers + motion_controller->signal_enter().connect([this, action, preview_func](double /*x*/, double /*y*/) { + // Check if preview is enabled + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool preview_enabled = prefs->getBool("/dialogs/align/enable-hover-preview", true); + if (!preview_enabled) return; + + // Cancel any existing timeout + if (_preview_timeout_connection.connected()) { + _preview_timeout_connection.disconnect(); + } + + // Store action and start timeout + _preview_action = action; + _preview_func = preview_func; + _preview_timeout_connection = Glib::signal_timeout().connect( + sigc::mem_fun(*this, &AlignAndDistribute::start_preview_timeout), 300); + }); + + motion_controller->signal_leave().connect([this]() { + // Cancel pending preview + if (_preview_timeout_connection.connected()) { + _preview_timeout_connection.disconnect(); + } + // End current preview + end_preview(); + }); + + // Add controller to button + button.add_controller(motion_controller); +} + void AlignAndDistribute::desktop_changed(SPDesktop* desktop) { @@ -255,7 +296,6 @@ AlignAndDistribute::tool_changed_callback(SPDesktop* desktop, Inkscape::UI::Tool tool_changed(desktop); } - void AlignAndDistribute::on_align_as_group_clicked() { @@ -292,28 +332,61 @@ AlignAndDistribute::on_align_relative_node_changed() } void -AlignAndDistribute::on_align_clicked(std::string const &align_to) +AlignAndDistribute::on_button_clicked(std::string const &action) { - // If preview is active, we just confirm it (transforms already applied) - if (_preview_active) { - _preview_active = false; - - // Clear preview data but don't restore (we want to keep the alignment) - _original_transforms.clear(); - _preview_objects.clear(); - - // Clear status message - auto win = InkscapeApplication::instance()->get_active_window(); - if (win) { - if (auto desktop = win->get_desktop()) { - desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, ""); - } - } - - return; // Alignment is already applied, just confirm it + // If preview is active, we just confirm it (transforms already applied in preview) + if (_preview_active && _preview_action == action) { + confirm_preview(); + return; + } + + // Normal operation (no preview was active) - execute the actual command + execute_action(action); +} + +void +AlignAndDistribute::execute_action(const std::string& action) +{ + if (action.find("distribute") != std::string::npos) { + execute_distribute_action(action); + } else if (action == "remove-overlap") { + on_remove_overlap_clicked(); + } else if (action.find("rearrange") != std::string::npos || + action.find("exchange") != std::string::npos || + action == "unclump") { + execute_rearrange_action(action); + } else { + // Alignment action + on_align_clicked(action); } +} + +void +AlignAndDistribute::execute_distribute_action(const std::string& action) +{ + auto app = Gio::Application::get_default(); + auto variant = Glib::Variant::create(action); + app->activate_action("object-distribute", variant); +} + +void +AlignAndDistribute::execute_rearrange_action(const std::string& action) +{ + auto app = Gio::Application::get_default(); + auto variant = Glib::Variant::create(action); - // Normal operation (no preview was active) + if (action == "rearrange-graph") { + app->activate_action("object-rearrange-graph", variant); + } else if (action.find("exchange") != std::string::npos) { + app->activate_action("object-exchange-positions", variant); + } else if (action == "unclump") { + app->activate_action("object-unclump", variant); + } +} + +void +AlignAndDistribute::on_align_clicked(std::string const &align_to) +{ Glib::ustring argument = align_to; argument += " " + align_relative_object.get_active_id(); @@ -366,15 +439,16 @@ AlignAndDistribute::on_align_node_clicked(std::string const &direction) bool AlignAndDistribute::start_preview_timeout() { - start_preview(_preview_action); + if (_preview_func) { + start_preview(); + } _preview_timeout_connection.disconnect(); - return false; // Return false to disconnect the timeout + return false; } void -AlignAndDistribute::start_preview(const std::string& action) +AlignAndDistribute::start_preview() { - // Get current window and desktop auto win = InkscapeApplication::instance()->get_active_window(); if (!win) return; @@ -394,26 +468,12 @@ AlignAndDistribute::start_preview(const std::string& action) // Store original transforms store_original_transforms(); - // Perform preview alignment (without committing to undo stack) - Glib::ustring argument = action; - argument += " " + align_relative_object.get_active_id(); - - if (align_move_as_group.get_active()) { - argument += " group"; - } - - // Create the action variant - auto variant = Glib::Variant::create(argument); - auto app = Gio::Application::get_default(); - // Set preview flag _preview_active = true; - // Execute alignment action - if (action.find("vertical") != Glib::ustring::npos || action.find("horizontal") != Glib::ustring::npos) { - app->activate_action("object-align-text", variant); - } else { - app->activate_action("object-align", variant); + // Execute preview function + if (_preview_func) { + _preview_func(_preview_action); } // Update status @@ -442,6 +502,28 @@ AlignAndDistribute::end_preview() } } +void +AlignAndDistribute::confirm_preview() +{ + if (!_preview_active) { + return; + } + + _preview_active = false; + + // Clear preview data but don't restore (we want to keep the changes) + _original_transforms.clear(); + _preview_objects.clear(); + + // Clear status message + auto win = InkscapeApplication::instance()->get_active_window(); + if (win) { + if (auto desktop = win->get_desktop()) { + desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, ""); + } + } +} + void AlignAndDistribute::store_original_transforms() { @@ -488,6 +570,183 @@ AlignAndDistribute::restore_original_transforms() _preview_objects.clear(); } +// ================== PREVIEW IMPLEMENTATION METHODS ================== + +void +AlignAndDistribute::preview_align(const std::string& action) +{ + auto win = InkscapeApplication::instance()->get_active_window(); + if (!win) return; + + auto desktop = win->get_desktop(); + if (!desktop) return; + + auto selection = desktop->getSelection(); + if (!selection || selection->isEmpty()) return; + + // Get the reference object/point for alignment + auto items = selection->items(); + if (items.empty()) return; + + // Calculate reference bounds based on align-to setting + Geom::OptRect reference_bounds; + std::string align_to = align_relative_object.get_active_id(); + + if (align_to == "page") { + reference_bounds = desktop->getDocument()->preferredBounds(); + } else if (align_to == "selection") { + reference_bounds = selection->preferredBounds(); + } else { + // For now, use selection bounds as fallback + reference_bounds = selection->preferredBounds(); + } + + if (!reference_bounds) return; + + // Apply alignment preview to each item + for (auto item : items) { + if (auto sp_item = cast(item)) { + auto item_bounds = sp_item->preferredBounds(); + if (!item_bounds) continue; + + Geom::Affine transform = sp_item->transform; + Geom::Point offset(0, 0); + + // Calculate offset based on alignment type + if (action == "left") { + offset.x() = reference_bounds->left() - item_bounds->left(); + } else if (action == "hcenter") { + offset.x() = reference_bounds->midpoint().x() - item_bounds->midpoint().x(); + } else if (action == "right") { + offset.x() = reference_bounds->right() - item_bounds->right(); + } else if (action == "top") { + offset.y() = reference_bounds->top() - item_bounds->top(); + } else if (action == "vcenter") { + offset.y() = reference_bounds->midpoint().y() - item_bounds->midpoint().y(); + } else if (action == "bottom") { + offset.y() = reference_bounds->bottom() - item_bounds->bottom(); + } + + // Apply the offset + transform *= Geom::Translate(offset); + sp_item->set_transform(transform); + } + } + + // Force canvas update + desktop->getCanvas()->redraw_all(); +} + +void +AlignAndDistribute::preview_distribute(const std::string& action) +{ + auto win = InkscapeApplication::instance()->get_active_window(); + if (!win) return; + + auto desktop = win->get_desktop(); + if (!desktop) return; + + auto selection = desktop->getSelection(); + if (!selection || selection->isEmpty()) return; + + auto items = selection->items(); + if (items.size() < 3) return; // Need at least 3 items to distribute + + // Sort items by position + std::vector sorted_items; + for (auto item : items) { + if (auto sp_item = cast(item)) { + sorted_items.push_back(sp_item); + } + } + + if (sorted_items.size() < 3) return; + + // Sort based on distribution type + if (action.find("horizontal") != std::string::npos || action.find("left") != std::string::npos || + action.find("right") != std::string::npos || action.find("hcenter") != std::string::npos) { + std::sort(sorted_items.begin(), sorted_items.end(), [](SPItem* a, SPItem* b) { + auto bounds_a = a->preferredBounds(); + auto bounds_b = b->preferredBounds(); + if (!bounds_a || !bounds_b) return false; + return bounds_a->midpoint().x() < bounds_b->midpoint().x(); + }); + } else { + std::sort(sorted_items.begin(), sorted_items.end(), [](SPItem* a, SPItem* b) { + auto bounds_a = a->preferredBounds(); + auto bounds_b = b->preferredBounds(); + if (!bounds_a || !bounds_b) return false; + return bounds_a->midpoint().y() < bounds_b->midpoint().y(); + }); + } + + // Calculate distribution spacing + auto first_bounds = sorted_items.front()->preferredBounds(); + auto last_bounds = sorted_items.back()->preferredBounds(); + if (!first_bounds || !last_bounds) return; + + double total_space; + if (action.find("horizontal") != std::string::npos) { + total_space = last_bounds->midpoint().x() - first_bounds->midpoint().x(); + } else { + total_space = last_bounds->midpoint().y() - first_bounds->midpoint().y(); + } + + double spacing = total_space / (sorted_items.size() - 1); + + // Apply distribution + for (size_t i = 1; i < sorted_items.size() - 1; ++i) { + auto item = sorted_items[i]; + auto item_bounds = item->preferredBounds(); + if (!item_bounds) continue; + + Geom::Affine transform = item->transform; + Geom::Point target_pos; + + if (action.find("horizontal") != std::string::npos) { + target_pos.x() = first_bounds->midpoint().x() + spacing * i; + target_pos.y() = item_bounds->midpoint().y(); + } else { + target_pos.x() = item_bounds->midpoint().x(); + target_pos.y() = first_bounds->midpoint().y() + spacing * i; + } + + Geom::Point offset = target_pos - item_bounds->midpoint(); + transform *= Geom::Translate(offset); + item->set_transform(transform); + } + + desktop->getCanvas()->redraw_all(); +} + +void +AlignAndDistribute::preview_remove_overlap() +{ + // For simplicity, just show current positions (remove overlap is complex to preview) + auto win = InkscapeApplication::instance()->get_active_window(); + if (!win) return; + + auto desktop = win->get_desktop(); + if (!desktop) return; + + desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, + "Remove overlap preview - click to apply"); +} + +void +AlignAndDistribute::preview_rearrange(const std::string& action) +{ + // For simplicity, just show current positions (rearrange operations are complex to preview) + auto win = InkscapeApplication::instance()->get_active_window(); + if (!win) return; + + auto desktop = win->get_desktop(); + if (!desktop) return; + + desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, + "Rearrange preview - click to apply"); +} + } // namespace Inkscape::UI::Dialog /* diff --git a/src/ui/dialog/align-and-distribute.h b/src/ui/dialog/align-and-distribute.h index 6e900c4b73..1dd6323648 100644 --- a/src/ui/dialog/align-and-distribute.h +++ b/src/ui/dialog/align-and-distribute.h @@ -64,13 +64,7 @@ private: void on_remove_overlap_clicked(); void on_align_node_clicked(std::string const &direction); - // Preview functionality - bool _preview_active = false; - std::string _preview_action; - std::vector _original_transforms; - std::vector _preview_objects; - sigc::connection _preview_timeout_connection; - std::map> _motion_controllers; +controllers; // Preview methods bool start_preview_timeout(); -- GitLab From cbd869bab01d791e22b88deaa0d945cc7bd85c9e Mon Sep 17 00:00:00 2001 From: Adam Belis Date: Sun, 27 Jul 2025 09:15:28 +0000 Subject: [PATCH 08/12] Update 2 files - /src/ui/dialog/align-and-distribute.h - /src/ui/dialog/align-and-distribute.cpp --- src/ui/dialog/align-and-distribute.cpp | 2 +- src/ui/dialog/align-and-distribute.h | 33 ++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/ui/dialog/align-and-distribute.cpp b/src/ui/dialog/align-and-distribute.cpp index c1f724aa93..3b0ad7fe7e 100644 --- a/src/ui/dialog/align-and-distribute.cpp +++ b/src/ui/dialog/align-and-distribute.cpp @@ -194,7 +194,7 @@ AlignAndDistribute::AlignAndDistribute(Inkscape::UI::Dialog::DialogBase *dlg) auto set_icon_size_prefs = [prefs, this]() { int size = prefs->getIntLimited("/toolbox/tools/iconsize", -1, 16, 48); - Inkscape::UI::set_icon_sizes(this, size); + Inkscape::UI::set_icon_sizes(static_cast(this), size); }; // For now we are going to track the toolbox icon size, in the future we will have our own diff --git a/src/ui/dialog/align-and-distribute.h b/src/ui/dialog/align-and-distribute.h index 1dd6323648..9278ff978b 100644 --- a/src/ui/dialog/align-and-distribute.h +++ b/src/ui/dialog/align-and-distribute.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include "2geom/affine.h" @@ -64,14 +65,29 @@ private: void on_remove_overlap_clicked(); void on_align_node_clicked(std::string const &direction); -controllers; + // Button handling + void on_button_clicked(std::string const &action); + void execute_action(const std::string& action); + void execute_distribute_action(const std::string& action); + void execute_rearrange_action(const std::string& action); + + // Hover preview system + void setup_hover_preview_for_button(const char* button_id, const char* action_name, + std::function preview_func); // Preview methods bool start_preview_timeout(); - void start_preview(const std::string& action); + void start_preview(); void end_preview(); + void confirm_preview(); void store_original_transforms(); void restore_original_transforms(); + + // Preview implementations + void preview_align(const std::string& action); + void preview_distribute(const std::string& action); + void preview_remove_overlap(); + void preview_rearrange(const std::string& action); // UI Glib::RefPtr builder; @@ -104,6 +120,19 @@ controllers; std::set single_selection_relative_categories = { "first", "last", "biggest", "smallest" }; + + // Preview state + bool _preview_active = false; + std::string _preview_action; + std::function _preview_func; + sigc::connection _preview_timeout_connection; + + // Store original transforms for preview + std::vector _preview_objects; + std::vector _original_transforms; + + // Motion controllers for hover detection + std::map> _motion_controllers; }; } // namespace Inkscape::UI::Dialog -- GitLab From 74a425ef1dcd623f948d1d510902a31d657f89b3 Mon Sep 17 00:00:00 2001 From: Adam Belis Date: Sun, 27 Jul 2025 09:21:37 +0000 Subject: [PATCH 09/12] Update file align-and-distribute.cpp --- src/ui/dialog/align-and-distribute.cpp | 38 ++++++++++++++------------ 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/ui/dialog/align-and-distribute.cpp b/src/ui/dialog/align-and-distribute.cpp index 3b0ad7fe7e..4f49452979 100644 --- a/src/ui/dialog/align-and-distribute.cpp +++ b/src/ui/dialog/align-and-distribute.cpp @@ -35,6 +35,7 @@ #include "ui/util.h" #include "message-stack.h" // For status messages #include "object/sp-item.h" // For SPItem cast +#include "object/sp-document.h" // For SPDocument #include "util/cast.h" // For cast function #include "ui/widget/canvas.h" // For canvas access #include "object/algorithms/removeoverlap.h" // For remove overlap preview @@ -593,12 +594,12 @@ AlignAndDistribute::preview_align(const std::string& action) std::string align_to = align_relative_object.get_active_id(); if (align_to == "page") { - reference_bounds = desktop->getDocument()->preferredBounds(); + reference_bounds = desktop->getDocument()->documentBounds(); } else if (align_to == "selection") { - reference_bounds = selection->preferredBounds(); + reference_bounds = selection->visualBounds(); } else { // For now, use selection bounds as fallback - reference_bounds = selection->preferredBounds(); + reference_bounds = selection->visualBounds(); } if (!reference_bounds) return; @@ -606,7 +607,7 @@ AlignAndDistribute::preview_align(const std::string& action) // Apply alignment preview to each item for (auto item : items) { if (auto sp_item = cast(item)) { - auto item_bounds = sp_item->preferredBounds(); + auto item_bounds = sp_item->visualBounds(); if (!item_bounds) continue; Geom::Affine transform = sp_item->transform; @@ -649,40 +650,43 @@ AlignAndDistribute::preview_distribute(const std::string& action) auto selection = desktop->getSelection(); if (!selection || selection->isEmpty()) return; + // Convert items to vector and check size + std::vector item_vector; auto items = selection->items(); - if (items.size() < 3) return; // Need at least 3 items to distribute - - // Sort items by position - std::vector sorted_items; for (auto item : items) { if (auto sp_item = cast(item)) { - sorted_items.push_back(sp_item); + item_vector.push_back(sp_item); } } - if (sorted_items.size() < 3) return; + if (item_vector.size() < 3) return; // Need at least 3 items to distribute + + // Sort items by position + std::vector sorted_items = item_vector; // Sort based on distribution type if (action.find("horizontal") != std::string::npos || action.find("left") != std::string::npos || action.find("right") != std::string::npos || action.find("hcenter") != std::string::npos) { std::sort(sorted_items.begin(), sorted_items.end(), [](SPItem* a, SPItem* b) { - auto bounds_a = a->preferredBounds(); - auto bounds_b = b->preferredBounds(); + if (!a || !b) return false; + auto bounds_a = a->visualBounds(); + auto bounds_b = b->visualBounds(); if (!bounds_a || !bounds_b) return false; return bounds_a->midpoint().x() < bounds_b->midpoint().x(); }); } else { std::sort(sorted_items.begin(), sorted_items.end(), [](SPItem* a, SPItem* b) { - auto bounds_a = a->preferredBounds(); - auto bounds_b = b->preferredBounds(); + if (!a || !b) return false; + auto bounds_a = a->visualBounds(); + auto bounds_b = b->visualBounds(); if (!bounds_a || !bounds_b) return false; return bounds_a->midpoint().y() < bounds_b->midpoint().y(); }); } // Calculate distribution spacing - auto first_bounds = sorted_items.front()->preferredBounds(); - auto last_bounds = sorted_items.back()->preferredBounds(); + auto first_bounds = sorted_items.front()->visualBounds(); + auto last_bounds = sorted_items.back()->visualBounds(); if (!first_bounds || !last_bounds) return; double total_space; @@ -697,7 +701,7 @@ AlignAndDistribute::preview_distribute(const std::string& action) // Apply distribution for (size_t i = 1; i < sorted_items.size() - 1; ++i) { auto item = sorted_items[i]; - auto item_bounds = item->preferredBounds(); + auto item_bounds = item->visualBounds(); if (!item_bounds) continue; Geom::Affine transform = item->transform; -- GitLab From f91651c95c107e6797ad88835128df9465997fa4 Mon Sep 17 00:00:00 2001 From: Adam Belis Date: Sun, 27 Jul 2025 09:33:37 +0000 Subject: [PATCH 10/12] Update file align-and-distribute.cpp --- src/ui/dialog/align-and-distribute.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/dialog/align-and-distribute.cpp b/src/ui/dialog/align-and-distribute.cpp index 4f49452979..1d0e4e26bc 100644 --- a/src/ui/dialog/align-and-distribute.cpp +++ b/src/ui/dialog/align-and-distribute.cpp @@ -35,7 +35,7 @@ #include "ui/util.h" #include "message-stack.h" // For status messages #include "object/sp-item.h" // For SPItem cast -#include "object/sp-document.h" // For SPDocument +#include "document.h" // For SPDocument (corrected include) #include "util/cast.h" // For cast function #include "ui/widget/canvas.h" // For canvas access #include "object/algorithms/removeoverlap.h" // For remove overlap preview -- GitLab From 1d7b2d0f91599595060ae1e58ea0f5e5438b3472 Mon Sep 17 00:00:00 2001 From: Adam Belis Date: Sun, 27 Jul 2025 09:47:23 +0000 Subject: [PATCH 11/12] Update file align-and-distribute.cpp --- src/ui/dialog/align-and-distribute.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ui/dialog/align-and-distribute.cpp b/src/ui/dialog/align-and-distribute.cpp index 1d0e4e26bc..0b4efd270a 100644 --- a/src/ui/dialog/align-and-distribute.cpp +++ b/src/ui/dialog/align-and-distribute.cpp @@ -594,7 +594,10 @@ AlignAndDistribute::preview_align(const std::string& action) std::string align_to = align_relative_object.get_active_id(); if (align_to == "page") { - reference_bounds = desktop->getDocument()->documentBounds(); + auto doc = desktop->getDocument(); + double width = doc->getWidth().value("px"); + double height = doc->getHeight().value("px"); + reference_bounds = Geom::Rect(0, 0, width, height); } else if (align_to == "selection") { reference_bounds = selection->visualBounds(); } else { @@ -604,7 +607,7 @@ AlignAndDistribute::preview_align(const std::string& action) if (!reference_bounds) return; - // Apply alignment preview to each item + // Apply alignment preview PHONE each item for (auto item : items) { if (auto sp_item = cast(item)) { auto item_bounds = sp_item->visualBounds(); @@ -628,7 +631,7 @@ AlignAndDistribute::preview_align(const std::string& action) offset.y() = reference_bounds->bottom() - item_bounds->bottom(); } - // Apply the offset + // Apply.Concurrent the offset transform *= Geom::Translate(offset); sp_item->set_transform(transform); } -- GitLab From 877bc9f957b54cf8a29398ff3898e99d584f0387 Mon Sep 17 00:00:00 2001 From: Adam Belis Date: Sun, 27 Jul 2025 09:55:36 +0000 Subject: [PATCH 12/12] Update file align-and-distribute.cpp --- src/ui/dialog/align-and-distribute.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ui/dialog/align-and-distribute.cpp b/src/ui/dialog/align-and-distribute.cpp index 0b4efd270a..c701b61826 100644 --- a/src/ui/dialog/align-and-distribute.cpp +++ b/src/ui/dialog/align-and-distribute.cpp @@ -24,6 +24,7 @@ #include #include #include +#include "util/units.h" #include "actions/actions-tools.h" // Tool switching. #include "desktop.h" // Tool switching. -- GitLab