diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt index 1e8feaa7a4..a231cb8ced 100644 --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -1,595 +1,597 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile ${EXIV2_INCLUDE_DIR} ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ${OCIO_INCLUDE_DIR} ) add_subdirectory( tests ) if (APPLE) find_library(FOUNDATION_LIBRARY Foundation) find_library(APPKIT_LIBRARY AppKit) endif () set(kritaui_LIB_SRCS canvas/kis_canvas_widget_base.cpp canvas/kis_canvas2.cpp canvas/kis_canvas_updates_compressor.cpp canvas/kis_canvas_controller.cpp canvas/kis_paintop_transformation_connector.cpp canvas/kis_display_color_converter.cpp canvas/kis_display_filter.cpp canvas/kis_exposure_gamma_correction_interface.cpp canvas/kis_tool_proxy.cpp canvas/kis_canvas_decoration.cc canvas/kis_coordinates_converter.cpp canvas/kis_grid_manager.cpp canvas/kis_grid_decoration.cpp canvas/kis_grid_config.cpp canvas/kis_prescaled_projection.cpp canvas/kis_qpainter_canvas.cpp canvas/kis_projection_backend.cpp canvas/kis_update_info.cpp canvas/kis_image_patch.cpp canvas/kis_image_pyramid.cpp canvas/kis_infinity_manager.cpp canvas/kis_change_guides_command.cpp canvas/kis_guides_decoration.cpp canvas/kis_guides_manager.cpp canvas/kis_guides_config.cpp canvas/kis_snap_config.cpp canvas/kis_snap_line_strategy.cpp canvas/KisSnapPointStrategy.cpp dialogs/kis_about_application.cpp dialogs/kis_dlg_adj_layer_props.cc dialogs/kis_dlg_adjustment_layer.cc dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_generator_layer.cpp dialogs/kis_dlg_file_layer.cpp dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_stroke_selection_properties.cpp dialogs/kis_dlg_image_properties.cc dialogs/kis_dlg_layer_properties.cc dialogs/kis_dlg_preferences.cc dialogs/slider_and_spin_box_sync.cpp dialogs/kis_dlg_blacklist_cleanup.cpp dialogs/kis_dlg_layer_style.cpp dialogs/kis_dlg_png_import.cpp dialogs/kis_dlg_import_image_sequence.cpp dialogs/kis_delayed_save_dialog.cpp dialogs/kis_dlg_internal_color_selector.cpp dialogs/KisSessionManagerDialog.cpp dialogs/KisNewWindowLayoutDialog.cpp flake/kis_node_dummies_graph.cpp flake/kis_dummies_facade_base.cpp flake/kis_dummies_facade.cpp flake/kis_node_shapes_graph.cpp flake/kis_node_shape.cpp flake/kis_shape_controller.cpp flake/kis_shape_layer.cc flake/kis_shape_layer_canvas.cpp flake/kis_shape_selection.cpp flake/kis_shape_selection_canvas.cpp flake/kis_shape_selection_model.cpp flake/kis_take_all_shapes_command.cpp brushhud/kis_uniform_paintop_property_widget.cpp brushhud/kis_brush_hud.cpp brushhud/kis_round_hud_button.cpp brushhud/kis_dlg_brush_hud_config.cpp brushhud/kis_brush_hud_properties_list.cpp brushhud/kis_brush_hud_properties_config.cpp kis_aspect_ratio_locker.cpp kis_autogradient.cc kis_bookmarked_configurations_editor.cc kis_bookmarked_configurations_model.cc kis_bookmarked_filter_configurations_model.cc KisPaintopPropertiesBase.cpp kis_canvas_resource_provider.cpp kis_derived_resources.cpp kis_categories_mapper.cpp kis_categorized_list_model.cpp kis_categorized_item_delegate.cpp kis_clipboard.cc kis_config.cc kis_config_notifier.cpp kis_control_frame.cpp kis_composite_ops_model.cc kis_paint_ops_model.cpp kis_cursor.cc kis_cursor_cache.cpp kis_custom_pattern.cc kis_file_layer.cpp kis_change_file_layer_command.h kis_safe_document_loader.cpp kis_splash_screen.cpp kis_filter_manager.cc kis_filters_model.cc kis_histogram_view.cc KisImageBarrierLockerWithFeedback.cpp kis_image_manager.cc kis_image_view_converter.cpp kis_import_catcher.cc kis_layer_manager.cc kis_mask_manager.cc kis_mimedata.cpp kis_node_commands_adapter.cpp kis_node_manager.cpp kis_node_juggler_compressed.cpp kis_node_selection_adapter.cpp kis_node_insertion_adapter.cpp kis_node_model.cpp kis_node_filter_proxy_model.cpp kis_model_index_converter_base.cpp kis_model_index_converter.cpp kis_model_index_converter_show_all.cpp kis_painting_assistant.cc kis_painting_assistants_decoration.cpp KisDecorationsManager.cpp kis_paintop_box.cc kis_paintop_option.cpp kis_paintop_options_model.cpp kis_paintop_settings_widget.cpp kis_popup_palette.cpp kis_png_converter.cpp kis_preference_set_registry.cpp KisResourceServerProvider.cpp KisResourceBundleServerProvider.cpp KisSelectedShapesProxy.cpp kis_selection_decoration.cc kis_selection_manager.cc kis_statusbar.cc kis_zoom_manager.cc kis_favorite_resource_manager.cpp kis_workspace_resource.cpp kis_action.cpp kis_action_manager.cpp KisActionPlugin.cpp kis_canvas_controls_manager.cpp kis_tooltip_manager.cpp kis_multinode_property.cpp kis_stopgradient_editor.cpp widgets/KisWelcomePageWidget.cpp kisexiv2/kis_exif_io.cpp kisexiv2/kis_exiv2.cpp kisexiv2/kis_iptc_io.cpp kisexiv2/kis_xmp_io.cpp opengl/kis_opengl.cpp opengl/kis_opengl_canvas2.cpp opengl/kis_opengl_canvas_debugger.cpp opengl/kis_opengl_image_textures.cpp opengl/kis_texture_tile.cpp opengl/kis_opengl_shader_loader.cpp opengl/kis_texture_tile_info_pool.cpp opengl/KisOpenGLUpdateInfoBuilder.cpp kis_fps_decoration.cpp + tool/KisToolChangesTracker.cpp + tool/KisToolChangesTrackerData.cpp tool/kis_selection_tool_helper.cpp tool/kis_selection_tool_config_widget_helper.cpp tool/kis_rectangle_constraint_widget.cpp tool/kis_shape_tool_helper.cpp tool/kis_tool.cc tool/kis_delegated_tool_policies.cpp tool/kis_tool_freehand.cc tool/kis_speed_smoother.cpp tool/kis_painting_information_builder.cpp tool/kis_stabilized_events_sampler.cpp tool/kis_tool_freehand_helper.cpp tool/kis_tool_multihand_helper.cpp tool/kis_figure_painting_tool_helper.cpp tool/kis_tool_paint.cc tool/kis_tool_shape.cc tool/kis_tool_ellipse_base.cpp tool/kis_tool_rectangle_base.cpp tool/kis_tool_polyline_base.cpp tool/kis_tool_utils.cpp tool/kis_resources_snapshot.cpp tool/kis_smoothing_options.cpp tool/KisStabilizerDelayedPaintHelper.cpp tool/KisStrokeSpeedMonitor.cpp tool/strokes/freehand_stroke.cpp tool/strokes/KisStrokeEfficiencyMeasurer.cpp tool/strokes/kis_painter_based_stroke_strategy.cpp tool/strokes/kis_filter_stroke_strategy.cpp tool/strokes/kis_color_picker_stroke_strategy.cpp tool/strokes/KisFreehandStrokeInfo.cpp tool/strokes/KisMaskedFreehandStrokePainter.cpp tool/strokes/KisMaskingBrushRenderer.cpp tool/strokes/KisMaskingBrushCompositeOpFactory.cpp widgets/kis_cmb_composite.cc widgets/kis_cmb_contour.cpp widgets/kis_cmb_gradient.cpp widgets/kis_paintop_list_widget.cpp widgets/kis_cmb_idlist.cc widgets/kis_color_space_selector.cc widgets/kis_advanced_color_space_selector.cc widgets/kis_cie_tongue_widget.cpp widgets/kis_tone_curve_widget.cpp widgets/kis_curve_widget.cpp widgets/kis_custom_image_widget.cc widgets/kis_image_from_clipboard_widget.cpp widgets/kis_double_widget.cc widgets/kis_filter_selector_widget.cc widgets/kis_gradient_chooser.cc widgets/kis_iconwidget.cc widgets/kis_mask_widgets.cpp widgets/kis_meta_data_merge_strategy_chooser_widget.cc widgets/kis_multi_bool_filter_widget.cc widgets/kis_multi_double_filter_widget.cc widgets/kis_multi_integer_filter_widget.cc widgets/kis_multipliers_double_slider_spinbox.cpp widgets/kis_paintop_presets_popup.cpp widgets/kis_tool_options_popup.cpp widgets/kis_paintop_presets_chooser_popup.cpp widgets/kis_paintop_presets_save.cpp widgets/kis_paintop_preset_icon_library.cpp widgets/kis_pattern_chooser.cc widgets/kis_popup_button.cc widgets/kis_preset_chooser.cpp widgets/kis_progress_widget.cpp widgets/kis_selection_options.cc widgets/kis_scratch_pad.cpp widgets/kis_scratch_pad_event_filter.cpp widgets/kis_preset_selector_strip.cpp widgets/kis_slider_spin_box.cpp widgets/KisSelectionPropertySlider.cpp widgets/kis_size_group.cpp widgets/kis_size_group_p.cpp widgets/kis_wdg_generator.cpp widgets/kis_workspace_chooser.cpp widgets/kis_categorized_list_view.cpp widgets/kis_widget_chooser.cpp widgets/kis_tool_button.cpp widgets/kis_floating_message.cpp widgets/kis_lod_availability_widget.cpp widgets/kis_color_label_selector_widget.cpp widgets/kis_color_filter_combo.cpp widgets/kis_elided_label.cpp widgets/kis_stopgradient_slider_widget.cpp widgets/kis_spinbox_color_selector.cpp widgets/kis_screen_color_picker.cpp widgets/kis_preset_live_preview_view.cpp widgets/KoDualColorButton.cpp widgets/kis_color_input.cpp widgets/kis_color_button.cpp widgets/KisVisualColorSelector.cpp widgets/KisVisualColorSelectorShape.cpp widgets/KisVisualEllipticalSelectorShape.cpp widgets/KisVisualRectangleSelectorShape.cpp widgets/KisVisualTriangleSelectorShape.cpp widgets/KoStrokeConfigWidget.cpp widgets/KoFillConfigWidget.cpp widgets/KisNewsWidget.cpp utils/kis_document_aware_spin_box_unit_manager.cpp input/kis_input_manager.cpp input/kis_input_manager_p.cpp input/kis_extended_modifiers_mapper.cpp input/kis_abstract_input_action.cpp input/kis_tool_invocation_action.cpp input/kis_pan_action.cpp input/kis_alternate_invocation_action.cpp input/kis_rotate_canvas_action.cpp input/kis_zoom_action.cpp input/kis_change_frame_action.cpp input/kis_gamma_exposure_action.cpp input/kis_show_palette_action.cpp input/kis_change_primary_setting_action.cpp input/kis_abstract_shortcut.cpp input/kis_native_gesture_shortcut.cpp input/kis_single_action_shortcut.cpp input/kis_stroke_shortcut.cpp input/kis_shortcut_matcher.cpp input/kis_select_layer_action.cpp input/KisQtWidgetsTweaker.cpp input/KisInputActionGroup.cpp operations/kis_operation.cpp operations/kis_operation_configuration.cpp operations/kis_operation_registry.cpp operations/kis_operation_ui_factory.cpp operations/kis_operation_ui_widget.cpp operations/kis_filter_selection_operation.cpp actions/kis_selection_action_factories.cpp actions/KisPasteActionFactory.cpp input/kis_touch_shortcut.cpp kis_document_undo_store.cpp kis_transaction_based_command.cpp kis_gui_context_command.cpp kis_gui_context_command_p.cpp input/kis_tablet_debugger.cpp input/kis_input_profile_manager.cpp input/kis_input_profile.cpp input/kis_shortcut_configuration.cpp input/config/kis_input_configuration_page.cpp input/config/kis_edit_profiles_dialog.cpp input/config/kis_input_profile_model.cpp input/config/kis_input_configuration_page_item.cpp input/config/kis_action_shortcuts_model.cpp input/config/kis_input_type_delegate.cpp input/config/kis_input_mode_delegate.cpp input/config/kis_input_button.cpp input/config/kis_input_editor_delegate.cpp input/config/kis_mouse_input_editor.cpp input/config/kis_wheel_input_editor.cpp input/config/kis_key_input_editor.cpp processing/fill_processing_visitor.cpp kis_asl_layer_style_serializer.cpp kis_psd_layer_style_resource.cpp canvas/kis_mirror_axis.cpp kis_abstract_perspective_grid.cpp KisApplication.cpp KisAutoSaveRecoveryDialog.cpp KisDetailsPane.cpp KisDocument.cpp KisCloneDocumentStroke.cpp KisNodeDelegate.cpp kis_node_view_visibility_delegate.cpp KisNodeToolTip.cpp KisNodeView.cpp kis_node_view_color_scheme.cpp KisImportExportFilter.cpp KisFilterEntry.cpp KisImportExportManager.cpp KisImportExportUtils.cpp kis_async_action_feedback.cpp KisMainWindow.cpp KisOpenPane.cpp KisPart.cpp KisPrintJob.cpp KisTemplate.cpp KisTemplateCreateDia.cpp KisTemplateGroup.cpp KisTemplates.cpp KisTemplatesPane.cpp KisTemplateTree.cpp KisUndoActionsUpdateManager.cpp KisView.cpp thememanager.cpp kis_mainwindow_observer.cpp KisViewManager.cpp kis_mirror_manager.cpp qtlockedfile/qtlockedfile.cpp qtsingleapplication/qtlocalpeer.cpp qtsingleapplication/qtsingleapplication.cpp KisResourceBundle.cpp KisResourceBundleManifest.cpp kis_md5_generator.cpp KisApplicationArguments.cpp KisNetworkAccessManager.cpp KisMultiFeedRSSModel.cpp KisRemoteFileFetcher.cpp KisPaletteModel.cpp kis_palette_delegate.cpp kis_palette_view.cpp KisColorsetChooser.cpp KisSaveGroupVisitor.cpp KisWindowLayoutResource.cpp KisWindowLayoutManager.cpp KisSessionResource.cpp KisReferenceImagesDecoration.cpp KisReferenceImage.cpp flake/KisReferenceImagesLayer.cpp flake/KisReferenceImagesLayer.h ) if(WIN32) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/kis_tablet_event.cpp input/wintab/kis_tablet_support_win.cpp input/wintab/kis_screen_size_choice_dialog.cpp qtlockedfile/qtlockedfile_win.cpp input/wintab/kis_tablet_support_win8.cpp opengl/kis_opengl_win.cpp ) endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} kis_animation_frame_cache.cpp kis_animation_cache_populator.cpp KisAsyncAnimationRendererBase.cpp KisAsyncAnimationCacheRenderer.cpp KisAsyncAnimationFramesSavingRenderer.cpp dialogs/KisAsyncAnimationRenderDialogBase.cpp dialogs/KisAsyncAnimationCacheRenderDialog.cpp dialogs/KisAsyncAnimationFramesSaveDialog.cpp canvas/kis_animation_player.cpp kis_animation_importer.cpp KisSyncedAudioPlayback.cpp KisFrameDataSerializer.cpp KisFrameCacheStore.cpp KisFrameCacheSwapper.cpp KisAbstractFrameCacheSwapper.cpp KisInMemoryFrameCacheSwapper.cpp input/wintab/drawpile_tablettester/tablettester.cpp input/wintab/drawpile_tablettester/tablettest.cpp ) if(UNIX) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/kis_tablet_event.cpp input/wintab/kis_tablet_support.cpp qtlockedfile/qtlockedfile_unix.cpp ) if(NOT APPLE) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/wintab/qxcbconnection_xi2.cpp input/wintab/qxcbconnection.cpp input/wintab/kis_xi2_event_filter.cpp ) endif() endif() if(APPLE) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} osx.mm ) endif() ki18n_wrap_ui(kritaui_LIB_SRCS widgets/KoFillConfigWidget.ui widgets/KoStrokeConfigWidget.ui forms/wdgdlgpngimport.ui forms/wdgfullscreensettings.ui forms/wdgautogradient.ui forms/wdggeneralsettings.ui forms/wdgperformancesettings.ui forms/wdggenerators.ui forms/wdgbookmarkedconfigurationseditor.ui forms/wdgapplyprofile.ui forms/wdgcustompattern.ui forms/wdglayerproperties.ui forms/wdgcolorsettings.ui forms/wdgtabletsettings.ui forms/wdgcolorspaceselector.ui forms/wdgcolorspaceselectoradvanced.ui forms/wdgdisplaysettings.ui forms/kis_previewwidgetbase.ui forms/kis_matrix_widget.ui forms/wdgselectionoptions.ui forms/wdggeometryoptions.ui forms/wdgnewimage.ui forms/wdgimageproperties.ui forms/wdgmaskfromselection.ui forms/wdgmasksource.ui forms/wdgfilterdialog.ui forms/wdgmetadatamergestrategychooser.ui forms/wdgpaintoppresets.ui forms/wdgpaintopsettings.ui forms/wdgdlggeneratorlayer.ui forms/wdgdlgfilelayer.ui forms/wdgfilterselector.ui forms/wdgfilternodecreation.ui forms/wdgmultipliersdoublesliderspinbox.ui forms/wdgnodequerypatheditor.ui forms/wdgpresetselectorstrip.ui forms/wdgsavebrushpreset.ui forms/wdgpreseticonlibrary.ui forms/wdgdlgblacklistcleanup.ui forms/wdgrectangleconstraints.ui forms/wdgimportimagesequence.ui forms/wdgstrokeselectionproperties.ui forms/KisDetailsPaneBase.ui forms/KisOpenPaneBase.ui forms/wdgstopgradienteditor.ui forms/wdgsessionmanager.ui forms/wdgnewwindowlayout.ui forms/KisWelcomePage.ui forms/KisNewsPage.ui brushhud/kis_dlg_brush_hud_config.ui forms/wdgdlginternalcolorselector.ui dialogs/kis_delayed_save_dialog.ui input/config/kis_input_configuration_page.ui input/config/kis_edit_profiles_dialog.ui input/config/kis_input_configuration_page_item.ui input/config/kis_mouse_input_editor.ui input/config/kis_wheel_input_editor.ui input/config/kis_key_input_editor.ui layerstyles/wdgBevelAndEmboss.ui layerstyles/wdgblendingoptions.ui layerstyles/WdgColorOverlay.ui layerstyles/wdgContour.ui layerstyles/wdgdropshadow.ui layerstyles/WdgGradientOverlay.ui layerstyles/wdgInnerGlow.ui layerstyles/wdglayerstyles.ui layerstyles/WdgPatternOverlay.ui layerstyles/WdgSatin.ui layerstyles/WdgStroke.ui layerstyles/wdgstylesselector.ui layerstyles/wdgTexture.ui wdgsplash.ui input/wintab/kis_screen_size_choice_dialog.ui input/wintab/drawpile_tablettester/tablettest.ui ) QT5_WRAP_CPP(kritaui_HEADERS_MOC KisNodePropertyAction_p.h) add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} ) generate_export_header(kritaui BASE_NAME kritaui) target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} ${EXIV2_LIBRARIES} ) if (HAVE_QT_MULTIMEDIA) target_link_libraries(kritaui Qt5::Multimedia) endif() if (HAVE_KIO) target_link_libraries(kritaui KF5::KIOCore) endif() if (NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${X11_X11_LIB} ${X11_Xinput_LIB} ${XCB_LIBRARIES}) endif() if(APPLE) target_link_libraries(kritaui ${FOUNDATION_LIBRARY}) target_link_libraries(kritaui ${APPKIT_LIBRARY}) endif () target_link_libraries(kritaui ${OPENEXR_LIBRARIES}) # Add VSync disable workaround if(NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${CMAKE_DL_LIBS} Qt5::X11Extras) endif() if(X11_FOUND) target_link_libraries(kritaui Qt5::X11Extras ${X11_LIBRARIES}) endif() target_include_directories(kritaui PUBLIC $ $ $ $ $ $ $ ) set_target_properties(kritaui PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaui ${INSTALL_TARGETS_DEFAULT_ARGS}) if (APPLE) install(FILES osx.stylesheet DESTINATION ${DATA_INSTALL_DIR}/krita) endif () diff --git a/libs/ui/tool/KisToolChangesTracker.cpp b/libs/ui/tool/KisToolChangesTracker.cpp new file mode 100644 index 0000000000..b740644148 --- /dev/null +++ b/libs/ui/tool/KisToolChangesTracker.cpp @@ -0,0 +1,46 @@ +#include "KisToolChangesTracker.h" + +#include "kis_global.h" +#include + +struct KisToolChangesTracker::Private { + QList undoStack; +}; + + +KisToolChangesTracker::KisToolChangesTracker() + : m_d(new Private) +{ +} + +KisToolChangesTracker::~KisToolChangesTracker() +{ +} + +void KisToolChangesTracker::commitConfig(KisToolChangesTrackerDataSP state) +{ + m_d->undoStack.append(state); +} + +void KisToolChangesTracker::requestUndo() +{ + if (m_d->undoStack.size() > 1) { + m_d->undoStack.removeLast(); + emit sigConfigChanged(m_d->undoStack.last()); + } +} + +KisToolChangesTrackerDataSP KisToolChangesTracker::lastState() const +{ + return !m_d->undoStack.isEmpty() ? m_d->undoStack.last() : 0; +} + +void KisToolChangesTracker::reset() +{ + m_d->undoStack.clear(); +} + +bool KisToolChangesTracker::isEmpty() const +{ + return m_d->undoStack.isEmpty(); +} diff --git a/libs/ui/tool/KisToolChangesTracker.h b/libs/ui/tool/KisToolChangesTracker.h new file mode 100644 index 0000000000..e81c568512 --- /dev/null +++ b/libs/ui/tool/KisToolChangesTracker.h @@ -0,0 +1,31 @@ +#ifndef KISTOOLCHANGESTRACKER_H +#define KISTOOLCHANGESTRACKER_H + +#include "kritaui_export.h" +#include +#include "KisToolChangesTrackerData.h" + +class KRITAUI_EXPORT KisToolChangesTracker :public QObject +{ + Q_OBJECT + +public: + KisToolChangesTracker(); + ~KisToolChangesTracker(); + + void commitConfig(KisToolChangesTrackerDataSP state); + void requestUndo(); + KisToolChangesTrackerDataSP lastState() const; + void reset(); + + bool isEmpty() const; + +Q_SIGNALS: + void sigConfigChanged(KisToolChangesTrackerDataSP state); + +private: + struct Private; + const QScopedPointer m_d; +}; + +#endif // KISTOOLCHANGESTRACKER_H diff --git a/libs/ui/tool/KisToolChangesTrackerData.cpp b/libs/ui/tool/KisToolChangesTrackerData.cpp new file mode 100644 index 0000000000..3cce59c497 --- /dev/null +++ b/libs/ui/tool/KisToolChangesTrackerData.cpp @@ -0,0 +1,18 @@ +#include "KisToolChangesTrackerData.h" + +struct KisToolChangesTrackerDataSPRegistrar { + KisToolChangesTrackerDataSPRegistrar() { + qRegisterMetaType("KisToolChangesTrackerDataSP"); + } +}; +static KisToolChangesTrackerDataSPRegistrar __registrar; + + +KisToolChangesTrackerData::~KisToolChangesTrackerData() +{ +} + +KisToolChangesTrackerData *KisToolChangesTrackerData::clone() const +{ + return new KisToolChangesTrackerData(*this); +} diff --git a/libs/ui/tool/KisToolChangesTrackerData.h b/libs/ui/tool/KisToolChangesTrackerData.h new file mode 100644 index 0000000000..6f879a91b5 --- /dev/null +++ b/libs/ui/tool/KisToolChangesTrackerData.h @@ -0,0 +1,19 @@ +#ifndef KISTOOLCHANGESTRACKERDATA_H +#define KISTOOLCHANGESTRACKERDATA_H + +#include +#include "kritaui_export.h" +#include + +class KRITAUI_EXPORT KisToolChangesTrackerData +{ +public: + virtual ~KisToolChangesTrackerData(); + virtual KisToolChangesTrackerData* clone() const; +}; + +typedef QSharedPointer KisToolChangesTrackerDataSP; + +Q_DECLARE_METATYPE(KisToolChangesTrackerDataSP) + +#endif // KISTOOLCHANGESTRACKERDATA_H diff --git a/plugins/tools/basictools/kis_tool_move.cc b/plugins/tools/basictools/kis_tool_move.cc index 1472fc8ef2..c1d3a81554 100644 --- a/plugins/tools/basictools/kis_tool_move.cc +++ b/plugins/tools/basictools/kis_tool_move.cc @@ -1,571 +1,595 @@ /* * Copyright (c) 1999 Matthias Elter * 1999 Michael Koch * 2002 Patrick Julien * 2004 Boudewijn Rempt * 2016 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_move.h" #include #include "kis_cursor.h" #include "kis_selection.h" #include "kis_canvas2.h" #include "kis_image.h" #include "kis_tool_utils.h" #include "kis_paint_layer.h" #include "strokes/move_stroke_strategy.h" #include "kis_tool_movetooloptionswidget.h" #include "strokes/move_selection_stroke_strategy.h" #include "kis_resources_snapshot.h" #include "kis_action_registry.h" #include "krita_utils.h" #include #include #include "kis_node_manager.h" #include "kis_signals_blocker.h" +#include + +struct KisToolMoveState : KisToolChangesTrackerData, boost::equality_comparable +{ + KisToolMoveState(QPoint _accumulatedOffset) : accumulatedOffset(_accumulatedOffset) {} + KisToolChangesTrackerData* clone() const { return new KisToolMoveState(*this); } + + bool operator ==(const KisToolMoveState &rhs) { + return accumulatedOffset == rhs.accumulatedOffset; + } + + QPoint accumulatedOffset; +}; + KisToolMove::KisToolMove(KoCanvasBase * canvas) : KisTool(canvas, KisCursor::moveCursor()) { m_canvas = dynamic_cast(canvas); setObjectName("tool_move"); m_optionsWidget = 0; - m_moveInProgress = false; QAction *a; KisActionRegistry *actionRegistry = KisActionRegistry::instance(); a = actionRegistry->makeQAction("movetool-move-up", this); addAction("movetool-move-up", a); connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Up, false);}); a = actionRegistry->makeQAction("movetool-move-down", this); addAction("movetool-move-down", a); connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Down, false);}); a = actionRegistry->makeQAction("movetool-move-left", this); addAction("movetool-move-left", a); connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Left, false);}); a = actionRegistry->makeQAction("movetool-move-right", this); addAction("movetool-move-right", a); connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Right, false);}); a = actionRegistry->makeQAction("movetool-move-up-more", this); addAction("movetool-move-up-more", a); connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Up, true);}); a = actionRegistry->makeQAction("movetool-move-down-more", this); addAction("movetool-move-down-more", a); connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Down, true);}); a = actionRegistry->makeQAction("movetool-move-left-more", this); addAction("movetool-move-left-more", a); connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Left, true);}); a = actionRegistry->makeQAction("movetool-move-right-more", this); addAction("movetool-move-right-more", a); connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Right, true);}); m_showCoordinatesAction = actionRegistry->makeQAction("movetool-show-coordinates", this); addAction("movetool-show-coordinates", m_showCoordinatesAction); + + connect(&m_changesTracker, + SIGNAL(sigConfigChanged(KisToolChangesTrackerDataSP)), + SLOT(slotTrackerChangedConfig(KisToolChangesTrackerDataSP))); } KisToolMove::~KisToolMove() { endStroke(); } void KisToolMove::resetCursorStyle() { KisTool::resetCursorStyle(); overrideCursorIfNotEditable(); } bool KisToolMove::startStrokeImpl(MoveToolMode mode, const QPoint *pos) { if (!currentNode()->isEditable()) return false; KisNodeSP node; KisNodeList nodes; KisImageSP image = this->image(); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image, currentNode(), this->canvas()->resourceManager()); KisSelectionSP selection = resources->activeSelection(); if (mode != MoveSelectedLayer && pos) { bool wholeGroup = !selection && mode == MoveGroup; node = KisToolUtils::findNode(image->root(), *pos, wholeGroup); if (node) { nodes = {node}; } } if (nodes.isEmpty()) { nodes = this->selectedNodes(); KritaUtils::filterContainer(nodes, [](KisNodeSP node) { return node->isEditable(); }); } if (nodes.size() == 1) { node = nodes.first(); } if (nodes.isEmpty()) { return false; } - initHandles(nodes); - /** * If the target node has changed, the stroke should be * restarted. Otherwise just continue processing current node. */ - if (m_strokeId) { - if (KritaUtils::compareListsUnordered(nodes, m_currentlyProcessingNodes)) { - return true; - } else { - endStroke(); - } + if (m_strokeId && !tryEndPreviousStroke(nodes)) { + return true; } + initHandles(nodes); + KisStrokeStrategy *strategy; KisPaintLayerSP paintLayer = node ? dynamic_cast(node.data()) : 0; if (paintLayer && selection && !selection->isTotallyUnselected(image->bounds())) { strategy = new MoveSelectionStrokeStrategy(paintLayer, selection, image.data(), image.data()); } else { strategy = new MoveStrokeStrategy(nodes, image.data(), image.data()); } m_strokeId = image->startStroke(strategy); m_currentlyProcessingNodes = nodes; m_accumulatedOffset = QPoint(); + KIS_SAFE_ASSERT_RECOVER(m_changesTracker.isEmpty()) { + m_changesTracker.reset(); + } + commitChanges(); + return true; } -void KisToolMove::moveDiscrete(MoveDirection direction, bool big) +QPoint KisToolMove::currentOffset() const { - if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging - if (!currentNode()->isEditable()) return; // Don't move invisible nodes + return m_accumulatedOffset + m_dragPos - m_dragStart; +} - if (startStrokeImpl(MoveSelectedLayer, 0)) { - setMode(KisTool::PAINT_MODE); - } +void KisToolMove::notifyGuiAfterMove(bool showFloatingMessage) +{ + if (!m_optionsWidget) return; - // Larger movement if "shift" key is pressed. - qreal scale = big ? m_optionsWidget->moveScale() : 1.0; - qreal moveStep = m_optionsWidget->moveStep() * scale; + const QPoint currentTopLeft = m_handlesRect.topLeft() + currentOffset(); - QPoint offset = direction == Up ? QPoint( 0, -moveStep) : - direction == Down ? QPoint( 0, moveStep) : - direction == Left ? QPoint(-moveStep, 0) : - QPoint( moveStep, 0) ; + KisSignalsBlocker b(m_optionsWidget); + emit moveInNewPosition(currentTopLeft); + // TODO: fetch this info not from options widget, but from config const bool showCoordinates = m_optionsWidget->showCoordinates(); - if (showCoordinates) { + if (showCoordinates && showFloatingMessage) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in move tool", "X: %1 px, Y: %2 px", - (m_startPosition + offset).x(), - (m_startPosition + offset).y()), + currentTopLeft.x(), + currentTopLeft.y()), QIcon(), 1000, KisFloatingMessage::High); } +} - KisSignalsBlocker b(m_optionsWidget); - emit moveInNewPosition(m_startPosition + offset); +bool KisToolMove::tryEndPreviousStroke(KisNodeList nodes) +{ + if (!m_strokeId) return false; + + bool strokeEnded = false; + + if (!KritaUtils::compareListsUnordered(nodes, m_currentlyProcessingNodes)) { + endStroke(); + strokeEnded = true; + } + + return strokeEnded; +} + +void KisToolMove::commitChanges() +{ + KIS_SAFE_ASSERT_RECOVER_RETURN(m_strokeId); + + QSharedPointer newState(new KisToolMoveState(m_accumulatedOffset)); + KisToolMoveState *lastState = dynamic_cast(m_changesTracker.lastState().data()); + if (lastState && *lastState == *newState) return; + + m_changesTracker.commitConfig(newState); +} + +void KisToolMove::moveDiscrete(MoveDirection direction, bool big) +{ + if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging + if (!currentNode()->isEditable()) return; // Don't move invisible nodes + + if (startStrokeImpl(MoveSelectedLayer, 0)) { + setMode(KisTool::PAINT_MODE); + } + + // Larger movement if "shift" key is pressed. + qreal scale = big ? m_optionsWidget->moveScale() : 1.0; + qreal moveStep = m_optionsWidget->moveStep() * scale; - m_startPosition += offset; + const QPoint offset = + direction == Up ? QPoint( 0, -moveStep) : + direction == Down ? QPoint( 0, moveStep) : + direction == Left ? QPoint(-moveStep, 0) : + QPoint( moveStep, 0) ; - image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset + offset)); m_accumulatedOffset += offset; + image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); - m_moveInProgress = false; - emit moveInProgressChanged(); + notifyGuiAfterMove(); + commitChanges(); setMode(KisTool::HOVER_MODE); } void KisToolMove::activate(ToolActivation toolActivation, const QSet &shapes) { KisTool::activate(toolActivation, shapes); QRect totalBounds; slotNodeChanged(this->selectedNodes()); } void KisToolMove::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if (m_dragInProgress) { QPainterPath handles; - handles.addRect(m_handlesRect.translated(m_pos)); + handles.addRect(m_handlesRect.translated(currentOffset())); QPainterPath path = pixelToView(handles); paintToolOutline(&gc, path); } } void KisToolMove::initHandles(const KisNodeList &nodes) { + /** + * The handles should be initialized only once, **before** the start of + * the stroke. If the nodes change, we should restart the stroke. + */ + KIS_SAFE_ASSERT_RECOVER_NOOP(!m_strokeId); + m_handlesRect = QRect(); for (KisNodeSP node : nodes) { node->exactBounds(); m_handlesRect |= node->exactBounds(); } if (image()->globalSelection()) { m_handlesRect &= image()->globalSelection()->selectedRect(); } - if (m_handlesRect.topLeft() != m_startPosition) { - m_handlesRect.moveTopLeft(m_startPosition); - } } void KisToolMove::deactivate() { endStroke(); KisTool::deactivate(); } void KisToolMove::requestStrokeEnd() { endStroke(); } void KisToolMove::requestStrokeCancellation() { cancelStroke(); } void KisToolMove::requestUndoDuringStroke() { - // we shouldn't cancel the stroke on Ctrl+Z, because it will not only - // cancel the stroke, but also undo the previous command, which we haven't - // yet pushed to the stack + if (!m_strokeId) return; + m_changesTracker.requestUndo(); } void KisToolMove::beginPrimaryAction(KoPointerEvent *event) { startAction(event, moveToolMode()); } void KisToolMove::continuePrimaryAction(KoPointerEvent *event) { continueAction(event); } void KisToolMove::endPrimaryAction(KoPointerEvent *event) { endAction(event); } void KisToolMove::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { // Ctrl+Right click toggles between moving current layer and moving layer w/ content if (action == PickFgNode || action == PickBgImage) { MoveToolMode mode = moveToolMode(); if (mode == MoveSelectedLayer) { mode = MoveFirstLayer; } else if (mode == MoveFirstLayer) { mode = MoveSelectedLayer; } startAction(event, mode); } else { startAction(event, MoveGroup); } } void KisToolMove::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(action) continueAction(event); } void KisToolMove::endAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(action) endAction(event); } void KisToolMove::startAction(KoPointerEvent *event, MoveToolMode mode) { QPoint pos = convertToPixelCoordAndSnap(event).toPoint(); m_dragStart = pos; - m_pos = QPoint(); - m_moveInProgress = true; + m_dragPos = pos; m_dragInProgress = true; - emit moveInProgressChanged(); if (startStrokeImpl(mode, &pos)) { setMode(KisTool::PAINT_MODE); } else { event->ignore(); + m_dragPos = QPoint(); + m_dragStart = QPoint(); + m_dragInProgress = false; } m_canvas->updateCanvas(); } void KisToolMove::continueAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); if (!m_strokeId) return; QPoint pos = convertToPixelCoordAndSnap(event).toPoint(); - - const bool showCoordinates = - m_optionsWidget ? m_optionsWidget->showCoordinates() : true; - - if (showCoordinates) { - KisCanvas2 *kisCanvas = dynamic_cast(canvas()); - kisCanvas->viewManager()-> - showFloatingMessage( - i18nc("floating message in move tool", - "X: %1 px, Y: %2 px", - (m_startPosition + (pos - m_dragStart)).x(), - (m_startPosition + (pos - m_dragStart)).y()), - QIcon(), 1000, KisFloatingMessage::High); - } - - KisSignalsBlocker b(m_optionsWidget); - emit moveInNewPosition(m_startPosition + (pos - m_dragStart)); - pos = applyModifiers(event->modifiers(), pos); + m_dragPos = pos; + drag(pos); - m_pos = pos - m_dragStart; + notifyGuiAfterMove(); + m_canvas->updateCanvas(); } void KisToolMove::endAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); setMode(KisTool::HOVER_MODE); if (!m_strokeId) return; QPoint pos = convertToPixelCoordAndSnap(event).toPoint(); pos = applyModifiers(event->modifiers(), pos); drag(pos); - m_pos = pos - m_dragStart; + m_accumulatedOffset += m_dragPos - m_dragStart; + m_dragStart = QPoint(); + m_dragPos = QPoint(); m_dragInProgress = false; - m_startPosition += pos - m_dragStart; - m_accumulatedOffset += pos - m_dragStart; + commitChanges(); + + notifyGuiAfterMove(); + m_canvas->updateCanvas(); } void KisToolMove::drag(const QPoint& newPos) { KisImageWSP image = currentImage(); QPoint offset = m_accumulatedOffset + newPos - m_dragStart; image->addJob(m_strokeId, new MoveStrokeStrategy::Data(offset)); } void KisToolMove::endStroke() { if (!m_strokeId) return; KisImageSP image = currentImage(); image->endStroke(m_strokeId); m_strokeId.clear(); + m_changesTracker.reset(); m_currentlyProcessingNodes.clear(); - m_moveInProgress = false; - emit moveInProgressChanged(); + m_accumulatedOffset = QPoint(); +} + +void KisToolMove::slotTrackerChangedConfig(KisToolChangesTrackerDataSP state) +{ + KIS_SAFE_ASSERT_RECOVER_RETURN(m_strokeId); + + KisToolMoveState *newState = dynamic_cast(state.data()); + KIS_SAFE_ASSERT_RECOVER_RETURN(newState); + + if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging + m_accumulatedOffset = newState->accumulatedOffset; + image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); + notifyGuiAfterMove(); } void KisToolMove::cancelStroke() { if (!m_strokeId) return; KisImageSP image = currentImage(); image->cancelStroke(m_strokeId); m_strokeId.clear(); + m_changesTracker.reset(); m_currentlyProcessingNodes.clear(); - m_moveInProgress = false; - emit moveInProgressChanged(); - - // we should reset m_startPosition into the original value when - // the stroke is cancelled - KisCanvas2 *canvas = dynamic_cast(this->canvas()); - canvas->viewManager()->blockUntilOperationsFinishedForced(image); - slotNodeChanged(this->selectedNodes()); + m_accumulatedOffset = QPoint(); + notifyGuiAfterMove(); } QWidget* KisToolMove::createOptionWidget() { if (!currentImage()) return 0; m_optionsWidget = new MoveToolOptionsWidget(0, currentImage()->xRes(), toolId()); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); m_optionsWidget->setFixedHeight(m_optionsWidget->sizeHint().height()); connect(m_showCoordinatesAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(setShowCoordinates(bool))); connect(m_optionsWidget, SIGNAL(showCoordinatesChanged(bool)), m_showCoordinatesAction, SLOT(setChecked(bool))); m_showCoordinatesAction->setChecked(m_optionsWidget->showCoordinates()); - m_optionsWidget->slotSetTranslate(m_startPosition); + m_optionsWidget->slotSetTranslate(m_handlesRect.topLeft() + currentOffset()); connect(m_optionsWidget, SIGNAL(sigSetTranslateX(int)), SLOT(moveBySpinX(int))); connect(m_optionsWidget, SIGNAL(sigSetTranslateY(int)), SLOT(moveBySpinY(int))); + connect(m_optionsWidget, SIGNAL(sigRequestCommitOffsetChanges()), this, SLOT(commitChanges())); connect(this, SIGNAL(moveInNewPosition(QPoint)), m_optionsWidget, SLOT(slotSetTranslate(QPoint))); KisCanvas2 *kisCanvas = dynamic_cast(canvas()); connect(kisCanvas->viewManager()->nodeManager(), SIGNAL(sigUiNeedChangeSelectedNodes(KisNodeList)), this, SLOT(slotNodeChanged(KisNodeList))); return m_optionsWidget; } KisToolMove::MoveToolMode KisToolMove::moveToolMode() const { if (m_optionsWidget) return m_optionsWidget->mode(); return MoveSelectedLayer; } -bool KisToolMove::moveInProgress() const -{ - return m_moveInProgress; -} - QPoint KisToolMove::applyModifiers(Qt::KeyboardModifiers modifiers, QPoint pos) { QPoint move = pos - m_dragStart; // Snap to axis if (modifiers & Qt::ShiftModifier) { move = snapToClosestAxis(move); } // "Precision mode" - scale down movement by 1/5 if (modifiers & Qt::AltModifier) { const qreal SCALE_FACTOR = .2; move = SCALE_FACTOR * move; } return m_dragStart + move; } void KisToolMove::moveBySpinX(int newX) { if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging if (!currentNode()->isEditable()) return; // Don't move invisible nodes if (startStrokeImpl(MoveSelectedLayer, 0)) { setMode(KisTool::PAINT_MODE); } - int offsetX = newX - m_startPosition.x(); - QPoint offset = QPoint(offsetX, 0); - - KisSignalsBlocker b(m_optionsWidget); - emit moveInNewPosition(m_startPosition + offset); + m_accumulatedOffset.rx() = newX - m_handlesRect.x(); - image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset + offset)); - m_accumulatedOffset += offset; - m_startPosition += offset; + image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); - m_moveInProgress = false; - emit moveInProgressChanged(); + notifyGuiAfterMove(false); setMode(KisTool::HOVER_MODE); } void KisToolMove::moveBySpinY(int newY) { if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging if (!currentNode()->isEditable()) return; // Don't move invisible nodes if (startStrokeImpl(MoveSelectedLayer, 0)) { setMode(KisTool::PAINT_MODE); } - int offsetY = newY - m_startPosition.y(); - QPoint offset = QPoint(0, offsetY); - - KisSignalsBlocker b(m_optionsWidget); - emit moveInNewPosition(m_startPosition + offset); + m_accumulatedOffset.ry() = newY - m_handlesRect.y(); - image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset + offset)); - m_accumulatedOffset += offset; - m_startPosition += offset; + image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); - m_moveInProgress = false; - emit moveInProgressChanged(); + notifyGuiAfterMove(false); setMode(KisTool::HOVER_MODE); } void KisToolMove::slotNodeChanged(KisNodeList nodes) { - QRect totalBounds; - - Q_FOREACH (KisNodeSP node, nodes) { - if (node && node->projection()) { - totalBounds |= node->projection()->nonDefaultPixelArea(); - } - } - - if (image()->globalSelection()) { - totalBounds &= image()->globalSelection()->selectedRect(); + if (m_strokeId && !tryEndPreviousStroke(nodes)) { + return; } - m_startPosition = totalBounds.topLeft(); - - if (m_optionsWidget) - { - KisSignalsBlocker b(m_optionsWidget); - m_optionsWidget->slotSetTranslate(m_startPosition); - } + initHandles(nodes); + notifyGuiAfterMove(false); } diff --git a/plugins/tools/basictools/kis_tool_move.h b/plugins/tools/basictools/kis_tool_move.h index d2dfa55727..8ed0649966 100644 --- a/plugins/tools/basictools/kis_tool_move.h +++ b/plugins/tools/basictools/kis_tool_move.h @@ -1,182 +1,186 @@ /* * Copyright (c) 1999 Matthias Elter * 1999 Michael Koch * 2003 Patrick Julien * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TOOL_MOVE_H_ #define KIS_TOOL_MOVE_H_ #include #include #include #include #include #include #include #include #include +#include "KisToolChangesTracker.h" #include "kis_canvas2.h" class KoCanvasBase; class MoveToolOptionsWidget; class KisDocument; class KisToolMove : public KisTool { Q_OBJECT Q_ENUMS(MoveToolMode); - Q_PROPERTY(bool moveInProgress READ moveInProgress NOTIFY moveInProgressChanged); public: KisToolMove(KoCanvasBase * canvas); ~KisToolMove() override; /** * @brief wantsAutoScroll * reimplemented from KoToolBase * there's an issue where autoscrolling with this tool never makes the * stroke end, so we return false here so that users don't get stuck with * the tool. See bug 362659 * @return false */ bool wantsAutoScroll() const override { return false; } public Q_SLOTS: void activate(ToolActivation toolActivation, const QSet &shapes) override; void deactivate() override; public Q_SLOTS: void requestStrokeEnd() override; void requestStrokeCancellation() override; void requestUndoDuringStroke() override; protected Q_SLOTS: void resetCursorStyle() override; public: enum MoveToolMode { MoveSelectedLayer, MoveFirstLayer, MoveGroup }; enum MoveDirection { Up, Down, Left, Right }; void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; void beginAlternateAction(KoPointerEvent *event, AlternateAction action) override; void continueAlternateAction(KoPointerEvent *event, AlternateAction action) override; void endAlternateAction(KoPointerEvent *event, AlternateAction action) override; void startAction(KoPointerEvent *event, MoveToolMode mode); void continueAction(KoPointerEvent *event); void endAction(KoPointerEvent *event); void paint(QPainter& gc, const KoViewConverter &converter) override; void initHandles(const KisNodeList &nodes); QWidget* createOptionWidget() override; void updateUIUnit(int newUnit); MoveToolMode moveToolMode() const; - bool moveInProgress() const; void setShowCoordinates(bool value); public Q_SLOTS: void moveDiscrete(MoveDirection direction, bool big); void moveBySpinX(int newX); void moveBySpinY(int newY); void slotNodeChanged(KisNodeList nodes); + void commitChanges(); Q_SIGNALS: void moveToolModeChanged(); - void moveInProgressChanged(); void moveInNewPosition(QPoint); private: void drag(const QPoint& newPos); void cancelStroke(); QPoint applyModifiers(Qt::KeyboardModifiers modifiers, QPoint pos); bool startStrokeImpl(MoveToolMode mode, const QPoint *pos); + QPoint currentOffset() const; + void notifyGuiAfterMove(bool showFloatingMessage = true); + bool tryEndPreviousStroke(KisNodeList nodes); + + private Q_SLOTS: void endStroke(); + void slotTrackerChangedConfig(KisToolChangesTrackerDataSP state); private: MoveToolOptionsWidget* m_optionsWidget; QPoint m_dragStart; ///< Point where current cursor dragging began QPoint m_accumulatedOffset; ///< Total offset including multiple clicks, up/down/left/right keys, etc. added together - QPoint m_startPosition; - KisStrokeId m_strokeId; - bool m_moveInProgress; KisNodeList m_currentlyProcessingNodes; int m_resolution; QAction *m_showCoordinatesAction; KisCanvas2* m_canvas; - QPoint m_pos; + QPoint m_dragPos; QRect m_handlesRect; bool m_dragInProgress = false; + + KisToolChangesTracker m_changesTracker; }; class KisToolMoveFactory : public KoToolFactoryBase { public: KisToolMoveFactory() : KoToolFactoryBase("KritaTransform/KisToolMove") { setToolTip(i18n("Move Tool")); setSection(TOOL_TYPE_TRANSFORM); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); setPriority(3); setIconName(koIconNameCStr("krita_tool_move")); setShortcut(QKeySequence( Qt::Key_T)); } ~KisToolMoveFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolMove(canvas); } }; #endif // KIS_TOOL_MOVE_H_ diff --git a/plugins/tools/basictools/kis_tool_movetooloptionswidget.cpp b/plugins/tools/basictools/kis_tool_movetooloptionswidget.cpp index c0c0dddfad..2aa47948ef 100644 --- a/plugins/tools/basictools/kis_tool_movetooloptionswidget.cpp +++ b/plugins/tools/basictools/kis_tool_movetooloptionswidget.cpp @@ -1,186 +1,189 @@ /* Copyright (C) 2012 Dan Leinir Turthra Jensen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "kis_tool_movetooloptionswidget.h" #include #include MoveToolOptionsWidget::MoveToolOptionsWidget(QWidget *parent, int resolution, QString toolId) : QWidget(parent) , m_resolution(resolution) , m_showCoordinates(false) { setupUi(this); m_configGroup = KSharedConfig::openConfig()->group(toolId); // load radio button m_moveToolMode = static_cast(m_configGroup.readEntry("moveToolMode", 0)); if(m_moveToolMode == KisToolMove::MoveSelectedLayer) radioSelectedLayer->setChecked(true); else if (m_moveToolMode == KisToolMove::MoveFirstLayer) radioFirstLayer->setChecked(true); else radioGroup->setChecked(true); // Keyboard shortcut move step m_moveStep = m_configGroup.readEntry("moveToolStep", 1); m_moveStepUnit = m_configGroup.readEntry("moveToolUnit", KoUnit(KoUnit::Pixel).indexInListForUi()); cmbUnit->addItems(KoUnit::listOfUnitNameForUi()); cmbUnit->setCurrentIndex(m_moveStepUnit); updateUIUnit(m_moveStepUnit); // Scale for large moves m_moveScale = m_configGroup.readEntry("moveToolScale", 10.0); spinMoveScale->blockSignals(true); spinMoveScale->setValue(m_moveScale); spinMoveScale->setSuffix("x"); spinMoveScale->blockSignals(false); // Switch mode for showing coordinates m_showCoordinates = m_configGroup.readEntry("moveToolShowCoordinates", false); connect(chkShowCoordinates, SIGNAL(toggled(bool)), SIGNAL(showCoordinatesChanged(bool))); chkShowCoordinates->setChecked(m_showCoordinates); translateXBox->setSuffix(i18n(" px")); translateYBox->setSuffix(i18n(" px")); translateXBox->setRange(-10000, 10000); translateYBox->setRange(-10000, 10000); } void MoveToolOptionsWidget::updateUIUnit(int newUnit) { const KoUnit selectedUnit = KoUnit::fromListForUi(newUnit); qreal valueForUI; if (selectedUnit != KoUnit(KoUnit::Pixel)) { spinMoveStep->setRange(0.0001, 10000.000); spinMoveStep->setSingleStep(.1); spinMoveStep->setDecimals(4); valueForUI = selectedUnit.toUserValue((qreal)m_moveStep / (qreal)m_resolution); } else { spinMoveStep->setRange(1, 10000); spinMoveStep->setSingleStep(1); spinMoveStep->setDecimals(0); valueForUI = m_moveStep; } spinMoveStep->blockSignals(true); spinMoveStep->setValue(valueForUI); spinMoveStep->blockSignals(false); + + connect(translateXBox, SIGNAL(editingFinished()), SIGNAL(sigRequestCommitOffsetChanges())); + connect(translateYBox, SIGNAL(editingFinished()), SIGNAL(sigRequestCommitOffsetChanges())); } void MoveToolOptionsWidget::on_spinMoveStep_valueChanged(double UIMoveStep) { const KoUnit selectedUnit = KoUnit::fromListForUi(m_moveStepUnit); const double scaledUiMoveStep = (selectedUnit == KoUnit(KoUnit::Pixel)) ? UIMoveStep : selectedUnit.fromUserValue(UIMoveStep * m_resolution); m_moveStep = qRound(scaledUiMoveStep); m_configGroup.writeEntry("moveToolStep", m_moveStep); } void MoveToolOptionsWidget::on_spinMoveScale_valueChanged(double UIMoveScale) { m_moveScale = UIMoveScale; m_configGroup.writeEntry("moveToolScale", m_moveScale); } void MoveToolOptionsWidget::on_cmbUnit_currentIndexChanged(int newUnit) { m_moveStepUnit = newUnit; updateUIUnit(newUnit); m_configGroup.writeEntry("moveToolUnit", newUnit); } void MoveToolOptionsWidget::on_radioSelectedLayer_toggled(bool checked) { Q_UNUSED(checked); setMoveToolMode(KisToolMove::MoveSelectedLayer); } void MoveToolOptionsWidget::on_radioFirstLayer_toggled(bool checked) { Q_UNUSED(checked); setMoveToolMode(KisToolMove::MoveFirstLayer); } void MoveToolOptionsWidget::on_radioGroup_toggled(bool checked) { Q_UNUSED(checked); setMoveToolMode(KisToolMove::MoveGroup); } void MoveToolOptionsWidget::setMoveToolMode(KisToolMove::MoveToolMode newMode) { m_moveToolMode = newMode; m_configGroup.writeEntry("moveToolMode", static_cast(newMode)); } void MoveToolOptionsWidget::on_chkShowCoordinates_toggled(bool checked) { m_showCoordinates = checked; m_configGroup.writeEntry("moveToolShowCoordinates", m_showCoordinates); } KisToolMove::MoveToolMode MoveToolOptionsWidget::mode() { return m_moveToolMode; } int MoveToolOptionsWidget::moveStep() { return m_moveStep; } double MoveToolOptionsWidget::moveScale() { return m_moveScale; } bool MoveToolOptionsWidget::showCoordinates() const { return m_showCoordinates; } void MoveToolOptionsWidget::setShowCoordinates(bool value) { chkShowCoordinates->setChecked(value); } void MoveToolOptionsWidget::slotSetTranslate(QPoint newPos) { translateXBox->setValue(newPos.x()); translateYBox->setValue(newPos.y()); } void MoveToolOptionsWidget::on_translateXBox_valueChanged(int arg1) { m_TranslateX = arg1; m_configGroup.writeEntry("moveToolChangedValueX", m_TranslateX); emit sigSetTranslateX(arg1); } void MoveToolOptionsWidget::on_translateYBox_valueChanged(int arg1) { m_TranslateY = arg1; m_configGroup.writeEntry("moveToolChangedValueY", m_TranslateY); emit sigSetTranslateY(arg1); } diff --git a/plugins/tools/basictools/kis_tool_movetooloptionswidget.h b/plugins/tools/basictools/kis_tool_movetooloptionswidget.h index f1434bfd20..7749f0e054 100644 --- a/plugins/tools/basictools/kis_tool_movetooloptionswidget.h +++ b/plugins/tools/basictools/kis_tool_movetooloptionswidget.h @@ -1,83 +1,85 @@ /* Copyright (C) 2012 Dan Leinir Turthra Jensen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef KIS_TOOL_MOVETOOLOPTIONSWIDGET_H #define KIS_TOOL_MOVETOOLOPTIONSWIDGET_H #include "ui_wdgmovetool.h" #include #include "kis_tool_move.h" class MoveToolOptionsWidget : public QWidget, public Ui::WdgMoveTool { Q_OBJECT public: MoveToolOptionsWidget(QWidget *parent, int resolution, QString toolId); int moveStep(); double moveScale(); KisToolMove::MoveToolMode mode(); bool showCoordinates() const; public Q_SLOTS: void setShowCoordinates(bool value); void slotSetTranslate(QPoint newPos); private Q_SLOTS: void on_spinMoveStep_valueChanged(double UIMoveStep); void on_spinMoveScale_valueChanged(double UIMoveScale); void on_cmbUnit_currentIndexChanged(int newUnit); void on_radioSelectedLayer_toggled(bool checked); void on_radioFirstLayer_toggled(bool checked); void on_radioGroup_toggled(bool checked); void on_chkShowCoordinates_toggled(bool checked); void on_translateXBox_valueChanged(int arg1); void on_translateYBox_valueChanged(int arg1); Q_SIGNALS: void showCoordinatesChanged(bool value); void sigSetTranslateX(int value); void sigSetTranslateY(int value); + void sigRequestCommitOffsetChanges(); + private: void updateUIUnit(int newUnit); void setMoveToolMode(KisToolMove::MoveToolMode newMode); int m_resolution; int m_moveStep; int m_moveStepUnit; qreal m_moveScale; KisToolMove::MoveToolMode m_moveToolMode; bool m_showCoordinates; int m_TranslateX; int m_TranslateY; KConfigGroup m_configGroup; }; #endif // KIS_TOOL_MOVETOOLOPTIONSWIDGET_H diff --git a/plugins/tools/tool_transform2/CMakeLists.txt b/plugins/tools/tool_transform2/CMakeLists.txt index ce61896b4f..e90230c48e 100644 --- a/plugins/tools/tool_transform2/CMakeLists.txt +++ b/plugins/tools/tool_transform2/CMakeLists.txt @@ -1,49 +1,48 @@ if (NOT WIN32 AND NOT APPLE) add_subdirectory(tests) endif() set(kritatooltransform_SOURCES tool_transform.cc tool_transform_args.cc kis_transform_mask_adapter.cpp kis_animated_transform_parameters.cpp - tool_transform_changes_tracker.cpp kis_tool_transform.cc kis_tool_transform_config_widget.cpp kis_transform_strategy_base.cpp kis_warp_transform_strategy.cpp kis_cage_transform_strategy.cpp kis_simplified_action_policy_strategy.cpp kis_liquify_transform_strategy.cpp kis_liquify_paint_helper.cpp kis_liquify_paintop.cpp kis_liquify_properties.cpp kis_free_transform_strategy.cpp kis_free_transform_strategy_gsl_helpers.cpp kis_perspective_transform_strategy.cpp kis_transform_utils.cpp kis_modify_transform_mask_command.cpp strokes/transform_stroke_strategy.cpp kis_transform_args_keyframe_channel.cpp ) qt5_add_resources(kritatooltransform_SOURCES tool_transform.qrc) ki18n_wrap_ui(kritatooltransform_SOURCES wdg_tool_transform.ui) add_library(kritatooltransform MODULE ${kritatooltransform_SOURCES}) generate_export_header(kritatooltransform BASE_NAME kritatooltransform) if (NOT GSL_FOUND) message (WARNING "KRITA WARNING! No GNU Scientific Library was found! Krita's Transform Tool will not be able to scale the image with handles. Please install GSL library.") target_link_libraries(kritatooltransform kritaui) else () target_link_libraries(kritatooltransform kritaui ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) endif () install(TARGETS kritatooltransform DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( FILES KisToolTransform.action DESTINATION ${DATA_INSTALL_DIR}/krita/actions) diff --git a/plugins/tools/tool_transform2/kis_tool_transform.cc b/plugins/tools/tool_transform2/kis_tool_transform.cc index 5bbaec35b6..09878a6da9 100644 --- a/plugins/tools/tool_transform2/kis_tool_transform.cc +++ b/plugins/tools/tool_transform2/kis_tool_transform.cc @@ -1,1278 +1,1282 @@ /* * kis_tool_transform.cc -- part of Krita * * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 C. Boemann * Copyright (c) 2010 Marc Pegon * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_transform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "widgets/kis_progress_widget.h" #include "kis_transform_utils.h" #include "kis_warp_transform_strategy.h" #include "kis_cage_transform_strategy.h" #include "kis_liquify_transform_strategy.h" #include "kis_free_transform_strategy.h" #include "kis_perspective_transform_strategy.h" #include "kis_transform_mask.h" #include "kis_transform_mask_adapter.h" #include "krita_container_utils.h" #include "kis_layer_utils.h" #include #include "strokes/transform_stroke_strategy.h" KisToolTransform::KisToolTransform(KoCanvasBase * canvas) : KisTool(canvas, KisCursor::rotateCursor()) , m_workRecursively(true) - , m_changesTracker(&m_transaction) , m_warpStrategy( new KisWarpTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) , m_cageStrategy( new KisCageTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) , m_liquifyStrategy( new KisLiquifyTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction, canvas->resourceManager())) , m_freeStrategy( new KisFreeTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), dynamic_cast(canvas)->snapGuide(), m_currentArgs, m_transaction)) , m_perspectiveStrategy( new KisPerspectiveTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), dynamic_cast(canvas)->snapGuide(), m_currentArgs, m_transaction)) { m_canvas = dynamic_cast(canvas); Q_ASSERT(m_canvas); setObjectName("tool_transform"); useCursor(KisCursor::selectCursor()); m_optionsWidget = 0; warpAction = new KisAction(i18n("Warp")); liquifyAction = new KisAction(i18n("Liquify")); cageAction = new KisAction(i18n("Cage")); freeTransformAction = new KisAction(i18n("Free")); perspectiveAction = new KisAction(i18n("Perspective")); // extra actions for free transform that are in the tool options mirrorHorizontalAction = new KisAction(i18n("Mirror Horizontal")); mirrorVericalAction = new KisAction(i18n("Mirror Vertical")); rotateNinteyCWAction = new KisAction(i18n("Rotate 90 degrees Clockwise")); rotateNinteyCCWAction = new KisAction(i18n("Rotate 90 degrees CounterClockwise")); applyTransformation = new KisAction(i18n("Apply")); resetTransformation = new KisAction(i18n("Reset")); m_contextMenu.reset(new QMenu()); connect(m_warpStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_cageStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_liquifyStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_liquifyStrategy.data(), SIGNAL(requestCursorOutlineUpdate(const QPointF&)), SLOT(cursorOutlineUpdateRequested(const QPointF&))); connect(m_liquifyStrategy.data(), SIGNAL(requestUpdateOptionWidget()), SLOT(updateOptionWidget())); connect(m_freeStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_freeStrategy.data(), SIGNAL(requestResetRotationCenterButtons()), SLOT(resetRotationCenterButtonsRequested())); connect(m_freeStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool))); connect(m_perspectiveStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_perspectiveStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool))); - connect(&m_changesTracker, SIGNAL(sigConfigChanged()), - this, SLOT(slotTrackerChangedConfig())); + connect(&m_changesTracker, SIGNAL(sigConfigChanged(KisToolChangesTrackerDataSP)), + this, SLOT(slotTrackerChangedConfig(KisToolChangesTrackerDataSP))); } KisToolTransform::~KisToolTransform() { cancelStroke(); } void KisToolTransform::outlineChanged() { emit freeTransformChanged(); m_canvas->updateCanvas(); } void KisToolTransform::canvasUpdateRequested() { m_canvas->updateCanvas(); } void KisToolTransform::resetCursorStyle() { setFunctionalCursor(); } void KisToolTransform::resetRotationCenterButtonsRequested() { if (!m_optionsWidget) return; m_optionsWidget->resetRotationCenterButtons(); } void KisToolTransform::imageTooBigRequested(bool value) { if (!m_optionsWidget) return; m_optionsWidget->setTooBigLabelVisible(value); } KisTransformStrategyBase* KisToolTransform::currentStrategy() const { if (m_currentArgs.mode() == ToolTransformArgs::FREE_TRANSFORM) { return m_freeStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::WARP) { return m_warpStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::CAGE) { return m_cageStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) { return m_liquifyStrategy.data(); } else /* if (m_currentArgs.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) */ { return m_perspectiveStrategy.data(); } } void KisToolTransform::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if (!m_strokeData.strokeId()) return; QRectF newRefRect = KisTransformUtils::imageToFlake(m_canvas->coordinatesConverter(), QRectF(0.0,0.0,1.0,1.0)); if (m_refRect != newRefRect) { m_refRect = newRefRect; currentStrategy()->externalConfigChanged(); } gc.save(); if (m_optionsWidget && m_optionsWidget->showDecorations()) { gc.setOpacity(0.3); gc.fillPath(m_selectionPath, Qt::black); } gc.restore(); currentStrategy()->paint(gc); if (!m_cursorOutline.isEmpty()) { QPainterPath mappedOutline = KisTransformUtils::imageToFlakeTransform( m_canvas->coordinatesConverter()).map(m_cursorOutline); paintToolOutline(&gc, mappedOutline); } } void KisToolTransform::setFunctionalCursor() { if (overrideCursorIfNotEditable()) { return; } if (!m_strokeData.strokeId()) { useCursor(KisCursor::pointingHandCursor()); } else { useCursor(currentStrategy()->getCurrentCursor()); } } void KisToolTransform::cursorOutlineUpdateRequested(const QPointF &imagePos) { QRect canvasUpdateRect; if (!m_cursorOutline.isEmpty()) { canvasUpdateRect = m_canvas->coordinatesConverter()-> imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect(); } m_cursorOutline = currentStrategy()-> getCursorOutline().translated(imagePos); if (!m_cursorOutline.isEmpty()) { canvasUpdateRect |= m_canvas->coordinatesConverter()-> imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect(); } if (!canvasUpdateRect.isEmpty()) { // grow rect a bit to follow interpolation fuzziness canvasUpdateRect = kisGrowRect(canvasUpdateRect, 2); m_canvas->updateCanvas(canvasUpdateRect); } } void KisToolTransform::beginActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (!nodeEditable()) { event->ignore(); return; } if (!m_strokeData.strokeId()) { startStroke(m_currentArgs.mode(), false); } else { bool result = false; if (usePrimaryAction) { result = currentStrategy()->beginPrimaryAction(event); } else { result = currentStrategy()->beginAlternateAction(event, action); } if (result) { setMode(KisTool::PAINT_MODE); } } m_actuallyMoveWhileSelected = false; outlineChanged(); } void KisToolTransform::continueActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (mode() != KisTool::PAINT_MODE) return; m_actuallyMoveWhileSelected = true; if (usePrimaryAction) { currentStrategy()->continuePrimaryAction(event); } else { currentStrategy()->continueAlternateAction(event, action); } updateOptionWidget(); outlineChanged(); } void KisToolTransform::endActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (mode() != KisTool::PAINT_MODE) return; setMode(KisTool::HOVER_MODE); if (m_actuallyMoveWhileSelected || currentStrategy()->acceptsClicks()) { bool result = false; if (usePrimaryAction) { result = currentStrategy()->endPrimaryAction(event); } else { result = currentStrategy()->endAlternateAction(event, action); } if (result) { commitChanges(); } outlineChanged(); } updateOptionWidget(); updateApplyResetAvailability(); } QMenu* KisToolTransform::popupActionsMenu() { if (m_contextMenu) { m_contextMenu->clear(); // add a quick switch to different transform types m_contextMenu->addAction(freeTransformAction); m_contextMenu->addAction(perspectiveAction); m_contextMenu->addAction(warpAction); m_contextMenu->addAction(cageAction); m_contextMenu->addAction(liquifyAction); // extra options if free transform is selected if (transformMode() == FreeTransformMode) { m_contextMenu->addSeparator(); m_contextMenu->addAction(mirrorHorizontalAction); m_contextMenu->addAction(mirrorVericalAction); m_contextMenu->addAction(rotateNinteyCWAction); m_contextMenu->addAction(rotateNinteyCCWAction); } m_contextMenu->addSeparator(); m_contextMenu->addAction(applyTransformation); m_contextMenu->addAction(resetTransformation); } return m_contextMenu.data(); } void KisToolTransform::beginPrimaryAction(KoPointerEvent *event) { beginActionImpl(event, true, KisTool::NONE); } void KisToolTransform::continuePrimaryAction(KoPointerEvent *event) { continueActionImpl(event, true, KisTool::NONE); } void KisToolTransform::endPrimaryAction(KoPointerEvent *event) { endActionImpl(event, true, KisTool::NONE); } void KisToolTransform::activatePrimaryAction() { currentStrategy()->activatePrimaryAction(); setFunctionalCursor(); } void KisToolTransform::deactivatePrimaryAction() { currentStrategy()->deactivatePrimaryAction(); } void KisToolTransform::activateAlternateAction(AlternateAction action) { currentStrategy()->activateAlternateAction(action); setFunctionalCursor(); } void KisToolTransform::deactivateAlternateAction(AlternateAction action) { currentStrategy()->deactivateAlternateAction(action); } void KisToolTransform::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { beginActionImpl(event, false, action); } void KisToolTransform::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { continueActionImpl(event, false, action); } void KisToolTransform::endAlternateAction(KoPointerEvent *event, AlternateAction action) { endActionImpl(event, false, action); } void KisToolTransform::mousePressEvent(KoPointerEvent *event) { KisTool::mousePressEvent(event); } void KisToolTransform::mouseMoveEvent(KoPointerEvent *event) { QPointF mousePos = m_canvas->coordinatesConverter()->documentToImage(event->point); cursorOutlineUpdateRequested(mousePos); if (this->mode() != KisTool::PAINT_MODE) { currentStrategy()->hoverActionCommon(event); setFunctionalCursor(); KisTool::mouseMoveEvent(event); return; } } void KisToolTransform::mouseReleaseEvent(KoPointerEvent *event) { KisTool::mouseReleaseEvent(event); } void KisToolTransform::applyTransform() { slotApplyTransform(); } KisToolTransform::TransformToolMode KisToolTransform::transformMode() const { TransformToolMode mode = FreeTransformMode; switch (m_currentArgs.mode()) { case ToolTransformArgs::FREE_TRANSFORM: mode = FreeTransformMode; break; case ToolTransformArgs::WARP: mode = WarpTransformMode; break; case ToolTransformArgs::CAGE: mode = CageTransformMode; break; case ToolTransformArgs::LIQUIFY: mode = LiquifyTransformMode; break; case ToolTransformArgs::PERSPECTIVE_4POINT: mode = PerspectiveTransformMode; break; default: KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode"); } return mode; } double KisToolTransform::translateX() const { return m_currentArgs.transformedCenter().x(); } double KisToolTransform::translateY() const { return m_currentArgs.transformedCenter().y(); } double KisToolTransform::rotateX() const { return m_currentArgs.aX(); } double KisToolTransform::rotateY() const { return m_currentArgs.aY(); } double KisToolTransform::rotateZ() const { return m_currentArgs.aZ(); } double KisToolTransform::scaleX() const { return m_currentArgs.scaleX(); } double KisToolTransform::scaleY() const { return m_currentArgs.scaleY(); } double KisToolTransform::shearX() const { return m_currentArgs.shearX(); } double KisToolTransform::shearY() const { return m_currentArgs.shearY(); } KisToolTransform::WarpType KisToolTransform::warpType() const { switch(m_currentArgs.warpType()) { case KisWarpTransformWorker::AFFINE_TRANSFORM: return AffineWarpType; case KisWarpTransformWorker::RIGID_TRANSFORM: return RigidWarpType; case KisWarpTransformWorker::SIMILITUDE_TRANSFORM: return SimilitudeWarpType; default: return RigidWarpType; } } double KisToolTransform::warpFlexibility() const { return m_currentArgs.alpha(); } int KisToolTransform::warpPointDensity() const { return m_currentArgs.numPoints(); } void KisToolTransform::setTransformMode(KisToolTransform::TransformToolMode newMode) { ToolTransformArgs::TransformMode mode = ToolTransformArgs::FREE_TRANSFORM; switch (newMode) { case FreeTransformMode: mode = ToolTransformArgs::FREE_TRANSFORM; break; case WarpTransformMode: mode = ToolTransformArgs::WARP; break; case CageTransformMode: mode = ToolTransformArgs::CAGE; break; case LiquifyTransformMode: mode = ToolTransformArgs::LIQUIFY; break; case PerspectiveTransformMode: mode = ToolTransformArgs::PERSPECTIVE_4POINT; break; default: KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode"); } if( mode != m_currentArgs.mode() ) { if( newMode == FreeTransformMode ) { m_optionsWidget->slotSetFreeTransformModeButtonClicked( true ); } else if( newMode == WarpTransformMode ) { m_optionsWidget->slotSetWarpModeButtonClicked( true ); } else if( newMode == CageTransformMode ) { m_optionsWidget->slotSetCageModeButtonClicked( true ); } else if( newMode == LiquifyTransformMode ) { m_optionsWidget->slotSetLiquifyModeButtonClicked( true ); } else if( newMode == PerspectiveTransformMode ) { m_optionsWidget->slotSetPerspectiveModeButtonClicked( true ); } emit transformModeChanged(); } } void KisToolTransform::setRotateX( double rotation ) { m_currentArgs.setAX( normalizeAngle(rotation) ); } void KisToolTransform::setRotateY( double rotation ) { m_currentArgs.setAY( normalizeAngle(rotation) ); } void KisToolTransform::setRotateZ( double rotation ) { m_currentArgs.setAZ( normalizeAngle(rotation) ); } void KisToolTransform::setWarpType( KisToolTransform::WarpType type ) { switch( type ) { case RigidWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::RIGID_TRANSFORM); break; case AffineWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::AFFINE_TRANSFORM); break; case SimilitudeWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::SIMILITUDE_TRANSFORM); break; default: break; } } void KisToolTransform::setWarpFlexibility( double flexibility ) { m_currentArgs.setAlpha( flexibility ); } void KisToolTransform::setWarpPointDensity( int density ) { m_optionsWidget->slotSetWarpDensity(density); } bool KisToolTransform::tryInitTransformModeFromNode(KisNodeSP node) { bool result = false; if (KisTransformMaskSP mask = dynamic_cast(node.data())) { KisTransformMaskParamsInterfaceSP savedParams = mask->transformParams(); KisTransformMaskAdapter *adapter = dynamic_cast(savedParams.data()); if (adapter) { m_currentArgs = adapter->transformArgs(); initGuiAfterTransformMode(); result = true; } } return result; } bool KisToolTransform::tryFetchArgsFromCommandAndUndo(ToolTransformArgs *args, ToolTransformArgs::TransformMode mode, KisNodeSP currentNode) { bool result = false; const KUndo2Command *lastCommand = image()->undoAdapter()->presentCommand(); KisNodeSP oldRootNode; KisNodeList oldTransformedNodes; if (lastCommand && TransformStrokeStrategy::fetchArgsFromCommand(lastCommand, args, &oldRootNode, &oldTransformedNodes) && args->mode() == mode && oldRootNode == currentNode) { KisNodeList perspectiveTransformedNodes = fetchNodesList(mode, currentNode, m_workRecursively); if (KritaUtils::compareListsUnordered(oldTransformedNodes, perspectiveTransformedNodes)) { args->saveContinuedState(); image()->undoAdapter()->undoLastCommand(); // FIXME: can we make it async? image()->waitForDone(); forceRepaintDelayedLayers(oldRootNode); result = true; } } return result; } void KisToolTransform::initTransformMode(ToolTransformArgs::TransformMode mode) { // NOTE: we are requesting an old value of m_currentArgs variable // here, which is global, don't forget about this on higher // levels. QString filterId = m_currentArgs.filterId(); m_currentArgs = ToolTransformArgs(); m_currentArgs.setOriginalCenter(m_transaction.originalCenterGeometric()); m_currentArgs.setTransformedCenter(m_transaction.originalCenterGeometric()); if (mode == ToolTransformArgs::FREE_TRANSFORM) { m_currentArgs.setMode(ToolTransformArgs::FREE_TRANSFORM); } else if (mode == ToolTransformArgs::WARP) { m_currentArgs.setMode(ToolTransformArgs::WARP); m_optionsWidget->setDefaultWarpPoints(); m_currentArgs.setEditingTransformPoints(false); } else if (mode == ToolTransformArgs::CAGE) { m_currentArgs.setMode(ToolTransformArgs::CAGE); m_currentArgs.setEditingTransformPoints(true); } else if (mode == ToolTransformArgs::LIQUIFY) { m_currentArgs.setMode(ToolTransformArgs::LIQUIFY); const QRect srcRect = m_transaction.originalRect().toAlignedRect(); if (!srcRect.isEmpty()) { m_currentArgs.initLiquifyTransformMode(m_transaction.originalRect().toAlignedRect()); } } else if (mode == ToolTransformArgs::PERSPECTIVE_4POINT) { m_currentArgs.setMode(ToolTransformArgs::PERSPECTIVE_4POINT); } initGuiAfterTransformMode(); } void KisToolTransform::initGuiAfterTransformMode() { currentStrategy()->externalConfigChanged(); outlineChanged(); updateOptionWidget(); updateApplyResetAvailability(); } void KisToolTransform::updateSelectionPath() { m_selectionPath = QPainterPath(); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); QPainterPath selectionOutline; KisSelectionSP selection = resources->activeSelection(); if (selection && selection->outlineCacheValid()) { selectionOutline = selection->outlineCache(); } else { selectionOutline.addRect(m_selectedPortionCache->exactBounds()); } const KisCoordinatesConverter *converter = m_canvas->coordinatesConverter(); QTransform i2f = converter->imageToDocumentTransform() * converter->documentToFlakeTransform(); m_selectionPath = i2f.map(selectionOutline); } void KisToolTransform::initThumbnailImage(KisPaintDeviceSP previewDevice) { QImage origImg; m_selectedPortionCache = previewDevice; QTransform thumbToImageTransform; const int maxSize = 2000; QRect srcRect(m_transaction.originalRect().toAlignedRect()); int x, y, w, h; srcRect.getRect(&x, &y, &w, &h); if (w > maxSize || h > maxSize) { qreal scale = qreal(maxSize) / (w > h ? w : h); QTransform scaleTransform = QTransform::fromScale(scale, scale); QRect thumbRect = scaleTransform.mapRect(m_transaction.originalRect()).toAlignedRect(); origImg = m_selectedPortionCache-> createThumbnail(thumbRect.width(), thumbRect.height(), srcRect, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); thumbToImageTransform = scaleTransform.inverted(); } else { origImg = m_selectedPortionCache->convertToQImage(0, x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); thumbToImageTransform = QTransform(); } // init both strokes since the thumbnail is initialized only once // during the stroke m_freeStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_perspectiveStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_warpStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_cageStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_liquifyStrategy->setThumbnailImage(origImg, thumbToImageTransform); } void KisToolTransform::activate(ToolActivation toolActivation, const QSet &shapes) { KisTool::activate(toolActivation, shapes); if (currentNode()) { m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); } startStroke(ToolTransformArgs::FREE_TRANSFORM, false); } void KisToolTransform::deactivate() { endStroke(); m_canvas->updateCanvas(); KisTool::deactivate(); } void KisToolTransform::requestUndoDuringStroke() { if (!m_strokeData.strokeId()) return; m_changesTracker.requestUndo(); } void KisToolTransform::requestStrokeEnd() { endStroke(); } void KisToolTransform::requestStrokeCancellation() { if (m_currentArgs.isIdentity()) { cancelStroke(); } else { slotResetTransform(); } } void KisToolTransform::startStroke(ToolTransformArgs::TransformMode mode, bool forceReset) { Q_ASSERT(!m_strokeData.strokeId()); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); KisNodeSP currentNode = resources->currentNode(); if (!currentNode || !currentNode->isEditable()) { return; } /** * FIXME: The transform tool is not completely asynchronous, it * needs the content of the layer for creation of the stroke * strategy. It means that we cannot start a new stroke until the * previous one is finished. Ideally, we should create the * m_selectedPortionCache and m_selectionPath somewhere in the * stroke and pass it to the tool somehow. But currently, we will * just disable starting a new stroke asynchronously */ if (image()->tryBarrierLock()) { image()->unlock(); } else { return; } /** * We must ensure that the currently selected subtree * has finished all its updates. */ forceRepaintDelayedLayers(currentNode); ToolTransformArgs fetchedArgs; bool fetchedFromCommand = false; if (!forceReset) { fetchedFromCommand = tryFetchArgsFromCommandAndUndo(&fetchedArgs, mode, currentNode); } if (m_optionsWidget) { m_workRecursively = m_optionsWidget->workRecursively() || !currentNode->paintDevice(); } QList nodesList = fetchNodesList(mode, currentNode, m_workRecursively); if (nodesList.isEmpty()) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Selected layer cannot be transformed with active transformation mode "), koIcon("object-locked"), 4000, KisFloatingMessage::High); // force-reset transform mode to default initTransformMode(mode); return; } TransformStrokeStrategy *strategy = new TransformStrokeStrategy(currentNode, nodesList, resources->activeSelection(), image().data()); KisPaintDeviceSP previewDevice = strategy->previewDevice(); KisSelectionSP selection = strategy->realSelection(); QRect srcRect = selection ? selection->selectedExactRect() : previewDevice->exactBounds(); if (!selection && resources->activeSelection()) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Selections are not used when editing transform masks "), QIcon(), 4000, KisFloatingMessage::Low); } if (srcRect.isEmpty()) { delete strategy; KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Cannot transform empty layer "), QIcon(), 1000, KisFloatingMessage::Medium); // force-reset transform mode to default initTransformMode(mode); return; } m_transaction = TransformTransactionProperties(srcRect, &m_currentArgs, currentNode, nodesList); initThumbnailImage(previewDevice); updateSelectionPath(); if (!forceReset && fetchedFromCommand) { m_currentArgs = fetchedArgs; initGuiAfterTransformMode(); } else if (forceReset || !tryInitTransformModeFromNode(currentNode)) { initTransformMode(mode); } m_strokeData = StrokeData(image()->startStroke(strategy)); bool haveInvisibleNodes = clearDevices(nodesList); if (haveInvisibleNodes) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Invisible sublayers will also be transformed. Lock layers if you do not want them to be transformed "), QIcon(), 4000, KisFloatingMessage::Low); } Q_ASSERT(m_changesTracker.isEmpty()); commitChanges(); } void KisToolTransform::endStroke() { if (!m_strokeData.strokeId()) return; if (!m_currentArgs.isIdentity()) { transformClearedDevices(); image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::TransformData( TransformStrokeStrategy::TransformData::SELECTION, m_currentArgs, m_transaction.rootNode())); // root node is used for progress only image()->endStroke(m_strokeData.strokeId()); } else { image()->cancelStroke(m_strokeData.strokeId()); } m_strokeData.clear(); m_changesTracker.reset(); m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); outlineChanged(); } void KisToolTransform::cancelStroke() { if (!m_strokeData.strokeId()) return; if (m_currentArgs.continuedTransform()) { m_currentArgs.restoreContinuedState(); endStroke(); } else { image()->cancelStroke(m_strokeData.strokeId()); m_strokeData.clear(); m_changesTracker.reset(); m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); outlineChanged(); } } void KisToolTransform::commitChanges() { if (!m_strokeData.strokeId()) return; - m_changesTracker.commitConfig(m_currentArgs); + m_changesTracker.commitConfig(toQShared(m_currentArgs.clone())); } -void KisToolTransform::slotTrackerChangedConfig() +void KisToolTransform::slotTrackerChangedConfig(KisToolChangesTrackerDataSP status) { + const ToolTransformArgs *newArgs = dynamic_cast(status.data()); + KIS_SAFE_ASSERT_RECOVER_RETURN(newArgs); + + *m_transaction.currentConfig() = *newArgs; + slotUiChangedConfig(); updateOptionWidget(); } QList KisToolTransform::fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive) { QList result; auto fetchFunc = [&result, mode, root] (KisNodeSP node) { if (node->isEditable(node == root) && (!node->inherits("KisShapeLayer") || mode == ToolTransformArgs::FREE_TRANSFORM) && !node->inherits("KisFileLayer") && (!node->inherits("KisTransformMask") || node == root)) { result << node; } }; if (recursive) { KisLayerUtils::recursiveApplyNodes(root, fetchFunc); } else { fetchFunc(root); } return result; } bool KisToolTransform::clearDevices(const QList &nodes) { bool haveInvisibleNodes = false; Q_FOREACH (KisNodeSP node, nodes) { haveInvisibleNodes |= !node->visible(false); image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::ClearSelectionData(node)); /** * It might happen that the editablity state of the node would * change during the stroke, so we need to save the set of * applicable nodes right in the beginning of the processing */ m_strokeData.addClearedNode(node); } return haveInvisibleNodes; } void KisToolTransform::transformClearedDevices() { Q_FOREACH (KisNodeSP node, m_strokeData.clearedNodes()) { KIS_ASSERT_RECOVER_RETURN(node); image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::TransformData( TransformStrokeStrategy::TransformData::PAINT_DEVICE, m_currentArgs, node)); } } QWidget* KisToolTransform::createOptionWidget() { m_optionsWidget = new KisToolTransformConfigWidget(&m_transaction, m_canvas, m_workRecursively, 0); Q_CHECK_PTR(m_optionsWidget); m_optionsWidget->setObjectName(toolId() + " option widget"); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); connect(m_optionsWidget, SIGNAL(sigConfigChanged()), this, SLOT(slotUiChangedConfig())); connect(m_optionsWidget, SIGNAL(sigApplyTransform()), this, SLOT(slotApplyTransform())); connect(m_optionsWidget, SIGNAL(sigResetTransform()), this, SLOT(slotResetTransform())); connect(m_optionsWidget, SIGNAL(sigRestartTransform()), this, SLOT(slotRestartTransform())); connect(m_optionsWidget, SIGNAL(sigEditingFinished()), this, SLOT(slotEditingFinished())); connect(mirrorHorizontalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipX())); connect(mirrorVericalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipY())); connect(rotateNinteyCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCW())); connect(rotateNinteyCCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCCW())); connect(warpAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToWarpType())); connect(perspectiveAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToPerspectiveType())); connect(freeTransformAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToFreeTransformType())); connect(liquifyAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToLiquifyType())); connect(cageAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToCageType())); connect(applyTransformation, SIGNAL(triggered(bool)), this, SLOT(slotApplyTransform())); connect(resetTransformation, SIGNAL(triggered(bool)), this, SLOT(slotResetTransform())); updateOptionWidget(); return m_optionsWidget; } void KisToolTransform::updateOptionWidget() { if (!m_optionsWidget) return; if (!currentNode()) { m_optionsWidget->setEnabled(false); return; } else { m_optionsWidget->setEnabled(true); m_optionsWidget->updateConfig(m_currentArgs); } } void KisToolTransform::updateApplyResetAvailability() { if (m_optionsWidget) { m_optionsWidget->setApplyResetDisabled(m_currentArgs.isIdentity()); } } void KisToolTransform::slotUiChangedConfig() { if (mode() == KisTool::PAINT_MODE) return; currentStrategy()->externalConfigChanged(); if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) { m_currentArgs.saveLiquifyTransformMode(); } outlineChanged(); updateApplyResetAvailability(); } void KisToolTransform::slotApplyTransform() { QApplication::setOverrideCursor(KisCursor::waitCursor()); endStroke(); QApplication::restoreOverrideCursor(); } void KisToolTransform::slotResetTransform() { if (m_currentArgs.continuedTransform()) { ToolTransformArgs::TransformMode savedMode = m_currentArgs.mode(); /** * Our reset transform button can be used for two purposes: * * 1) Reset current transform to the initial one, which was * loaded from the previous user action. * * 2) Reset transform frame to infinity when the frame is unchanged */ const bool transformDiffers = !m_currentArgs.continuedTransform()->isSameMode(m_currentArgs); if (transformDiffers && m_currentArgs.continuedTransform()->mode() == savedMode) { m_currentArgs.restoreContinuedState(); initGuiAfterTransformMode(); slotEditingFinished(); } else { KisNodeSP root = m_transaction.rootNode() ? m_transaction.rootNode() : image()->root(); cancelStroke(); image()->waitForDone(); forceRepaintDelayedLayers(root); startStroke(savedMode, true); KIS_ASSERT_RECOVER_NOOP(!m_currentArgs.continuedTransform()); } } else { initTransformMode(m_currentArgs.mode()); slotEditingFinished(); } } void KisToolTransform::slotRestartTransform() { if (!m_strokeData.strokeId()) return; KisNodeSP root = m_transaction.rootNode(); KIS_ASSERT_RECOVER_RETURN(root); // the stroke is guaranteed to be started by an 'if' above ToolTransformArgs savedArgs(m_currentArgs); cancelStroke(); image()->waitForDone(); forceRepaintDelayedLayers(root); startStroke(savedArgs.mode(), true); } void KisToolTransform::forceRepaintDelayedLayers(KisNodeSP root) { KIS_SAFE_ASSERT_RECOVER_RETURN(root); KisLayerUtils::forceAllDelayedNodesUpdate(root); image()->waitForDone(); } void KisToolTransform::slotEditingFinished() { commitChanges(); } void KisToolTransform::slotUpdateToWarpType() { setTransformMode(KisToolTransform::TransformToolMode::WarpTransformMode); } void KisToolTransform::slotUpdateToPerspectiveType() { setTransformMode(KisToolTransform::TransformToolMode::PerspectiveTransformMode); } void KisToolTransform::slotUpdateToFreeTransformType() { setTransformMode(KisToolTransform::TransformToolMode::FreeTransformMode); } void KisToolTransform::slotUpdateToLiquifyType() { setTransformMode(KisToolTransform::TransformToolMode::LiquifyTransformMode); } void KisToolTransform::slotUpdateToCageType() { setTransformMode(KisToolTransform::TransformToolMode::CageTransformMode); } void KisToolTransform::setShearY(double shear) { m_optionsWidget->slotSetShearY(shear); } void KisToolTransform::setShearX(double shear) { m_optionsWidget->slotSetShearX(shear); } void KisToolTransform::setScaleY(double scale) { m_optionsWidget->slotSetScaleY(scale); } void KisToolTransform::setScaleX(double scale) { m_optionsWidget->slotSetScaleX(scale); } void KisToolTransform::setTranslateY(double translation) { m_optionsWidget->slotSetTranslateY(translation); } void KisToolTransform::setTranslateX(double translation) { m_optionsWidget->slotSetTranslateX(translation); } diff --git a/plugins/tools/tool_transform2/kis_tool_transform.h b/plugins/tools/tool_transform2/kis_tool_transform.h index 15e034c533..e03b2f0dec 100644 --- a/plugins/tools/tool_transform2/kis_tool_transform.h +++ b/plugins/tools/tool_transform2/kis_tool_transform.h @@ -1,378 +1,378 @@ /* * kis_tool_transform.h - part of Krita * * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 C. Boemann * Copyright (c) 2010 Marc Pegon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TOOL_TRANSFORM_H_ #define KIS_TOOL_TRANSFORM_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tool_transform_args.h" -#include "tool_transform_changes_tracker.h" +#include "KisToolChangesTracker.h" #include "kis_tool_transform_config_widget.h" #include "transform_transaction_properties.h" class QTouchEvent; class KisTransformStrategyBase; class KisWarpTransformStrategy; class KisCageTransformStrategy; class KisLiquifyTransformStrategy; class KisFreeTransformStrategy; class KisPerspectiveTransformStrategy; /** * Transform tool * This tool offers several modes. * - Free Transform mode allows the user to translate, scale, shear, rotate and * apply a perspective transformation to a selection or the whole canvas. * - Warp mode allows the user to warp the selection of the canvas by grabbing * and moving control points placed on the image. The user can either work * with default control points, like a grid whose density can be modified, or * place the control points manually. The modifications made on the selected * pixels are applied only when the user clicks the Apply button : the * semi-transparent image displayed until the user click that button is only a * preview. * - Cage transform is similar to warp transform with control points exactly * placed on the outer boundary. The user draws a boundary polygon, the * vertices of which become control points. * - Perspective transform applies a two-point perspective transformation. The * user can manipulate the corners of the selection. If the vanishing points * of the resulting quadrilateral are on screen, the user can manipulate those * as well. * - Liquify transform transforms the selection by painting motions, as if the * user were fingerpainting. */ class KisToolTransform : public KisTool { Q_OBJECT Q_PROPERTY(TransformToolMode transformMode READ transformMode WRITE setTransformMode NOTIFY transformModeChanged) Q_PROPERTY(double translateX READ translateX WRITE setTranslateX NOTIFY freeTransformChanged) Q_PROPERTY(double translateY READ translateY WRITE setTranslateY NOTIFY freeTransformChanged) Q_PROPERTY(double rotateX READ rotateX WRITE setRotateX NOTIFY freeTransformChanged) Q_PROPERTY(double rotateY READ rotateY WRITE setRotateY NOTIFY freeTransformChanged) Q_PROPERTY(double rotateZ READ rotateZ WRITE setRotateZ NOTIFY freeTransformChanged) Q_PROPERTY(double scaleX READ scaleX WRITE setScaleX NOTIFY freeTransformChanged) Q_PROPERTY(double scaleY READ scaleY WRITE setScaleY NOTIFY freeTransformChanged) Q_PROPERTY(double shearX READ shearX WRITE setShearX NOTIFY freeTransformChanged) Q_PROPERTY(double shearY READ shearY WRITE setShearY NOTIFY freeTransformChanged) Q_PROPERTY(WarpType warpType READ warpType WRITE setWarpType NOTIFY warpTransformChanged) Q_PROPERTY(double warpFlexibility READ warpFlexibility WRITE setWarpFlexibility NOTIFY warpTransformChanged) Q_PROPERTY(int warpPointDensity READ warpPointDensity WRITE setWarpPointDensity NOTIFY warpTransformChanged) public: enum TransformToolMode { FreeTransformMode, WarpTransformMode, CageTransformMode, LiquifyTransformMode, PerspectiveTransformMode }; Q_ENUMS(TransformToolMode) enum WarpType { RigidWarpType, AffineWarpType, SimilitudeWarpType }; Q_ENUMS(WarpType) KisToolTransform(KoCanvasBase * canvas); ~KisToolTransform() override; /** * @brief wantsAutoScroll * reimplemented from KoToolBase * there's an issue where autoscrolling with this tool never makes the * stroke end, so we return false here so that users don't get stuck with * the tool. See bug 362659 * @return false */ bool wantsAutoScroll() const { return false; } QWidget* createOptionWidget() override; void mousePressEvent(KoPointerEvent *e) override; void mouseMoveEvent(KoPointerEvent *e) override; void mouseReleaseEvent(KoPointerEvent *e) override; void beginActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action); void continueActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action); void endActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action); QMenu* popupActionsMenu() override; void activatePrimaryAction() override; void deactivatePrimaryAction() override; void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; void activateAlternateAction(AlternateAction action) override; void deactivateAlternateAction(AlternateAction action) override; void beginAlternateAction(KoPointerEvent *event, AlternateAction action) override; void continueAlternateAction(KoPointerEvent *event, AlternateAction action) override; void endAlternateAction(KoPointerEvent *event, AlternateAction action) override; void paint(QPainter& gc, const KoViewConverter &converter) override; TransformToolMode transformMode() const; double translateX() const; double translateY() const; double rotateX() const; double rotateY() const; double rotateZ() const; double scaleX() const; double scaleY() const; double shearX() const; double shearY() const; WarpType warpType() const; double warpFlexibility() const; int warpPointDensity() const; public Q_SLOTS: void activate(ToolActivation toolActivation, const QSet &shapes) override; void deactivate() override; // Applies the current transformation to the original paint device and commits it to the undo stack void applyTransform(); void setTransformMode( KisToolTransform::TransformToolMode newMode ); void setTranslateX(double translateX); void setTranslateY(double translateY); void setRotateX(double rotation); void setRotateY(double rotation); void setRotateZ(double rotation); void setScaleX(double scaleX); void setScaleY(double scaleY); void setShearX(double shearX); void setShearY(double shearY); void setWarpType(WarpType type); void setWarpFlexibility(double flexibility); void setWarpPointDensity(int density); protected Q_SLOTS: void resetCursorStyle() override; Q_SIGNALS: void transformModeChanged(); void freeTransformChanged(); void warpTransformChanged(); public Q_SLOTS: void requestUndoDuringStroke() override; void requestStrokeEnd() override; void requestStrokeCancellation() override; void canvasUpdateRequested(); void cursorOutlineUpdateRequested(const QPointF &imagePos); // Update the widget according to m_currentArgs void updateOptionWidget(); void resetRotationCenterButtonsRequested(); void imageTooBigRequested(bool value); private: QList fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive); QScopedPointer m_contextMenu; bool clearDevices(const QList &nodes); void transformClearedDevices(); void startStroke(ToolTransformArgs::TransformMode mode, bool forceReset); void endStroke(); void cancelStroke(); private: void outlineChanged(); // Sets the cursor according to mouse position (doesn't take shearing into account well yet) void setFunctionalCursor(); // Sets m_function according to mouse position and modifier void setTransformFunction(QPointF mousePos, Qt::KeyboardModifiers modifiers); void commitChanges(); bool tryInitTransformModeFromNode(KisNodeSP node); bool tryFetchArgsFromCommandAndUndo(ToolTransformArgs *args, ToolTransformArgs::TransformMode mode, KisNodeSP currentNode); void initTransformMode(ToolTransformArgs::TransformMode mode); void initGuiAfterTransformMode(); void initThumbnailImage(KisPaintDeviceSP previewDevice); void updateSelectionPath(); void updateApplyResetAvailability(); void forceRepaintDelayedLayers(KisNodeSP root); private: ToolTransformArgs m_currentArgs; bool m_actuallyMoveWhileSelected; // true <=> selection has been moved while clicked KisPaintDeviceSP m_selectedPortionCache; struct StrokeData { StrokeData() {} StrokeData(KisStrokeId strokeId) : m_strokeId(strokeId) {} void clear() { m_strokeId.clear(); m_clearedNodes.clear(); } const KisStrokeId strokeId() const { return m_strokeId; } void addClearedNode(KisNodeSP node) { m_clearedNodes.append(node); } const QVector& clearedNodes() const { return m_clearedNodes; } private: KisStrokeId m_strokeId; QVector m_clearedNodes; }; StrokeData m_strokeData; bool m_workRecursively; QPainterPath m_selectionPath; // original (unscaled) selection outline, used for painting decorations KisToolTransformConfigWidget *m_optionsWidget; QPointer m_canvas; TransformTransactionProperties m_transaction; - TransformChangesTracker m_changesTracker; + KisToolChangesTracker m_changesTracker; /// actions for the context click menu KisAction* warpAction; KisAction* liquifyAction; KisAction* cageAction; KisAction* freeTransformAction; KisAction* perspectiveAction; KisAction* applyTransformation; KisAction* resetTransformation; // a few extra context click options if free transform is active KisAction* mirrorHorizontalAction; KisAction* mirrorVericalAction; KisAction* rotateNinteyCWAction; KisAction* rotateNinteyCCWAction; /** * This artificial rect is used to store the image to flake * transformation. We check against this rect to get to know * whether zoom has changed. */ QRectF m_refRect; QScopedPointer m_warpStrategy; QScopedPointer m_cageStrategy; QScopedPointer m_liquifyStrategy; QScopedPointer m_freeStrategy; QScopedPointer m_perspectiveStrategy; KisTransformStrategyBase* currentStrategy() const; QPainterPath m_cursorOutline; private Q_SLOTS: - void slotTrackerChangedConfig(); + void slotTrackerChangedConfig(KisToolChangesTrackerDataSP status); void slotUiChangedConfig(); void slotApplyTransform(); void slotResetTransform(); void slotRestartTransform(); void slotEditingFinished(); // context menu options for updating the transform type // this is to help with discoverability since come people can't find the tool options void slotUpdateToWarpType(); void slotUpdateToPerspectiveType(); void slotUpdateToFreeTransformType(); void slotUpdateToLiquifyType(); void slotUpdateToCageType(); }; class KisToolTransformFactory : public KoToolFactoryBase { public: KisToolTransformFactory() : KoToolFactoryBase("KisToolTransform") { setToolTip(i18n("Transform a layer or a selection")); setSection(TOOL_TYPE_TRANSFORM); setIconName(koIconNameCStr("krita_tool_transform")); setShortcut(QKeySequence(Qt::CTRL + Qt::Key_T)); setPriority(2); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); } ~KisToolTransformFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolTransform(canvas); } }; #endif // KIS_TOOL_TRANSFORM_H_ diff --git a/plugins/tools/tool_transform2/tool_transform_args.cc b/plugins/tools/tool_transform2/tool_transform_args.cc index 1a2612d47e..7453961e8e 100644 --- a/plugins/tools/tool_transform2/tool_transform_args.cc +++ b/plugins/tools/tool_transform2/tool_transform_args.cc @@ -1,486 +1,491 @@ /* * tool_transform_args.h - part of Krita * * Copyright (c) 2010 Marc Pegon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "tool_transform_args.h" #include #include #include #include #include "kis_liquify_transform_worker.h" #include "kis_dom_utils.h" ToolTransformArgs::ToolTransformArgs() : m_liquifyProperties(new KisLiquifyProperties()) { m_mode = FREE_TRANSFORM; m_transformedCenter = QPointF(0, 0); m_originalCenter = QPointF(0, 0); m_rotationCenterOffset = QPointF(0, 0); m_cameraPos = QVector3D(0,0,1024); m_aX = 0; m_aY = 0; m_aZ = 0; m_scaleX = 1.0; m_scaleY = 1.0; m_shearX = 0.0; m_shearY = 0.0; m_origPoints = QVector(); m_transfPoints = QVector(); m_warpType = KisWarpTransformWorker::RIGID_TRANSFORM; m_alpha = 1.0; m_keepAspectRatio = false; m_defaultPoints = true; KConfigGroup configGroup = KSharedConfig::openConfig()->group("KisToolTransform"); QString savedFilterId = configGroup.readEntry("filterId", "Bicubic"); setFilterId(savedFilterId); m_transformAroundRotationCenter = configGroup.readEntry("transformAroundRotationCenter", "0").toInt(); m_editTransformPoints = false; } void ToolTransformArgs::setFilterId(const QString &id) { m_filter = KisFilterStrategyRegistry::instance()->value(id); if (m_filter) { KConfigGroup configGroup = KSharedConfig::openConfig()->group("KisToolTransform"); configGroup.writeEntry("filterId", id); } } void ToolTransformArgs::setTransformAroundRotationCenter(bool value) { m_transformAroundRotationCenter = value; KConfigGroup configGroup = KSharedConfig::openConfig()->group("KisToolTransform"); configGroup.writeEntry("transformAroundRotationCenter", int(value)); } void ToolTransformArgs::init(const ToolTransformArgs& args) { m_mode = args.mode(); m_transformedCenter = args.transformedCenter(); m_originalCenter = args.originalCenter(); m_rotationCenterOffset = args.rotationCenterOffset(); m_transformAroundRotationCenter = args.transformAroundRotationCenter(); m_cameraPos = args.m_cameraPos; m_aX = args.aX(); m_aY = args.aY(); m_aZ = args.aZ(); m_scaleX = args.scaleX(); m_scaleY = args.scaleY(); m_shearX = args.shearX(); m_shearY = args.shearY(); m_origPoints = args.origPoints(); //it's a copy m_transfPoints = args.transfPoints(); m_warpType = args.warpType(); m_alpha = args.alpha(); m_defaultPoints = args.defaultPoints(); m_keepAspectRatio = args.keepAspectRatio(); m_filter = args.m_filter; m_flattenedPerspectiveTransform = args.m_flattenedPerspectiveTransform; m_editTransformPoints = args.m_editTransformPoints; if (args.m_liquifyWorker) { m_liquifyWorker.reset(new KisLiquifyTransformWorker(*args.m_liquifyWorker.data())); } m_continuedTransformation.reset(args.m_continuedTransformation ? new ToolTransformArgs(*args.m_continuedTransformation) : 0); } void ToolTransformArgs::clear() { m_origPoints.clear(); m_transfPoints.clear(); } ToolTransformArgs::ToolTransformArgs(const ToolTransformArgs& args) : m_liquifyProperties(args.m_liquifyProperties) { init(args); } +KisToolChangesTrackerData *ToolTransformArgs::clone() const +{ + return new ToolTransformArgs(*this); +} + ToolTransformArgs& ToolTransformArgs::operator=(const ToolTransformArgs& args) { clear(); m_liquifyProperties = args.m_liquifyProperties; init(args); return *this; } bool ToolTransformArgs::operator==(const ToolTransformArgs& other) const { return m_mode == other.m_mode && m_defaultPoints == other.m_defaultPoints && m_origPoints == other.m_origPoints && m_transfPoints == other.m_transfPoints && m_warpType == other.m_warpType && m_alpha == other.m_alpha && m_transformedCenter == other.m_transformedCenter && m_originalCenter == other.m_originalCenter && m_rotationCenterOffset == other.m_rotationCenterOffset && m_transformAroundRotationCenter == other.m_transformAroundRotationCenter && m_aX == other.m_aX && m_aY == other.m_aY && m_aZ == other.m_aZ && m_cameraPos == other.m_cameraPos && m_scaleX == other.m_scaleX && m_scaleY == other.m_scaleY && m_shearX == other.m_shearX && m_shearY == other.m_shearY && m_keepAspectRatio == other.m_keepAspectRatio && m_flattenedPerspectiveTransform == other.m_flattenedPerspectiveTransform && m_editTransformPoints == other.m_editTransformPoints && (m_liquifyProperties == other.m_liquifyProperties || *m_liquifyProperties == *other.m_liquifyProperties) && // pointer types ((m_filter && other.m_filter && m_filter->id() == other.m_filter->id()) || m_filter == other.m_filter) && ((m_liquifyWorker && other.m_liquifyWorker && *m_liquifyWorker == *other.m_liquifyWorker) || m_liquifyWorker == other.m_liquifyWorker); } bool ToolTransformArgs::isSameMode(const ToolTransformArgs& other) const { if (m_mode != other.m_mode) return false; bool result = true; if (m_mode == FREE_TRANSFORM) { result &= m_transformedCenter == other.m_transformedCenter; result &= m_originalCenter == other.m_originalCenter; result &= m_scaleX == other.m_scaleX; result &= m_scaleY == other.m_scaleY; result &= m_shearX == other.m_shearX; result &= m_shearY == other.m_shearY; result &= m_aX == other.m_aX; result &= m_aY == other.m_aY; result &= m_aZ == other.m_aZ; } else if (m_mode == PERSPECTIVE_4POINT) { result &= m_transformedCenter == other.m_transformedCenter; result &= m_originalCenter == other.m_originalCenter; result &= m_scaleX == other.m_scaleX; result &= m_scaleY == other.m_scaleY; result &= m_shearX == other.m_shearX; result &= m_shearY == other.m_shearY; result &= m_flattenedPerspectiveTransform == other.m_flattenedPerspectiveTransform; } else if(m_mode == WARP || m_mode == CAGE) { result &= m_origPoints == other.m_origPoints; result &= m_transfPoints == other.m_transfPoints; } else if (m_mode == LIQUIFY) { result &= m_liquifyProperties && (m_liquifyProperties == other.m_liquifyProperties || *m_liquifyProperties == *other.m_liquifyProperties); result &= (m_liquifyWorker && other.m_liquifyWorker && *m_liquifyWorker == *other.m_liquifyWorker) || m_liquifyWorker == other.m_liquifyWorker; } else { KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "unknown transform mode"); } return result; } ToolTransformArgs::ToolTransformArgs(TransformMode mode, QPointF transformedCenter, QPointF originalCenter, QPointF rotationCenterOffset, bool transformAroundRotationCenter, double aX, double aY, double aZ, double scaleX, double scaleY, double shearX, double shearY, KisWarpTransformWorker::WarpType warpType, double alpha, bool defaultPoints, const QString &filterId) : m_liquifyProperties(new KisLiquifyProperties()) { m_mode = mode; m_transformedCenter = transformedCenter; m_originalCenter = originalCenter; m_rotationCenterOffset = rotationCenterOffset; m_transformAroundRotationCenter = transformAroundRotationCenter; m_cameraPos = QVector3D(0,0,1024); m_aX = aX; m_aY = aY; m_aZ = aZ; m_scaleX = scaleX; m_scaleY = scaleY; m_shearX = shearX; m_shearY = shearY; m_origPoints = QVector(); m_transfPoints = QVector(); m_warpType = warpType; m_alpha = alpha; m_defaultPoints = defaultPoints; m_keepAspectRatio = false; setFilterId(filterId); m_editTransformPoints = false; } ToolTransformArgs::~ToolTransformArgs() { clear(); } void ToolTransformArgs::translate(const QPointF &offset) { if (m_mode == FREE_TRANSFORM || m_mode == PERSPECTIVE_4POINT) { m_originalCenter += offset; m_rotationCenterOffset += offset; m_transformedCenter += offset; } else if(m_mode == WARP || m_mode == CAGE) { for (auto &pt : m_origPoints) { pt += offset; } for (auto &pt : m_transfPoints) { pt += offset; } } else if (m_mode == LIQUIFY) { KIS_ASSERT_RECOVER_RETURN(m_liquifyWorker); m_liquifyWorker->translate(offset); } else { KIS_ASSERT_RECOVER_NOOP(0 && "unknown transform mode"); } } bool ToolTransformArgs::isIdentity() const { if (m_mode == FREE_TRANSFORM) { return (m_transformedCenter == m_originalCenter && m_scaleX == 1 && m_scaleY == 1 && m_shearX == 0 && m_shearY == 0 && m_aX == 0 && m_aY == 0 && m_aZ == 0); } else if (m_mode == PERSPECTIVE_4POINT) { return (m_transformedCenter == m_originalCenter && m_scaleX == 1 && m_scaleY == 1 && m_shearX == 0 && m_shearY == 0 && m_flattenedPerspectiveTransform.isIdentity()); } else if(m_mode == WARP || m_mode == CAGE) { for (int i = 0; i < m_origPoints.size(); ++i) if (m_origPoints[i] != m_transfPoints[i]) return false; return true; } else if (m_mode == LIQUIFY) { // Not implemented! return false; } else { KIS_ASSERT_RECOVER_NOOP(0 && "unknown transform mode"); return true; } } void ToolTransformArgs::initLiquifyTransformMode(const QRect &srcRect) { m_liquifyWorker.reset(new KisLiquifyTransformWorker(srcRect, 0, 8)); m_liquifyProperties->loadAndResetMode(); } void ToolTransformArgs::saveLiquifyTransformMode() const { m_liquifyProperties->saveMode(); } void ToolTransformArgs::toXML(QDomElement *e) const { e->setAttribute("mode", (int) m_mode); QDomDocument doc = e->ownerDocument(); if (m_mode == FREE_TRANSFORM || m_mode == PERSPECTIVE_4POINT) { QDomElement freeEl = doc.createElement("free_transform"); e->appendChild(freeEl); KisDomUtils::saveValue(&freeEl, "transformedCenter", m_transformedCenter); KisDomUtils::saveValue(&freeEl, "originalCenter", m_originalCenter); KisDomUtils::saveValue(&freeEl, "rotationCenterOffset", m_rotationCenterOffset); KisDomUtils::saveValue(&freeEl, "transformAroundRotationCenter", m_transformAroundRotationCenter); KisDomUtils::saveValue(&freeEl, "aX", m_aX); KisDomUtils::saveValue(&freeEl, "aY", m_aY); KisDomUtils::saveValue(&freeEl, "aZ", m_aZ); KisDomUtils::saveValue(&freeEl, "cameraPos", m_cameraPos); KisDomUtils::saveValue(&freeEl, "scaleX", m_scaleX); KisDomUtils::saveValue(&freeEl, "scaleY", m_scaleY); KisDomUtils::saveValue(&freeEl, "shearX", m_shearX); KisDomUtils::saveValue(&freeEl, "shearY", m_shearY); KisDomUtils::saveValue(&freeEl, "keepAspectRatio", m_keepAspectRatio); KisDomUtils::saveValue(&freeEl, "flattenedPerspectiveTransform", m_flattenedPerspectiveTransform); KisDomUtils::saveValue(&freeEl, "filterId", m_filter->id()); } else if (m_mode == WARP || m_mode == CAGE) { QDomElement warpEl = doc.createElement("warp_transform"); e->appendChild(warpEl); KisDomUtils::saveValue(&warpEl, "defaultPoints", m_defaultPoints); KisDomUtils::saveValue(&warpEl, "originalPoints", m_origPoints); KisDomUtils::saveValue(&warpEl, "transformedPoints", m_transfPoints); KisDomUtils::saveValue(&warpEl, "warpType", (int)m_warpType); // limited! KisDomUtils::saveValue(&warpEl, "alpha", m_alpha); } else if (m_mode == LIQUIFY) { QDomElement liqEl = doc.createElement("liquify_transform"); e->appendChild(liqEl); m_liquifyProperties->toXML(&liqEl); m_liquifyWorker->toXML(&liqEl); } else { KIS_ASSERT_RECOVER_RETURN(0 && "Unknown transform mode"); } // m_editTransformPoints should not be saved since it is reset explicitly } ToolTransformArgs ToolTransformArgs::fromXML(const QDomElement &e) { ToolTransformArgs args; int newMode = e.attribute("mode", "0").toInt(); if (newMode < 0 || newMode >= N_MODES) return ToolTransformArgs(); args.m_mode = (TransformMode) newMode; // reset explicitly args.m_editTransformPoints = false; bool result = false; if (args.m_mode == FREE_TRANSFORM || args.m_mode == PERSPECTIVE_4POINT) { QDomElement freeEl; QString filterId; result = KisDomUtils::findOnlyElement(e, "free_transform", &freeEl) && KisDomUtils::loadValue(freeEl, "transformedCenter", &args.m_transformedCenter) && KisDomUtils::loadValue(freeEl, "originalCenter", &args.m_originalCenter) && KisDomUtils::loadValue(freeEl, "rotationCenterOffset", &args.m_rotationCenterOffset) && KisDomUtils::loadValue(freeEl, "transformAroundRotationCenter", &args.m_transformAroundRotationCenter) && KisDomUtils::loadValue(freeEl, "aX", &args.m_aX) && KisDomUtils::loadValue(freeEl, "aY", &args.m_aY) && KisDomUtils::loadValue(freeEl, "aZ", &args.m_aZ) && KisDomUtils::loadValue(freeEl, "cameraPos", &args.m_cameraPos) && KisDomUtils::loadValue(freeEl, "scaleX", &args.m_scaleX) && KisDomUtils::loadValue(freeEl, "scaleY", &args.m_scaleY) && KisDomUtils::loadValue(freeEl, "shearX", &args.m_shearX) && KisDomUtils::loadValue(freeEl, "shearY", &args.m_shearY) && KisDomUtils::loadValue(freeEl, "keepAspectRatio", &args.m_keepAspectRatio) && KisDomUtils::loadValue(freeEl, "flattenedPerspectiveTransform", &args.m_flattenedPerspectiveTransform) && KisDomUtils::loadValue(freeEl, "filterId", &filterId); if (result) { args.m_filter = KisFilterStrategyRegistry::instance()->value(filterId); result = (bool) args.m_filter; } } else if (args.m_mode == WARP || args.m_mode == CAGE) { QDomElement warpEl; int warpType = 0; result = KisDomUtils::findOnlyElement(e, "warp_transform", &warpEl) && KisDomUtils::loadValue(warpEl, "defaultPoints", &args.m_defaultPoints) && KisDomUtils::loadValue(warpEl, "originalPoints", &args.m_origPoints) && KisDomUtils::loadValue(warpEl, "transformedPoints", &args.m_transfPoints) && KisDomUtils::loadValue(warpEl, "warpType", &warpType) && KisDomUtils::loadValue(warpEl, "alpha", &args.m_alpha); if (result && warpType >= 0 && warpType < KisWarpTransformWorker::N_MODES) { args.m_warpType = (KisWarpTransformWorker::WarpType_) warpType; } else { result = false; } } else if (args.m_mode == LIQUIFY) { QDomElement liquifyEl; result = KisDomUtils::findOnlyElement(e, "liquify_transform", &liquifyEl); *args.m_liquifyProperties = KisLiquifyProperties::fromXML(e); args.m_liquifyWorker.reset(KisLiquifyTransformWorker::fromXML(e)); } else { KIS_ASSERT_RECOVER_NOOP(0 && "Unknown transform mode"); } if (!result) { args = ToolTransformArgs(); } return args; } void ToolTransformArgs::saveContinuedState() { m_continuedTransformation.reset(); m_continuedTransformation.reset(new ToolTransformArgs(*this)); } void ToolTransformArgs::restoreContinuedState() { QScopedPointer tempTransformation( new ToolTransformArgs(*m_continuedTransformation)); *this = *tempTransformation; m_continuedTransformation.swap(tempTransformation); } const ToolTransformArgs* ToolTransformArgs::continuedTransform() const { return m_continuedTransformation.data(); } diff --git a/plugins/tools/tool_transform2/tool_transform_args.h b/plugins/tools/tool_transform2/tool_transform_args.h index b04e2ec363..073e15f8eb 100644 --- a/plugins/tools/tool_transform2/tool_transform_args.h +++ b/plugins/tools/tool_transform2/tool_transform_args.h @@ -1,333 +1,335 @@ /* * tool_transform_args.h - part of Krita * * Copyright (c) 2010 Marc Pegon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef TOOL_TRANSFORM_ARGS_H_ #define TOOL_TRANSFORM_ARGS_H_ #include #include #include #include #include "kis_liquify_properties.h" #include "kritatooltransform_export.h" #include "kis_global.h" - +#include "KisToolChangesTrackerData.h" #include class KisLiquifyTransformWorker; class QDomElement; /** * Class used to store the parameters of a transformation. * Some parameters are specific to free transform mode, and * others to warp mode : maybe add a union to save a little more * memory. */ -class KRITATOOLTRANSFORM_EXPORT ToolTransformArgs +class KRITATOOLTRANSFORM_EXPORT ToolTransformArgs : public KisToolChangesTrackerData { public: enum TransformMode {FREE_TRANSFORM = 0, WARP, CAGE, LIQUIFY, PERSPECTIVE_4POINT, N_MODES}; /** * Initializes the parameters for an identity transformation, * with mode set to free transform. */ ToolTransformArgs(); /** * The object return will be a copy of args. */ ToolTransformArgs(const ToolTransformArgs& args); + KisToolChangesTrackerData *clone() const; + /** * If mode is warp, original and transformed vector points will be of size 0. * Use setPoints method to set those vectors. */ ToolTransformArgs(TransformMode mode, QPointF transformedCenter, QPointF originalCenter, QPointF rotationCenterOffset, bool transformAroundRotationCenter, double aX, double aY, double aZ, double scaleX, double scaleY, double shearX, double shearY, KisWarpTransformWorker::WarpType warpType, double alpha, bool defaultPoints, const QString &filterId); ~ToolTransformArgs(); ToolTransformArgs& operator=(const ToolTransformArgs& args); bool operator==(const ToolTransformArgs& other) const; bool isSameMode(const ToolTransformArgs& other) const; inline TransformMode mode() const { return m_mode; } inline void setMode(TransformMode mode) { m_mode = mode; } //warp-related inline int numPoints() const { KIS_ASSERT_RECOVER_NOOP(m_origPoints.size() == m_transfPoints.size()); return m_origPoints.size(); } inline QPointF &origPoint(int i) { return m_origPoints[i]; } inline QPointF &transfPoint(int i) { return m_transfPoints[i]; } inline const QVector &origPoints() const { return m_origPoints; } inline const QVector &transfPoints() const { return m_transfPoints; } inline QVector &refOriginalPoints() { return m_origPoints; } inline QVector &refTransformedPoints() { return m_transfPoints; } inline KisWarpTransformWorker::WarpType warpType() const { return m_warpType; } inline double alpha() const { return m_alpha; } inline bool defaultPoints() const { return m_defaultPoints; } inline void setPoints(QVector origPoints, QVector transfPoints) { m_origPoints = QVector(origPoints); m_transfPoints = QVector(transfPoints); } inline void setWarpType(KisWarpTransformWorker::WarpType warpType) { m_warpType = warpType; } inline void setWarpCalculation(KisWarpTransformWorker::WarpCalculation warpCalc) { m_warpCalculation = warpCalc; } inline KisWarpTransformWorker::WarpCalculation warpCalculation() { return m_warpCalculation; } inline void setAlpha(double alpha) { m_alpha = alpha; } inline void setDefaultPoints(bool defaultPoints) { m_defaultPoints = defaultPoints; } //"free transform"-related inline QPointF transformedCenter() const { return m_transformedCenter; } inline QPointF originalCenter() const { return m_originalCenter; } inline QPointF rotationCenterOffset() const { return m_rotationCenterOffset; } inline bool transformAroundRotationCenter() const { return m_transformAroundRotationCenter; } inline double aX() const { return m_aX; } inline double aY() const { return m_aY; } inline double aZ() const { return m_aZ; } inline QVector3D cameraPos() const { return m_cameraPos; } inline double scaleX() const { return m_scaleX; } inline double scaleY() const { return m_scaleY; } inline bool keepAspectRatio() const { return m_keepAspectRatio; } inline double shearX() const { return m_shearX; } inline double shearY() const { return m_shearY; } inline void setTransformedCenter(QPointF transformedCenter) { m_transformedCenter = transformedCenter; } inline void setOriginalCenter(QPointF originalCenter) { m_originalCenter = originalCenter; } inline void setRotationCenterOffset(QPointF rotationCenterOffset) { m_rotationCenterOffset = rotationCenterOffset; } void setTransformAroundRotationCenter(bool value); inline void setAX(double aX) { KIS_ASSERT_RECOVER_NOOP(aX == normalizeAngle(aX)); m_aX = aX; } inline void setAY(double aY) { KIS_ASSERT_RECOVER_NOOP(aY == normalizeAngle(aY)); m_aY = aY; } inline void setAZ(double aZ) { KIS_ASSERT_RECOVER_NOOP(aZ == normalizeAngle(aZ)); m_aZ = aZ; } inline void setCameraPos(const QVector3D &pos) { m_cameraPos = pos; } inline void setScaleX(double scaleX) { m_scaleX = scaleX; } inline void setScaleY(double scaleY) { m_scaleY = scaleY; } inline void setKeepAspectRatio(bool value) { m_keepAspectRatio = value; } inline void setShearX(double shearX) { m_shearX = shearX; } inline void setShearY(double shearY) { m_shearY = shearY; } inline QString filterId() const { return m_filter->id(); } void setFilterId(const QString &id); inline KisFilterStrategy* filter() const { return m_filter; } bool isIdentity() const; inline QTransform flattenedPerspectiveTransform() const { return m_flattenedPerspectiveTransform; } inline void setFlattenedPerspectiveTransform(const QTransform &value) { m_flattenedPerspectiveTransform = value; } bool isEditingTransformPoints() const { return m_editTransformPoints; } void setEditingTransformPoints(bool value) { m_editTransformPoints = value; } const KisLiquifyProperties* liquifyProperties() const { return m_liquifyProperties.data(); } KisLiquifyProperties* liquifyProperties() { return m_liquifyProperties.data(); } void initLiquifyTransformMode(const QRect &srcRect); void saveLiquifyTransformMode() const; KisLiquifyTransformWorker* liquifyWorker() const { return m_liquifyWorker.data(); } void toXML(QDomElement *e) const; static ToolTransformArgs fromXML(const QDomElement &e); void translate(const QPointF &offset); void saveContinuedState(); void restoreContinuedState(); const ToolTransformArgs* continuedTransform() const; private: void clear(); void init(const ToolTransformArgs& args); TransformMode m_mode; // warp-related arguments // these are basically the arguments taken by the warp transform worker bool m_defaultPoints; // true : the original points are set to make a grid // which density is given by numPoints() QVector m_origPoints; QVector m_transfPoints; KisWarpTransformWorker::WarpType m_warpType; KisWarpTransformWorker::WarpCalculation m_warpCalculation; // DRAW or GRID double m_alpha; //'free transform'-related // basically the arguments taken by the transform worker QPointF m_transformedCenter; QPointF m_originalCenter; QPointF m_rotationCenterOffset; // the position of the rotation center relative to // the original top left corner of the selection // before any transformation bool m_transformAroundRotationCenter; // In freehand mode makes the scaling and other transformations // be anchored to the rotation center point. double m_aX; double m_aY; double m_aZ; QVector3D m_cameraPos; double m_scaleX; double m_scaleY; double m_shearX; double m_shearY; bool m_keepAspectRatio; // perspective trasform related QTransform m_flattenedPerspectiveTransform; KisFilterStrategy *m_filter; bool m_editTransformPoints; QSharedPointer m_liquifyProperties; QScopedPointer m_liquifyWorker; /** * When we continue a transformation, m_continuedTransformation * stores the initial step of our transform. All cancel and revert * operations should revert to it. */ QScopedPointer m_continuedTransformation; }; #endif // TOOL_TRANSFORM_ARGS_H_ diff --git a/plugins/tools/tool_transform2/tool_transform_changes_tracker.cpp b/plugins/tools/tool_transform2/tool_transform_changes_tracker.cpp deleted file mode 100644 index 68a8195002..0000000000 --- a/plugins/tools/tool_transform2/tool_transform_changes_tracker.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2013 Dmitry Kazakov - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "tool_transform_changes_tracker.h" - - -TransformChangesTracker::TransformChangesTracker(TransformTransactionProperties *transaction) - : m_transaction(transaction) -{ -} - -void TransformChangesTracker::commitConfig(const ToolTransformArgs &config) -{ - m_config.append(config); -} - -void TransformChangesTracker::requestUndo() -{ - if (m_config.size() > 1) { - m_config.removeLast(); - *m_transaction->currentConfig() = m_config.last(); - emit sigConfigChanged(); - } -} - -void TransformChangesTracker::reset() -{ - m_config.clear(); -} - -bool TransformChangesTracker::isEmpty() const -{ - return m_config.isEmpty(); -} diff --git a/plugins/tools/tool_transform2/tool_transform_changes_tracker.h b/plugins/tools/tool_transform2/tool_transform_changes_tracker.h deleted file mode 100644 index c5bf20a147..0000000000 --- a/plugins/tools/tool_transform2/tool_transform_changes_tracker.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2013 Dmitry Kazakov - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef __TOOL_TRANSFORM_CHANGES_TRACKER_H -#define __TOOL_TRANSFORM_CHANGES_TRACKER_H - -#include - -#include "tool_transform_args.h" -#include "transform_transaction_properties.h" - - -/** - * This class is supposed to support undo operations for the Transform - * Tool. Note, that the transform tool is not going to support redo() - * operations, so we do not use QUndoStack here to not complicate the - * structure of classes. - */ -class TransformChangesTracker : public QObject -{ - Q_OBJECT -public: - TransformChangesTracker(TransformTransactionProperties *transaction); - - void commitConfig(const ToolTransformArgs &config); - void requestUndo(); - void reset(); - - bool isEmpty() const; - -Q_SIGNALS: - void sigConfigChanged(); - -private: - QList m_config; - TransformTransactionProperties *m_transaction; -}; - -#endif /* __TOOL_TRANSFORM_CHANGES_TRACKER_H */