diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt index a9a9400e71..7484068add 100644 --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -1,543 +1,545 @@ # Disable -Wswitch because of the extra definitions we here: # kis_input_manager.cpp: In member function ‘virtual bool KisInputManager::eventFilter(QObject*, QEvent*)’: # warning: case value ‘1001’ not in enumerated type ‘QEvent::Type’ [-Wswitch] # warning: case value ‘1002’ not in enumerated type ‘QEvent::Type’ [-Wswitch] if (CMAKE_COMPILER_IS_GNUCXX) add_definitions(${KDE4_ENABLE_EXCEPTIONS} -Wno-switch) endif () include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile ${EXIV2_INCLUDE_DIR} ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ${OCIO_INCLUDE_DIR} ${Boost_INCLUDE_DIRS} ) add_subdirectory( tests ) if (APPLE) find_library(FOUNDATION_LIBRARY Foundation) 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 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_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 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 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_safe_document_loader.cpp kis_splash_screen.cpp kis_filter_manager.cc kis_filters_model.cc kis_histogram_view.cc 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 kis_painting_assistants_manager.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 kis_resource_server_provider.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 kis_view_plugin.cpp kis_canvas_controls_manager.cpp kis_tooltip_manager.cpp kis_multinode_property.cpp kis_async_action_feedback.cpp kisexiv2/kis_exif_io.cpp kisexiv2/kis_exiv2.cpp kisexiv2/kis_iptc_io.cpp kisexiv2/kis_xmp_io.cpp kra/kis_kra_utils.cpp kra/kis_kra_load_visitor.cpp kra/kis_kra_loader.cpp kra/kis_kra_save_visitor.cpp kra/kis_kra_saver.cpp kra/kis_kra_savexml_visitor.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 kis_fps_decoration.cpp ora/kis_open_raster_stack_load_visitor.cpp ora/kis_open_raster_stack_save_visitor.cpp ora/ora_load_context.cc ora/ora_save_context.cc recorder/kis_node_query_path_editor.cc recorder/kis_recorded_action_creator.cc recorder/kis_recorded_action_creator_factory.cc recorder/kis_recorded_action_creator_factory_registry.cc recorder/kis_recorded_action_editor_factory.cc recorder/kis_recorded_action_editor_factory_registry.cc recorder/kis_recorded_filter_action_editor.cc recorder/kis_recorded_filter_action_creator.cpp recorder/kis_recorded_paint_action_editor.cc 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_recording_adapter.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/strokes/freehand_stroke.cpp tool/strokes/kis_painter_based_stroke_strategy.cpp tool/strokes/kis_filter_stroke_strategy.cpp tool/strokes/kis_color_picker_stroke_strategy.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_gradient_slider_widget.cc widgets/kis_gradient_slider.cpp 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_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/kis_size_group.cpp widgets/kis_size_group_p.cpp widgets/kis_wdg_generator.cpp widgets/kis_workspace_chooser.cpp widgets/squeezedcombobox.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 + 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_single_action_shortcut.cpp input/kis_stroke_shortcut.cpp input/kis_shortcut_matcher.cpp input/kis_select_layer_action.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 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 KisNodeDelegate.cpp kis_node_view_visibility_delegate.cpp KisNodeToolTip.cpp KisNodeView.cpp kis_node_view_color_scheme.cpp KisFilterChain.cpp KisFilterChainLink.cpp KisFilterChainLinkList.cpp KisImportExportFilter.cpp KisFilterEdge.cpp KisFilterEntry.cpp KisFilterGraph.cpp KisImportExportManager.cpp KisFilterVertex.cpp KisMainWindow.cpp KisOpenPane.cpp KisPart.cpp KisPrintJob.cpp KisTemplate.cpp KisTemplateCreateDia.cpp KisTemplateGroup.cpp KisTemplates.cpp KisTemplatesPane.cpp KisTemplateTree.cpp KisUndoStackAction.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 KisColorsetChooser.cpp KisSaveGroupVisitor.cpp ) if(WIN32) if (NOT Qt5Gui_PRIVATE_INCLUDE_DIRS) message(FATAL_ERROR "Qt5Gui Private header are missing!") endif() 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 ) include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS}) endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} kis_animation_frame_cache.cpp kis_animation_cache_populator.cpp canvas/kis_animation_player.cpp kis_animation_exporter.cpp kis_animation_importer.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/kis_tablet_support_x11.cpp input/wintab/qxcbconnection_xi2.cpp input/wintab/qxcbconnection.cpp input/wintab/kis_xi2_event_filter.cpp ) endif() endif() if(WIN32) #ki18n_wrap_ui( # input/wintab/kis_screen_size_choice_dialog.ui #) endif() ki18n_wrap_ui(kritaui_LIB_SRCS forms/wdgdlgpngimport.ui forms/wdgfullscreensettings.ui forms/wdgautogradient.ui forms/wdggeneralsettings.ui forms/wdgperformancesettings.ui forms/wdggenerators.ui forms/wdgcustompalette.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/wdgpaintactioneditor.ui forms/wdgmultipliersdoublesliderspinbox.ui forms/wdgnodequerypatheditor.ui forms/wdgpresetselectorstrip.ui forms/wdgdlgblacklistcleanup.ui forms/wdgrectangleconstraints.ui forms/wdgimportimagesequence.ui forms/KisDetailsPaneBase.ui forms/KisOpenPaneBase.ui brushhud/kis_dlg_brush_hud_config.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 ) 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 kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} ${EXIV2_LIBRARIES} ) 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}) 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_link_libraries(kritaui LINK_INTERFACE_LIBRARIES kritaimage kritalibbrush kritapigment kritawidgets KF5::Completion KF5::I18n ${GL_INTERFACE_LIBRARIES}) 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/utils/kis_document_aware_spin_box_unit_manager.cpp b/libs/ui/utils/kis_document_aware_spin_box_unit_manager.cpp new file mode 100644 index 0000000000..89cf8696d7 --- /dev/null +++ b/libs/ui/utils/kis_document_aware_spin_box_unit_manager.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2016 Laurent Valentin Jospin + * + * 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_document_aware_spin_box_unit_manager.h" + +#include "KisPart.h" +#include "KisMainWindow.h" +#include "KisView.h" +#include "KisDocument.h" +#include "kis_types.h" +#include "kis_image.h" +#include "kis_image_animation_interface.h" +#include "kis_time_range.h" + + +void KisDocumentAwareSpinBoxUnitManager::setDocumentAwarnessToExistingUnitSpinBox(KisDoubleParseUnitSpinBox* spinBox) +{ + KisDocumentAwareSpinBoxUnitManager* manager = new KisDocumentAwareSpinBoxUnitManager(spinBox); + spinBox->setUnitManager(manager); +} + +KisDoubleParseUnitSpinBox* KisDocumentAwareSpinBoxUnitManager::createUnitSpinBoxWithDocumentAwarness(QWidget* parent) +{ + KisDoubleParseUnitSpinBox* spinBox = new KisDoubleParseUnitSpinBox(parent); + setDocumentAwarnessToExistingUnitSpinBox(spinBox); + + return spinBox; +} + +KisDocumentAwareSpinBoxUnitManager::KisDocumentAwareSpinBoxUnitManager(QObject *parent, int pPixDir): + KisSpinBoxUnitManager(parent) +{ + if (pPixDir == PIX_DIR_Y) { + pixDir = PIX_DIR_Y; + } else { + pixDir = PIX_DIR_X; + } + + grantDocumentRelativeUnits(); //the purpose of this class is to manage document relative units. +} + + +qreal KisDocumentAwareSpinBoxUnitManager::getConversionFactor(UnitDimension dim, QString symbol) const +{ + qreal factor = KisSpinBoxUnitManager::getConversionFactor(dim, symbol); + + if (factor > 0) { + //no errors occured at a lower level, so the conversion factor has been get. + return factor; + } + + factor = 1; //fall back to something natural in case document is unreachable (1 px = 1 pt = 1vw = 1vh). So a virtual document of 100x100 with a resolution of 1. + + KisView* view = KisPart::instance()->currentMainwindow()->activeView(); + + if (view == nullptr) { + return factor; + } + + KisDocument* doc = view->document(); + + if (doc == nullptr) { + return factor; + } + + KisImage* img = doc->image().data(); + + if (img == nullptr) { + return factor; + } + + qreal resX = img->xRes(); + qreal resY = img->yRes(); + qreal sizeX = img->width(); + qreal sizeY = img->height(); + + switch (dim) { + + case LENGTH: + if (symbol == "px") { + + if (pixDir == PIX_DIR_X) { + factor = resX; + } else { + factor = resY; + } + } else if (symbol == "vw") { + qreal docWidth = sizeX/resX; + + factor = 100.0/docWidth; //1 vw is 1% of document width, 1 vw in point is docWidth/100 so 1 point in vw is the inverse. + } else if (symbol == "vh") { + qreal docHeight = sizeY/resY; + + factor = 100.0/docHeight; + } + break; + + case TIME: + { + KisImage* img = KisPart::instance()->currentMainwindow()->activeView()->document()->image().data(); + if (symbol == "s") { + qreal fps = img->animationInterface()->framerate(); + + factor = 1/fps; + } else if (symbol == "%") { + const KisTimeRange & time_range = img->animationInterface()->fullClipRange(); + qreal n_frame = time_range.end() - time_range.start(); + + factor = 100/n_frame; + } + } + break; + + default: + break; + + } + + return factor; +} + +qreal KisDocumentAwareSpinBoxUnitManager::getConversionConstant(UnitDimension dim, QString symbol) const +{ + if (dim == TIME && symbol == "%") { + KisImage* img = KisPart::instance()->currentMainwindow()->activeView()->document()->image().data(); + const KisTimeRange & time_range = img->animationInterface()->fullClipRange(); + qreal n_frame = time_range.end() - time_range.start(); + + return -time_range.start()*100.0/n_frame; + } + + return KisSpinBoxUnitManager::getConversionConstant(dim, symbol); +} diff --git a/libs/ui/utils/kis_document_aware_spin_box_unit_manager.h b/libs/ui/utils/kis_document_aware_spin_box_unit_manager.h new file mode 100644 index 0000000000..05194c5a7c --- /dev/null +++ b/libs/ui/utils/kis_document_aware_spin_box_unit_manager.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2016 Laurent Valentin Jospin + * + * 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 KISDOCUMENTAWARESPINBOXUNITMANAGER_H +#define KISDOCUMENTAWARESPINBOXUNITMANAGER_H + +#include "kis_spin_box_unit_manager.h" +#include "kis_double_parse_unit_spin_box.h" + +#include "kritaui_export.h" + + +class KRITAUI_EXPORT KisDocumentAwareSpinBoxUnitManager : public KisSpinBoxUnitManager +{ + Q_OBJECT + +public: + + enum PixDir { + PIX_DIR_X, + PIX_DIR_Y + }; //in case the image has not the same x and y resolution, indicate on which direction get the resolution. + + //! \brief configure a KisDocumentAwareSpinBoxUnitManager for the given spinbox (make the manager a child of the spinbox and attach it to the spinbox). + static void setDocumentAwarnessToExistingUnitSpinBox(KisDoubleParseUnitSpinBox* spinBox); + + //! \brief create a unitSpinBox that is already document aware. + static KisDoubleParseUnitSpinBox* createUnitSpinBoxWithDocumentAwarness(QWidget* parent = 0); + + KisDocumentAwareSpinBoxUnitManager(QObject *parent = 0, int pPixDir = PIX_DIR_X); + + virtual qreal getConversionFactor(UnitDimension dim, QString symbol) const; + virtual qreal getConversionConstant(UnitDimension dim, QString symbol) const; + +private: + + PixDir pixDir; +}; + +#endif // KISDOCUMENTAWARESPINBOXUNITMANAGER_H diff --git a/libs/widgets/KoShadowConfigWidget.cpp b/libs/widgets/KoShadowConfigWidget.cpp index 4807d08105..c75a8fb1fa 100644 --- a/libs/widgets/KoShadowConfigWidget.cpp +++ b/libs/widgets/KoShadowConfigWidget.cpp @@ -1,231 +1,241 @@ /* This file is part of the KDE project * Copyright (C) 2012 C. Boemann * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShadowConfigWidget.h" #include "ui_KoShadowConfigWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include class Q_DECL_HIDDEN KoShadowConfigWidget::Private { public: Private() { } Ui_KoShadowConfigWidget widget; KoColorPopupAction *actionShadowColor; KoCanvasBase *canvas; }; KoShadowConfigWidget::KoShadowConfigWidget(QWidget *parent) : QWidget(parent) , d(new Private()) { d->widget.setupUi(this); d->widget.shadowOffset->setValue(8.0); d->widget.shadowBlur->setValue(8.0); d->widget.shadowBlur->setMinimum(0.0); d->widget.shadowAngle->setValue(315.0); d->widget.shadowAngle->setMinimum(0.0); d->widget.shadowAngle->setMaximum(360.0); d->widget.shadowVisible->setChecked(false); visibilityChanged(); d->actionShadowColor = new KoColorPopupAction(this); d->actionShadowColor->setCurrentColor(QColor(0, 0, 0, 192)); // some reasonable default for shadow d->actionShadowColor->setIcon(koIcon("format-stroke-color")); d->actionShadowColor->setToolTip(i18n("Change the color of the shadow")); d->widget.shadowColor->setDefaultAction(d->actionShadowColor); connect(d->widget.shadowVisible, SIGNAL(toggled(bool)), this, SLOT(applyChanges())); connect(d->widget.shadowVisible, SIGNAL(toggled(bool)), this, SLOT(visibilityChanged())); connect(d->actionShadowColor, SIGNAL(colorChanged(const KoColor&)), this, SLOT(applyChanges())); connect(d->widget.shadowAngle, SIGNAL(valueChanged(int)), this, SLOT(applyChanges())); connect(d->widget.shadowOffset, SIGNAL(valueChangedPt(qreal)), this, SLOT(applyChanges())); connect(d->widget.shadowBlur, SIGNAL(valueChangedPt(qreal)), this, SLOT(applyChanges())); } KoShadowConfigWidget::~KoShadowConfigWidget() { delete d; } void KoShadowConfigWidget::setShadowColor(const QColor &color) { d->widget.shadowColor->blockSignals(true); d->actionShadowColor->blockSignals(true); d->actionShadowColor->setCurrentColor( color ); d->actionShadowColor->blockSignals(false); d->widget.shadowColor->blockSignals(false); } QColor KoShadowConfigWidget::shadowColor() const { return d->actionShadowColor->currentColor(); } void KoShadowConfigWidget::setShadowOffset(const QPointF &offset) { qreal length = sqrt(offset.x()*offset.x() + offset.y()*offset.y()); qreal angle = atan2(-offset.y(), offset.x()); if (angle < 0.0) { angle += 2*M_PI; } d->widget.shadowAngle->blockSignals(true); d->widget.shadowAngle->setValue(-90 - angle * 180.0 / M_PI); d->widget.shadowAngle->blockSignals(false); d->widget.shadowOffset->blockSignals(true); d->widget.shadowOffset->changeValue(length); d->widget.shadowOffset->blockSignals(false); } QPointF KoShadowConfigWidget::shadowOffset() const { QPointF offset(d->widget.shadowOffset->value(), 0); QTransform m; m.rotate(d->widget.shadowAngle->value() + 90); return m.map(offset); } void KoShadowConfigWidget::setShadowBlur(const qreal &blur) { d->widget.shadowBlur->blockSignals(true); d->widget.shadowBlur->changeValue(blur); d->widget.shadowBlur->blockSignals(false); } qreal KoShadowConfigWidget::shadowBlur() const { return d->widget.shadowBlur->value(); } void KoShadowConfigWidget::setShadowVisible(bool visible) { d->widget.shadowVisible->blockSignals(true); d->widget.shadowVisible->setChecked(visible); d->widget.shadowVisible->blockSignals(false); visibilityChanged(); } bool KoShadowConfigWidget::shadowVisible() const { return d->widget.shadowVisible->isChecked(); } void KoShadowConfigWidget::visibilityChanged() { d->widget.shadowAngle->setEnabled( d->widget.shadowVisible->isChecked() ); d->widget.shadowBlur->setEnabled( d->widget.shadowVisible->isChecked() ); d->widget.shadowColor->setEnabled( d->widget.shadowVisible->isChecked() ); d->widget.shadowOffset->setEnabled( d->widget.shadowVisible->isChecked() ); } void KoShadowConfigWidget::applyChanges() { if (d->canvas) { KoSelection *selection = d->canvas->shapeManager()->selection(); KoShape * shape = selection->firstSelectedShape(KoFlake::TopLevelSelection); if (! shape) { return; } KoShapeShadow *newShadow = new KoShapeShadow(); newShadow->setVisible(shadowVisible()); newShadow->setColor(shadowColor()); newShadow->setOffset(shadowOffset()); newShadow->setBlur(shadowBlur()); d->canvas->addCommand(new KoShapeShadowCommand(selection->selectedShapes(KoFlake::TopLevelSelection), newShadow)); } } void KoShadowConfigWidget::selectionChanged() { if (! d->canvas) { return; } KoSelection *selection = d->canvas->shapeManager()->selection(); KoShape * shape = selection->firstSelectedShape(KoFlake::TopLevelSelection); setEnabled(shape != 0); if (! shape) { setShadowVisible(false); return; } KoShapeShadow * shadow = shape->shadow(); if (! shadow) { setShadowVisible(false); return; } setShadowVisible(shadow->isVisible()); setShadowOffset(shadow->offset()); setShadowColor(shadow->color()); setShadowBlur(shadow->blur()); } void KoShadowConfigWidget::setCanvas(KoCanvasBase *canvas) { d->canvas = canvas; connect(canvas->shapeManager(), SIGNAL(selectionChanged()), this, SLOT(selectionChanged())); connect(canvas->shapeManager(), SIGNAL(selectionContentChanged()), this, SLOT(selectionChanged())); setUnit(canvas->unit()); connect( d->canvas->resourceManager(), SIGNAL( canvasResourceChanged( int, const QVariant& ) ), this, SLOT( resourceChanged( int, const QVariant& ) ) ); } +void KoShadowConfigWidget::setUnitManager(KisSpinBoxUnitManager* managerBlur, KisSpinBoxUnitManager *managerOffset) +{ + d->widget.shadowOffset->blockSignals(true); + d->widget.shadowBlur->blockSignals(true); + d->widget.shadowOffset->setUnitManager(managerOffset); + d->widget.shadowBlur->setUnitManager(managerBlur); + d->widget.shadowOffset->blockSignals(false); + d->widget.shadowBlur->blockSignals(false); +} + void KoShadowConfigWidget::setUnit(const KoUnit &unit) { d->widget.shadowOffset->blockSignals(true); d->widget.shadowBlur->blockSignals(true); d->widget.shadowOffset->setUnit(unit); d->widget.shadowBlur->setUnit(unit); d->widget.shadowOffset->blockSignals(false); d->widget.shadowBlur->blockSignals(false); } void KoShadowConfigWidget::resourceChanged( int key, const QVariant & res ) { if( key == KoCanvasResourceManager::Unit ) { setUnit(res.value()); } } diff --git a/libs/widgets/KoShadowConfigWidget.h b/libs/widgets/KoShadowConfigWidget.h index ba6e7a5aa5..3c11d62858 100644 --- a/libs/widgets/KoShadowConfigWidget.h +++ b/libs/widgets/KoShadowConfigWidget.h @@ -1,77 +1,80 @@ /* This file is part of the KDE project * Copyright (C) 2012 C. Boemann * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSHADOWCONFIGWIDGET_H #define KOSHADOWCONFIGWIDGET_H #include "kritawidgets_export.h" #include class KoUnit; class KoCanvasBase; +class KisSpinBoxUnitManager; /// A widget for configuring the shadow of a shape class KRITAWIDGETS_EXPORT KoShadowConfigWidget : public QWidget { Q_OBJECT public: explicit KoShadowConfigWidget(QWidget *parent); ~KoShadowConfigWidget(); /// Sets the shadow color void setShadowColor(const QColor &color); /// Returns the shadow color QColor shadowColor() const; /// Sets the shadow offset void setShadowOffset(const QPointF &offset); /// Returns the shadow offset QPointF shadowOffset() const; /// Sets the shadow blur radius void setShadowBlur(const qreal &blur); /// Returns the shadow blur radius qreal shadowBlur() const; /// Sets if the shadow is visible void setShadowVisible(bool visible); /// Returns if shadow is visible bool shadowVisible() const; public Q_SLOTS: + + void setUnitManager(KisSpinBoxUnitManager* managerBlur, KisSpinBoxUnitManager* managerOffset); void setUnit( const KoUnit &unit ); void setCanvas(KoCanvasBase *canvas); private Q_SLOTS: void visibilityChanged(); void applyChanges(); void selectionChanged(); void resourceChanged( int key, const QVariant & res ); private: class Private; Private *const d; }; #endif // KOSHADOWCONFIGWIDGET_H diff --git a/libs/widgets/kis_double_parse_unit_spin_box.cpp b/libs/widgets/kis_double_parse_unit_spin_box.cpp index 568d6b5a93..81b84b5e21 100644 --- a/libs/widgets/kis_double_parse_unit_spin_box.cpp +++ b/libs/widgets/kis_double_parse_unit_spin_box.cpp @@ -1,270 +1,332 @@ /* * Copyright (c) 2016 Laurent Valentin Jospin * * 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_double_parse_unit_spin_box.h" #include "kis_spin_box_unit_manager.h" #include class Q_DECL_HIDDEN KisDoubleParseUnitSpinBox::Private { public: Private(double low, double up, double step) : lowerInPoints(low), upperInPoints(up), stepInPoints(step), unit(KoUnit(KoUnit::Point)), - unitManager() + unitManager(new KisSpinBoxUnitManager()), + defaultUnitManager(unitManager), + isDeleting(false) { } double lowerInPoints; ///< lowest value in points double upperInPoints; ///< highest value in points double stepInPoints; ///< step in points KoUnit unit; - KisSpinBoxUnitManager unitManager; //manage more units than permitted by KoUnit. + KisSpinBoxUnitManager* unitManager; //manage more units than permitted by KoUnit. + KisSpinBoxUnitManager* defaultUnitManager; //the default unit manager is the one the spinbox rely on and go back to if a connected unit manager is destroyed before the spinbox. + + bool isDeleting; }; KisDoubleParseUnitSpinBox::KisDoubleParseUnitSpinBox(QWidget *parent) : KisDoubleParseSpinBox(parent), d(new Private(-9999, 9999, 1)) { setUnit( KoUnit(KoUnit::Point) ); setAlignment( Qt::AlignRight ); connect(this, SIGNAL(valueChanged( double )), SLOT(privateValueChanged())); connect(lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(detectUnitChanges()) ); } KisDoubleParseUnitSpinBox::~KisDoubleParseUnitSpinBox() { + d->isDeleting = true; + delete d->defaultUnitManager; delete d; } +void KisDoubleParseUnitSpinBox::setUnitManager(KisSpinBoxUnitManager* unitManager) +{ + qreal oldVal = d->unitManager->getReferenceValue(KisDoubleParseSpinBox::value()); + QString oldSymbol = d->unitManager->getApparentUnitSymbol(); + + if (oldSymbol == unitManager->getApparentUnitSymbol() && + d->unitManager->getUnitDimensionType() == unitManager->getUnitDimensionType()) + { + d->unitManager = unitManager; //set the new unitmanager anyway, since it may be a subclass, so change the behavior anyway. + return; + } + + qreal newVal; + + if (d->unitManager->getUnitDimensionType() != unitManager->getUnitDimensionType()) { + //dimension is the same, calculate the new value + newVal = unitManager->getApparentValue(oldVal); + } else { + newVal = unitManager->getApparentValue(d->lowerInPoints); + } + + double newMin = unitManager->getApparentValue(d->lowerInPoints); + double newMax = unitManager->getApparentValue(d->upperInPoints); + double newStep = unitManager->getApparentValue(d->stepInPoints); + + if (unitManager->getApparentUnitSymbol() == KoUnit(KoUnit::Pixel).symbol()) { + // limit the pixel step by 1.0 + newStep = qMax(qreal(1.0), newStep); + } + + KisDoubleParseSpinBox::setMinimum(newMin); + KisDoubleParseSpinBox::setMaximum(newMax); + KisDoubleParseSpinBox::setSingleStep(newStep); + + if (d->unitManager != d->defaultUnitManager) { + disconnect(d->unitManager, &QObject::destroyed, + this, &KisDoubleParseUnitSpinBox::disconnectExternalUnitManager); //there's no dependance anymore. + } + + d->unitManager = unitManager; + + connect(d->unitManager, &QObject::destroyed, + this, &KisDoubleParseUnitSpinBox::disconnectExternalUnitManager); + + KisDoubleParseSpinBox::setValue(newVal); +} + void KisDoubleParseUnitSpinBox::changeValue( double newValue ) { - if (d->unitManager.getApparentValue(newValue) == KisDoubleParseSpinBox::value()) { + if (d->unitManager->getApparentValue(newValue) == KisDoubleParseSpinBox::value()) { return; } - KisDoubleParseSpinBox::setValue( d->unitManager.getApparentValue(newValue) ); + KisDoubleParseSpinBox::setValue( d->unitManager->getApparentValue(newValue) ); } void KisDoubleParseUnitSpinBox::setUnit( const KoUnit & unit) { - if (d->unitManager.getUnitDimensionType() != KisSpinBoxUnitManager::LENGTH) { - d->unitManager.setUnitDim(KisSpinBoxUnitManager::LENGTH); //setting the unit using a KoUnit mean you want to use a length. + if (d->unitManager->getUnitDimensionType() != KisSpinBoxUnitManager::LENGTH) { + d->unitManager->setUnitDim(KisSpinBoxUnitManager::LENGTH); //setting the unit using a KoUnit mean you want to use a length. } setUnit(unit.symbol()); d->unit = unit; } void KisDoubleParseUnitSpinBox::setUnit(const QString &symbol) { - double oldValue = d->unitManager.getReferenceValue(KisDoubleParseSpinBox::value()); - QString oldSymbol = d->unitManager.getApparentUnitSymbol(); + double oldValue = d->unitManager->getReferenceValue(KisDoubleParseSpinBox::value()); + QString oldSymbol = d->unitManager->getApparentUnitSymbol(); if (symbol == oldSymbol) { return; } - d->unitManager.setApparentUnitFromSymbol(symbol); + d->unitManager->setApparentUnitFromSymbol(symbol); - if (d->unitManager.getApparentUnitSymbol() == oldSymbol) { //the setApparentUnitFromSymbol is a bit clever, for example in regard of Casesensitivity. So better check like this. + if (d->unitManager->getApparentUnitSymbol() == oldSymbol) { //the setApparentUnitFromSymbol is a bit clever, for example in regard of Casesensitivity. So better check like this. return; } - KisDoubleParseSpinBox::setMinimum( d->unitManager.getApparentValue( d->lowerInPoints ) ); - KisDoubleParseSpinBox::setMaximum( d->unitManager.getApparentValue( d->upperInPoints ) ); + KisDoubleParseSpinBox::setMinimum( d->unitManager->getApparentValue( d->lowerInPoints ) ); + KisDoubleParseSpinBox::setMaximum( d->unitManager->getApparentValue( d->upperInPoints ) ); - qreal step = d->unitManager.getApparentValue( d->stepInPoints ); + qreal step = d->unitManager->getApparentValue( d->stepInPoints ); if (symbol == KoUnit(KoUnit::Pixel).symbol()) { // limit the pixel step by 1.0 step = qMax(qreal(1.0), step); } KisDoubleParseSpinBox::setSingleStep( step ); - KisDoubleParseSpinBox::setValue( d->unitManager.getApparentValue( oldValue ) ); + KisDoubleParseSpinBox::setValue( d->unitManager->getApparentValue( oldValue ) ); } void KisDoubleParseUnitSpinBox::setDimensionType(int dim) { if (!KisSpinBoxUnitManager::isUnitId(dim)) { return; } - d->unitManager.setUnitDim((KisSpinBoxUnitManager::UnitDimension) dim); + d->unitManager->setUnitDim((KisSpinBoxUnitManager::UnitDimension) dim); } double KisDoubleParseUnitSpinBox::value( ) const { - return d->unitManager.getReferenceValue( KisDoubleParseSpinBox::value() ); + return d->unitManager->getReferenceValue( KisDoubleParseSpinBox::value() ); } void KisDoubleParseUnitSpinBox::setMinimum(double min) { d->lowerInPoints = min; - KisDoubleParseSpinBox::setMinimum( d->unitManager.getApparentValue( min ) ); + KisDoubleParseSpinBox::setMinimum( d->unitManager->getApparentValue( min ) ); } void KisDoubleParseUnitSpinBox::setMaximum(double max) { d->upperInPoints = max; - KisDoubleParseSpinBox::setMaximum( d->unitManager.getApparentValue( max ) ); + KisDoubleParseSpinBox::setMaximum( d->unitManager->getApparentValue( max ) ); } void KisDoubleParseUnitSpinBox::setLineStep(double step) { - d->stepInPoints = d->unitManager.getReferenceValue(step); + d->stepInPoints = d->unitManager->getReferenceValue(step); KisDoubleParseSpinBox::setSingleStep( step ); } void KisDoubleParseUnitSpinBox::setLineStepPt(double step) { d->stepInPoints = step; - KisDoubleParseSpinBox::setSingleStep( d->unitManager.getApparentValue( step ) ); + KisDoubleParseSpinBox::setSingleStep( d->unitManager->getApparentValue( step ) ); } void KisDoubleParseUnitSpinBox::setMinMaxStep( double min, double max, double step ) { setMinimum( min ); setMaximum( max ); setLineStepPt( step ); } QValidator::State KisDoubleParseUnitSpinBox::validate(QString &input, int &pos) const { Q_UNUSED(pos); QRegExp regexp ("([ a-zA-Z]+)$"); // Letters or spaces at end const int res = input.indexOf( regexp ); if ( res == -1 ) { // Nothing like an unit? The user is probably editing the unit return QValidator::Intermediate; } QString expr ( input.left( res ) ); const QString unitName ( regexp.cap( 1 ).trimmed().toLower() ); bool ok = true; bool interm = false; QValidator::State exprState = KisDoubleParseSpinBox::validate(expr, pos); if (exprState == QValidator::Invalid) { return exprState; } else if (exprState == QValidator::Intermediate) { interm = true; } //check if we can parse the unit. - QStringList listOfSymbol = d->unitManager.getsUnitSymbolList(); + QStringList listOfSymbol = d->unitManager->getsUnitSymbolList(); ok = listOfSymbol.contains(unitName); if (!ok || interm) { return QValidator::Intermediate; } return QValidator::Acceptable; } QString KisDoubleParseUnitSpinBox::textFromValue( double value ) const { QString txt = KisDoubleParseSpinBox::textFromValue(value); - if (!txt.endsWith(d->unitManager.getApparentUnitSymbol())) { - txt += " " + d->unitManager.getApparentUnitSymbol(); + if (!txt.endsWith(d->unitManager->getApparentUnitSymbol())) { + txt += " " + d->unitManager->getApparentUnitSymbol(); } return txt; } QString KisDoubleParseUnitSpinBox::veryCleanText() const { return makeTextClean(cleanText()); } double KisDoubleParseUnitSpinBox::valueFromText( const QString& str ) const { QString txt = makeTextClean(str); return KisDoubleParseSpinBox::valueFromText(txt); //this function will take care of prefix (and don't mind if suffix has been removed. } void KisDoubleParseUnitSpinBox::privateValueChanged() { emit valueChangedPt( value() ); } QString KisDoubleParseUnitSpinBox::detectUnit() { QString str = veryCleanText().trimmed(); //text with the new unit but not the old one. QRegExp regexp ("([ ]*[a-zA-Z]+[ ]*)$"); // Letters or spaces at end int res = str.indexOf( regexp ); if (res > -1) { QString expr ( str.right( str.size() - res ) ); expr = expr.trimmed(); return expr; } return ""; } void KisDoubleParseUnitSpinBox::detectUnitChanges() { QString unitSymb = detectUnit(); if (unitSymb.isEmpty()) { return; } setUnit(unitSymb); setValue(valueFromText(cleanText())); //change value keep the old value, but converted to new unit... which is different from the value the user entered in the new unit. So we need to set the new value. } QString KisDoubleParseUnitSpinBox::makeTextClean(QString const& txt) const { QString expr = txt; - QString symbol = d->unitManager.getApparentUnitSymbol(); + QString symbol = d->unitManager->getApparentUnitSymbol(); if ( expr.endsWith(suffix()) ) { expr.remove(expr.size()-suffix().size(), suffix().size()); } expr = expr.trimmed(); if ( expr.endsWith(symbol) ) { expr.remove(expr.size()-symbol.size(), symbol.size()); } return expr; } + +void KisDoubleParseUnitSpinBox::disconnectExternalUnitManager() +{ + if (!d->isDeleting) + { + setUnitManager(d->defaultUnitManager); //go back to default unit manager. + } +} diff --git a/libs/widgets/kis_double_parse_unit_spin_box.h b/libs/widgets/kis_double_parse_unit_spin_box.h index 9c6b8b3575..091a1996d6 100644 --- a/libs/widgets/kis_double_parse_unit_spin_box.h +++ b/libs/widgets/kis_double_parse_unit_spin_box.h @@ -1,117 +1,122 @@ /* * Copyright (c) 2016 Laurent Valentin Jospin * * 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_DOUBLEPARSEUNITSPINBOX_H #define KIS_DOUBLEPARSEUNITSPINBOX_H #include #include "kis_double_parse_spin_box.h" #include "kritawidgets_export.h" +class KisSpinBoxUnitManager; + /*! * \brief The KisDoubleParseUnitSpinBox class is an evolution of the \see KoUnitDoubleSpinBox, but inherit from \see KisDoubleParseSpinBox to be able to parse math expressions. * * This class store the */ class KRITAWIDGETS_EXPORT KisDoubleParseUnitSpinBox : public KisDoubleParseSpinBox { Q_OBJECT public: KisDoubleParseUnitSpinBox(QWidget* parent = 0); ~KisDoubleParseUnitSpinBox(); + void setUnitManager(KisSpinBoxUnitManager* unitManager); + /** * Set the new value in points which will then be converted to the current unit for display * @param newValue the new value * @see value() */ virtual void changeValue( double newValue ); /** * This spinbox shows the internal value after a conversion to the unit set here. */ virtual void setUnit(const KoUnit &unit); virtual void setUnit(const QString & symbol); /** * @brief setDimensionType set the dimension (for example length or angle) of the units the spinbox manage * @param dim the dimension id. (if not an id in KisSpinBoxUnitManager::UnitDimension, then the function does nothing). */ virtual void setDimensionType(int dim); /// @return the current value, converted in points double value( ) const; /// Set minimum value in points. void setMinimum(double min); /// Set maximum value in points. void setMaximum(double max); /// Set step size in the current unit. void setLineStep(double step); /// Set step size in points. void setLineStepPt(double step); /// Set minimum, maximum value and the step size (all in points) void setMinMaxStep( double min, double max, double step ); /// reimplemented from superclass, will forward to KoUnitDoubleValidator virtual QValidator::State validate(QString &input, int &pos) const; /** * Transform the double in a nice text, using locale symbols * @param value the number as double * @return the resulting string */ virtual QString textFromValue( double value ) const; //! \brief get the text in the spinbox without prefix or suffix, and remove unit symbol if present. virtual QString veryCleanText() const; /** * Transfrom a string into a double, while taking care of locale specific symbols. * @param str the string to transform into a number * @return the value as double */ virtual double valueFromText( const QString& str ) const; Q_SIGNALS: /// emitted like valueChanged in the parent, but this one emits the point value void valueChangedPt( qreal ); private: class Private; Private * const d; QString detectUnit(); QString makeTextClean(QString const& txt) const; private Q_SLOTS: // exists to do emits for valueChangedPt void privateValueChanged(); void detectUnitChanges(); + void disconnectExternalUnitManager(); }; #endif // KIS_DOUBLEPARSEUNITSPINBOX_H diff --git a/libs/widgetutils/kis_spin_box_unit_manager.cpp b/libs/widgetutils/kis_spin_box_unit_manager.cpp index 8896771561..0201f6ada4 100644 --- a/libs/widgetutils/kis_spin_box_unit_manager.cpp +++ b/libs/widgetutils/kis_spin_box_unit_manager.cpp @@ -1,326 +1,421 @@ /* * Copyright (c) 2016 Laurent Valentin Jospin * * 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_spin_box_unit_manager.h" #include "KoUnit.h" #include #include const QStringList KisSpinBoxUnitManager::referenceUnitSymbols = {"pt", "°", "frame"}; +const QStringList KisSpinBoxUnitManager::documentRelativeLengthUnitSymbols = {"px", "vw", "vh"}; //px are relative to the resolution, vw and vh to the width and height. +const QStringList KisSpinBoxUnitManager::documentRelativeTimeUnitSymbols = {"s", "%"}; //secondes are relative to the framerate, % to the sequence length. + class Q_DECL_HIDDEN KisSpinBoxUnitManager::Private { public: Private(KisSpinBoxUnitManager::UnitDimension pDim = KisSpinBoxUnitManager::LENGTH, QString pUnitSymbol = "pt", double pConv = 1.0): dim(pDim), unitSymbol(pUnitSymbol), conversionFactor(pConv), + conversionFactorIsFixed(true), + conversionConstant(0), + conversionConstantIsFixed(true), constrains(0), unitListCached(false), hasHundredPercent(false), - hasWidthAndHeight(false) + canAccessDocument(false) { } KisSpinBoxUnitManager::UnitDimension dim; QString unitSymbol; - double conversionFactor; + mutable double conversionFactor; + bool conversionFactorIsFixed; //tell if it's possible to trust the conversion factor stored or if it's needed to recompute it. + mutable double conversionConstant; + bool conversionConstantIsFixed; //tell if it's possible to trust the conversion constant stored or if it's needed to recompute it. KisSpinBoxUnitManager::Constrains constrains; mutable QStringList unitList; mutable bool unitListCached; mutable QStringList unitListWithName; mutable bool unitListWithNameCached; + //it's possible to store a reference for the % unit, for lenght. bool hasHundredPercent; qreal hundredPercent; - bool hasWidthAndHeight; - qreal width; - qreal height; + + bool canAccessDocument; }; KisSpinBoxUnitManager::KisSpinBoxUnitManager(QObject *parent) : QObject(parent) { d = new Private(); } KisSpinBoxUnitManager::~KisSpinBoxUnitManager() { delete d; } int KisSpinBoxUnitManager::getUnitDimensionType() const { return d->dim; } QString KisSpinBoxUnitManager::getReferenceUnitSymbol() const { return referenceUnitSymbols[d->dim]; } QString KisSpinBoxUnitManager::getApparentUnitSymbol() const { return d->unitSymbol; } int KisSpinBoxUnitManager::getApparentUnitId() const { QStringList list = getsUnitSymbolList(); return list.indexOf(d->unitSymbol); } QStringList KisSpinBoxUnitManager::getsUnitSymbolList(bool withName) const{ QStringList list; //TODO: cache if (withName) { if (d->unitListWithNameCached) { return d->unitListWithName; } } else { if (d->unitListCached) { return d->unitList; } } switch (d->dim) { case LENGTH: for (int i = 0; i < KoUnit::TypeCount; i++) { + + if (KoUnit::Type(i) == KoUnit::Pixel) { + continue; //skip pixel, which is a document relative unit, in the base classe. + } + if (withName) { list << KoUnit::unitDescription(KoUnit::Type(i)); } else { list << KoUnit(KoUnit::Type(i)).symbol(); } } + + if (d->canAccessDocument) { + // ad document relative units + if (withName) { + list << KoUnit::unitDescription(KoUnit::Pixel) << i18n("view width (vw)") << i18n("view height (vh)"); + } else { + list << documentRelativeLengthUnitSymbols; + } + } + break; case ANGLE: if (withName) { list << i18n("degrees (°)") << i18n("radians (rad)") << i18n("gons (gon)") << i18n("percent of circle (%)"); } else { list << "°" << "rad" << "gon" << "%"; } break; case TIME: if (withName) { list << i18n("frames (f)"); } else { list << "f"; } + + if (d->canAccessDocument) { + if (withName) { + list << i18n("seconds (s)") << i18n("percent of animation (%)"); + } else { + list << documentRelativeTimeUnitSymbols; + } + } + break; } if (withName) { d->unitListWithName = list; d->unitListWithNameCached = true; } else { d->unitList = list; d->unitListCached = true; } return list; } +qreal KisSpinBoxUnitManager::getConversionConstant(UnitDimension dim, QString symbol) const +{ + return 0; // all units managed here are transform via a linear function, so this wll alway be 0 in this class. +} + qreal KisSpinBoxUnitManager::getReferenceValue(double apparentValue) const { + if (!d->conversionFactorIsFixed) { + recomputeConversionFactor(); + } - qreal v = apparentValue/d->conversionFactor; + if(!d->conversionConstantIsFixed) { + recomputeConvesrionConstant(); + } + + qreal v = (apparentValue - d->conversionConstant)/d->conversionFactor; if (d->constrains &= REFISINT) { v = qFloor(v); } return v; } qreal KisSpinBoxUnitManager::getApparentValue(double refValue) const { + if (!d->conversionFactorIsFixed) { + recomputeConversionFactor(); + } - qreal v = refValue*d->conversionFactor; + if(!d->conversionConstantIsFixed) { + recomputeConvesrionConstant(); + } + + qreal v = refValue*d->conversionFactor + d->conversionConstant; if (d->constrains &= VALISINT) { v = qFloor(v); } return v; } qreal KisSpinBoxUnitManager::getConversionFactor(UnitDimension dim, QString symbol) const { qreal factor = -1; switch (dim) { case LENGTH: do { + if (symbol == "px") { + break; + } + bool ok; KoUnit unit = KoUnit::fromSymbol(symbol, &ok); if (! ok) { break; } factor = unit.toUserValue(1.0); } while (0) ; break; case ANGLE: if (symbol == "°") { factor = 1.0; break; } if (symbol == "rad") { factor = acos(-1)/90.0; break; } if (symbol == "gon") { factor = 10.0/9.0; break; } if (symbol == "%") { factor = 2.5/9.0; //(25% of circle is 90°) break; } break; case TIME: if (symbol != "f") { //we have only frames for the moment. break; } factor = 1.0; break; } return factor; } void KisSpinBoxUnitManager::setUnitDim(UnitDimension dim) { if (dim == d->dim) { return; } d->dim = dim; d->unitSymbol = referenceUnitSymbols[d->dim]; //Active dim is reference dim when just changed. d->conversionFactor = 1.0; emit unitDimensionChanged(d->dim); } void KisSpinBoxUnitManager::setApparentUnitFromSymbol(QString pSymbol) { QString symbol = pSymbol.trimmed(); if (symbol == d->unitSymbol) { return; } QString newSymb = ""; switch (d->dim) { case ANGLE: if (symbol.toLower() == "deg") { newSymb = "°"; break; } goto default_indentifier; //alway do default after handling possible special cases. default_indentifier: default: QStringList list = getsUnitSymbolList(); if (list.contains(symbol, Qt::CaseInsensitive)) { for (QString str : list) { if (str.toLower() == symbol.toLower()) { newSymb = str; //official symbol may contain capitals letters, so better take the official version. break; } } break; } } if(newSymb.isEmpty()) { return; //abort if it was impossible to locate the correct symbol. } + if (d->canAccessDocument) { + //manage document relative units. + + QStringList speUnits; + + switch (d->dim) { + + case LENGTH: + speUnits = documentRelativeLengthUnitSymbols; + goto default_identifier_conv_fact; + + case TIME: + speUnits = documentRelativeTimeUnitSymbols; + goto default_identifier_conv_fact; + + default_identifier_conv_fact: + default: + + if (speUnits.isEmpty()) { + d->conversionFactorIsFixed = true; + break; + } + + if (speUnits.contains(newSymb)) { + d->conversionFactorIsFixed = false; + break; + } + + d->conversionFactorIsFixed = true; + break; + } + + if (d->dim == TIME) { + if (newSymb == "%") { + d->conversionConstantIsFixed = false; + } + } else { + d->conversionConstantIsFixed = true; + } + + } + qreal conversFact = getConversionFactor(d->dim, newSymb); qreal oldConversFact = d->conversionFactor; d->conversionFactor = conversFact; emit conversionFactorChanged(d->conversionFactor, oldConversFact); d->unitSymbol = newSymb; emit unitChanged(newSymb); } -void KisSpinBoxUnitManager::configureRelativeUnitReference(qreal value) +void KisSpinBoxUnitManager::recomputeConversionFactor() const { - if (d->hasHundredPercent && d->hundredPercent == value) { + if (d->conversionFactorIsFixed) { return; } - if (!d->hasHundredPercent) { //in case we add relative units we need to clear cache for unitlists. - d->unitListCached = false; - d->unitListWithNameCached = false; - emit unitListChanged(); - } + qreal oldConversionFactor = d->conversionFactor; - d->hundredPercent = value; - d->hasHundredPercent = true; + d->conversionFactor = getConversionFactor(d->dim, d->unitSymbol); + + if (oldConversionFactor != d->conversionFactor) { + emit conversionFactorChanged(d->conversionFactor, oldConversionFactor); + } } -void KisSpinBoxUnitManager::configureRelativeUnitWidthAndHeight(qreal width, qreal height) +void KisSpinBoxUnitManager::recomputeConvesrionConstant() const { - if (d->hasWidthAndHeight && d->width == width && d->height == height) { + if (d->conversionConstantIsFixed) { return; } - if (!d->hasWidthAndHeight) { //in case we add relative units we need to clear cache for unitlists. - d->unitListCached = false; - d->unitListWithNameCached = false; - emit unitListChanged(); - } + qreal oldConversionConstant = d->conversionConstant; - d->width = width; - d->height = height; - d->hasWidthAndHeight = true; + d->conversionConstant = getConversionConstant(d->dim, d->unitSymbol); +} +void KisSpinBoxUnitManager::grantDocumentRelativeUnits() +{ + d->canAccessDocument = true; } diff --git a/libs/widgetutils/kis_spin_box_unit_manager.h b/libs/widgetutils/kis_spin_box_unit_manager.h index f707db246e..1fedbdc787 100644 --- a/libs/widgetutils/kis_spin_box_unit_manager.h +++ b/libs/widgetutils/kis_spin_box_unit_manager.h @@ -1,103 +1,114 @@ /* * Copyright (c) 2016 Laurent Valentin Jospin * * 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 KISSPINBOXUNITMANAGER_H #define KISSPINBOXUNITMANAGER_H #include #include #include "kritawidgetutils_export.h" /** * @brief The KisSpinBoxUnitManager class is an abstract interface for the unitspinboxes classes to manage different type of units. * - * The class make a difference between unit dimension (distance, angle, time), unit reference mode (absolute or relative). - * If one want to use relative units a reference must be configured using the proper function. + * The class make a difference between unit dimension (distance, angle, time). * - * The class allow to converte values between reference unit and apparent unit, but also to get other information like possible unit symbols. + * The class allow to converte values between reference unit and apparent unit, but also to get other information like possible units symbols. + * + * This class don't allow to use relative units (units which conversion factor is dependant of the context), even if it's private data are prepared to manage it. + * The reason for this is that from the library of this class it's very hard to acess easily the informations needed. So all will be managed by subclasses in other libs. * */ class KRITAWIDGETUTILS_EXPORT KisSpinBoxUnitManager : public QObject { Q_OBJECT public: enum UnitDimension{ LENGTH = 0, ANGLE = 1, TIME = 2 }; static inline bool isUnitId(int code) { return (code == LENGTH || code == ANGLE || code == TIME); } //! \brief this list hold the symbols of the referenc unit per dimension. The index is equal to the value in UnitDimension so that the dimension name can be used to index the list. static const QStringList referenceUnitSymbols; enum Constrain{ NOCONSTR = 0, REFISINT = 1, VALISINT = 2 }; Q_DECLARE_FLAGS(Constrains, Constrain) explicit KisSpinBoxUnitManager(QObject *parent = 0); ~KisSpinBoxUnitManager(); int getUnitDimensionType() const; QString getReferenceUnitSymbol() const; QString getApparentUnitSymbol() const; //! \brief get the position of the apparent unit in the list of units. It is usefull if we want to build a model for combo-box based unit management. int getApparentUnitId() const; - QStringList getsUnitSymbolList(bool withName = false) const; + virtual QStringList getsUnitSymbolList(bool withName = false) const; qreal getReferenceValue(double apparentValue) const; qreal getApparentValue(double refValue) const; - qreal getConversionFactor(UnitDimension dim, QString symbol) const; + //! \brief gets the conversion factor of a managed unit, or -1 in case of error. This method is the one that need to be overridden to extend the ability of the KisSpinBoxUnitManager. + virtual qreal getConversionFactor(UnitDimension dim, QString symbol) const; + //! \brief some units conversions are done via an affine transform, not just a linear transform. This function gives the constant of this affine transform (usually 0). + virtual qreal getConversionConstant(UnitDimension dim, QString symbol) const; Q_SIGNALS: void unitDimensionChanged(int dimCode); void unitChanged(QString symbol); - void conversionFactorChanged(qreal newConversionFactor, qreal oldConversionFactor); + void conversionFactorChanged(qreal newConversionFactor, qreal oldConversionFactor) const; void unitListChanged(); public Q_SLOTS: void setUnitDim(UnitDimension dim); void setApparentUnitFromSymbol(QString pSymbol); - //! \brief configure the reference length (100%) in reference unit. This activate relative units. - void configureRelativeUnitReference(qreal value); - void configureRelativeUnitWidthAndHeight(qreal width, qreal height); - protected: class Private; Private * d; + //unit's that may be used only if acess to the document informations exists. + static const QStringList documentRelativeLengthUnitSymbols; + static const QStringList documentRelativeTimeUnitSymbols; + + void recomputeConversionFactor() const; + void recomputeConvesrionConstant() const; + + //! \brief calling this method give acess to document relative units. Only subclasses that manage thoses units should call it. + void grantDocumentRelativeUnits(); + }; #endif // KISSPINBOXUNITMANAGER_H diff --git a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp index a1a92c23d5..48b7a8fc9f 100644 --- a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp +++ b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp @@ -1,1236 +1,1241 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2008-2009 Jan Hambrecht Copyright (C) 2008 C. Boemann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "DefaultTool.h" #include "DefaultToolWidget.h" #include "DefaultToolArrangeWidget.h" #include "SelectionDecorator.h" #include "ShapeMoveStrategy.h" #include "ShapeRotateStrategy.h" #include "ShapeShearStrategy.h" #include "ShapeResizeStrategy.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 "kis_action_registry.h" +#include "kis_document_aware_spin_box_unit_manager.h" + #include #include #include #include #include #include #include #define HANDLE_DISTANCE 10 class NopInteractionStrategy : public KoInteractionStrategy { public: explicit NopInteractionStrategy(KoToolBase *parent) : KoInteractionStrategy(parent) { } virtual KUndo2Command *createCommand() { return 0; } virtual void handleMouseMove(const QPointF & /*mouseLocation*/, Qt::KeyboardModifiers /*modifiers*/) {} virtual void finishInteraction(Qt::KeyboardModifiers /*modifiers*/) {} }; class SelectionHandler : public KoToolSelection { public: SelectionHandler(DefaultTool *parent) : KoToolSelection(parent) , m_selection(parent->koSelection()) { Q_ASSERT(m_selection); } bool hasSelection() { return m_selection->count(); } private: KoSelection *m_selection; }; DefaultTool::DefaultTool(KoCanvasBase *canvas) : KoInteractionTool(canvas) , m_lastHandle(KoFlake::NoHandle) , m_hotPosition(KoFlake::TopLeftCorner) , m_mouseWasInsideHandles(false) , m_moveCommand(0) , m_selectionHandler(new SelectionHandler(this)) , m_customEventStrategy(0) { setupActions(); QPixmap rotatePixmap, shearPixmap; rotatePixmap.load(":/cursor_rotate.png"); Q_ASSERT(!rotatePixmap.isNull()); shearPixmap.load(":/cursor_shear.png"); Q_ASSERT(!shearPixmap.isNull()); m_rotateCursors[0] = QCursor(rotatePixmap.transformed(QTransform().rotate(45))); m_rotateCursors[1] = QCursor(rotatePixmap.transformed(QTransform().rotate(90))); m_rotateCursors[2] = QCursor(rotatePixmap.transformed(QTransform().rotate(135))); m_rotateCursors[3] = QCursor(rotatePixmap.transformed(QTransform().rotate(180))); m_rotateCursors[4] = QCursor(rotatePixmap.transformed(QTransform().rotate(225))); m_rotateCursors[5] = QCursor(rotatePixmap.transformed(QTransform().rotate(270))); m_rotateCursors[6] = QCursor(rotatePixmap.transformed(QTransform().rotate(315))); m_rotateCursors[7] = QCursor(rotatePixmap); /* m_rotateCursors[0] = QCursor(Qt::RotateNCursor); m_rotateCursors[1] = QCursor(Qt::RotateNECursor); m_rotateCursors[2] = QCursor(Qt::RotateECursor); m_rotateCursors[3] = QCursor(Qt::RotateSECursor); m_rotateCursors[4] = QCursor(Qt::RotateSCursor); m_rotateCursors[5] = QCursor(Qt::RotateSWCursor); m_rotateCursors[6] = QCursor(Qt::RotateWCursor); m_rotateCursors[7] = QCursor(Qt::RotateNWCursor); */ m_shearCursors[0] = QCursor(shearPixmap); m_shearCursors[1] = QCursor(shearPixmap.transformed(QTransform().rotate(45))); m_shearCursors[2] = QCursor(shearPixmap.transformed(QTransform().rotate(90))); m_shearCursors[3] = QCursor(shearPixmap.transformed(QTransform().rotate(135))); m_shearCursors[4] = QCursor(shearPixmap.transformed(QTransform().rotate(180))); m_shearCursors[5] = QCursor(shearPixmap.transformed(QTransform().rotate(225))); m_shearCursors[6] = QCursor(shearPixmap.transformed(QTransform().rotate(270))); m_shearCursors[7] = QCursor(shearPixmap.transformed(QTransform().rotate(315))); m_sizeCursors[0] = Qt::SizeVerCursor; m_sizeCursors[1] = Qt::SizeBDiagCursor; m_sizeCursors[2] = Qt::SizeHorCursor; m_sizeCursors[3] = Qt::SizeFDiagCursor; m_sizeCursors[4] = Qt::SizeVerCursor; m_sizeCursors[5] = Qt::SizeBDiagCursor; m_sizeCursors[6] = Qt::SizeHorCursor; m_sizeCursors[7] = Qt::SizeFDiagCursor; KoShapeManager *manager = canvas->shapeManager(); connect(manager, SIGNAL(selectionChanged()), this, SLOT(updateActions())); } DefaultTool::~DefaultTool() { } bool DefaultTool::wantsAutoScroll() const { return true; } void DefaultTool::setupActions() { KisActionRegistry *actionRegistry = KisActionRegistry::instance(); QAction *actionBringToFront = actionRegistry->makeQAction("object_order_front", this); addAction("object_order_front", actionBringToFront); connect(actionBringToFront, SIGNAL(triggered()), this, SLOT(selectionBringToFront())); QAction *actionRaise = actionRegistry->makeQAction("object_order_raise", this); addAction("object_order_raise", actionRaise); connect(actionRaise, SIGNAL(triggered()), this, SLOT(selectionMoveUp())); QAction *actionLower = actionRegistry->makeQAction("object_order_lower", this); addAction("object_order_lower", actionLower); connect(actionLower, SIGNAL(triggered()), this, SLOT(selectionMoveDown())); QAction *actionSendToBack = actionRegistry->makeQAction("object_order_back", this); addAction("object_order_back", actionSendToBack); connect(actionSendToBack, SIGNAL(triggered()), this, SLOT(selectionSendToBack())); QAction *actionAlignLeft = actionRegistry->makeQAction("object_align_horizontal_left", this); addAction("object_align_horizontal_left", actionAlignLeft); connect(actionAlignLeft, SIGNAL(triggered()), this, SLOT(selectionAlignHorizontalLeft())); QAction *actionAlignCenter = actionRegistry->makeQAction("object_align_horizontal_center", this); addAction("object_align_horizontal_center", actionAlignCenter); connect(actionAlignCenter, SIGNAL(triggered()), this, SLOT(selectionAlignHorizontalCenter())); QAction *actionAlignRight = actionRegistry->makeQAction("object_align_horizontal_right", this); addAction("object_align_horizontal_right", actionAlignRight); connect(actionAlignRight, SIGNAL(triggered()), this, SLOT(selectionAlignHorizontalRight())); QAction *actionAlignTop = actionRegistry->makeQAction("object_align_vertical_top", this); addAction("object_align_vertical_top", actionAlignTop); connect(actionAlignTop, SIGNAL(triggered()), this, SLOT(selectionAlignVerticalTop())); QAction *actionAlignMiddle = actionRegistry->makeQAction("object_align_vertical_center", this); addAction("object_align_vertical_center", actionAlignMiddle); connect(actionAlignMiddle, SIGNAL(triggered()), this, SLOT(selectionAlignVerticalCenter())); QAction *actionAlignBottom = actionRegistry->makeQAction("object_align_vertical_bottom", this); addAction("object_align_vertical_bottom", actionAlignBottom); connect(actionAlignBottom, SIGNAL(triggered()), this, SLOT(selectionAlignVerticalBottom())); QAction *actionGroupBottom = actionRegistry->makeQAction("object_group", this); addAction("object_group", actionGroupBottom); connect(actionGroupBottom, SIGNAL(triggered()), this, SLOT(selectionGroup())); QAction *actionUngroupBottom = actionRegistry->makeQAction("object_ungroup", this); addAction("object_ungroup", actionUngroupBottom); connect(actionUngroupBottom, SIGNAL(triggered()), this, SLOT(selectionUngroup())); } qreal DefaultTool::rotationOfHandle(KoFlake::SelectionHandle handle, bool useEdgeRotation) { QPointF selectionCenter = koSelection()->absolutePosition(); QPointF direction; switch (handle) { case KoFlake::TopMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::TopRightCorner) - koSelection()->absolutePosition(KoFlake::TopLeftCorner); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeftCorner); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::TopRightCorner) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::TopRightHandle: direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopRightCorner) - koSelection()->absolutePosition(KoFlake::TopLeftCorner)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopRightCorner) - koSelection()->absolutePosition(KoFlake::BottomRightCorner)).normalized()).toPointF(); break; case KoFlake::RightMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::BottomRightCorner) - koSelection()->absolutePosition(KoFlake::TopRightCorner); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopRightCorner); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRightCorner) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::BottomRightHandle: direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomRightCorner) - koSelection()->absolutePosition(KoFlake::BottomLeftCorner)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomRightCorner) - koSelection()->absolutePosition(KoFlake::TopRightCorner)).normalized()).toPointF(); break; case KoFlake::BottomMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::BottomLeftCorner) - koSelection()->absolutePosition(KoFlake::BottomRightCorner); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::BottomLeftCorner); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRightCorner) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::BottomLeftHandle: direction = koSelection()->absolutePosition(KoFlake::BottomLeftCorner) - selectionCenter; direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeftCorner) - koSelection()->absolutePosition(KoFlake::BottomRightCorner)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeftCorner) - koSelection()->absolutePosition(KoFlake::TopLeftCorner)).normalized()).toPointF(); break; case KoFlake::LeftMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::TopLeftCorner) - koSelection()->absolutePosition(KoFlake::BottomLeftCorner); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeftCorner); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomLeftCorner) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::TopLeftHandle: direction = koSelection()->absolutePosition(KoFlake::TopLeftCorner) - selectionCenter; direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopLeftCorner) - koSelection()->absolutePosition(KoFlake::TopRightCorner)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopLeftCorner) - koSelection()->absolutePosition(KoFlake::BottomLeftCorner)).normalized()).toPointF(); break; case KoFlake::NoHandle: return 0.0; break; } qreal rotation = atan2(direction.y(), direction.x()) * 180.0 / M_PI; switch (handle) { case KoFlake::TopMiddleHandle: if (useEdgeRotation) { rotation -= 0.0; } else { rotation -= 270.0; } break; case KoFlake::TopRightHandle: rotation -= 315.0; break; case KoFlake::RightMiddleHandle: if (useEdgeRotation) { rotation -= 90.0; } else { rotation -= 0.0; } break; case KoFlake::BottomRightHandle: rotation -= 45.0; break; case KoFlake::BottomMiddleHandle: if (useEdgeRotation) { rotation -= 180.0; } else { rotation -= 90.0; } break; case KoFlake::BottomLeftHandle: rotation -= 135.0; break; case KoFlake::LeftMiddleHandle: if (useEdgeRotation) { rotation -= 270.0; } else { rotation -= 180.0; } break; case KoFlake::TopLeftHandle: rotation -= 225.0; break; case KoFlake::NoHandle: break; } if (rotation < 0.0) { rotation += 360.0; } return rotation; } void DefaultTool::updateCursor() { QCursor cursor = Qt::ArrowCursor; QString statusText; if (koSelection()->count() > 0) { // has a selection bool editable = editableShapesCount(koSelection()->selectedShapes(KoFlake::StrippedSelection)); if (!m_mouseWasInsideHandles) { m_angle = rotationOfHandle(m_lastHandle, true); int rotOctant = 8 + int(8.5 + m_angle / 45); bool rotateHandle = false; bool shearHandle = false; switch (m_lastHandle) { case KoFlake::TopMiddleHandle: cursor = m_shearCursors[(0 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::TopRightHandle: cursor = m_rotateCursors[(1 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::RightMiddleHandle: cursor = m_shearCursors[(2 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::BottomRightHandle: cursor = m_rotateCursors[(3 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::BottomMiddleHandle: cursor = m_shearCursors[(4 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::BottomLeftHandle: cursor = m_rotateCursors[(5 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::LeftMiddleHandle: cursor = m_shearCursors[(6 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::TopLeftHandle: cursor = m_rotateCursors[(7 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::NoHandle: cursor = Qt::ArrowCursor; break; } if (rotateHandle) { statusText = i18n("Left click rotates around center, right click around highlighted position."); } if (shearHandle) { statusText = i18n("Click and drag to shear selection."); } } else { statusText = i18n("Click and drag to resize selection."); m_angle = rotationOfHandle(m_lastHandle, false); int rotOctant = 8 + int(8.5 + m_angle / 45); bool cornerHandle = false; switch (m_lastHandle) { case KoFlake::TopMiddleHandle: cursor = m_sizeCursors[(0 + rotOctant) % 8]; break; case KoFlake::TopRightHandle: cursor = m_sizeCursors[(1 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::RightMiddleHandle: cursor = m_sizeCursors[(2 + rotOctant) % 8]; break; case KoFlake::BottomRightHandle: cursor = m_sizeCursors[(3 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::BottomMiddleHandle: cursor = m_sizeCursors[(4 + rotOctant) % 8]; break; case KoFlake::BottomLeftHandle: cursor = m_sizeCursors[(5 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::LeftMiddleHandle: cursor = m_sizeCursors[(6 + rotOctant) % 8]; break; case KoFlake::TopLeftHandle: cursor = m_sizeCursors[(7 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::NoHandle: cursor = Qt::SizeAllCursor; statusText = i18n("Click and drag to move selection."); break; } if (cornerHandle) { statusText = i18n("Click and drag to resize selection. Middle click to set highlighted position."); } } if (!editable) { cursor = Qt::ArrowCursor; } } else { // there used to be guides... :'''( } useCursor(cursor); if (currentStrategy() == 0) { emit statusTextChanged(statusText); } } void DefaultTool::paint(QPainter &painter, const KoViewConverter &converter) { KoInteractionTool::paint(painter, converter); if (currentStrategy() == 0 && koSelection()->count() > 0) { SelectionDecorator decorator(m_mouseWasInsideHandles ? m_lastHandle : KoFlake::NoHandle, true, true); decorator.setSelection(koSelection()); decorator.setHandleRadius(handleRadius()); decorator.setHotPosition(m_hotPosition); decorator.paint(painter, converter); } painter.save(); KoShape::applyConversion(painter, converter); canvas()->snapGuide()->paint(painter, converter); painter.restore(); } void DefaultTool::mousePressEvent(KoPointerEvent *event) { KoInteractionTool::mousePressEvent(event); updateCursor(); } void DefaultTool::mouseMoveEvent(KoPointerEvent *event) { KoInteractionTool::mouseMoveEvent(event); if (currentStrategy() == 0 && koSelection()->count() > 0) { QRectF bound = handlesSize(); if (bound.contains(event->point)) { bool inside; KoFlake::SelectionHandle newDirection = handleAt(event->point, &inside); if (inside != m_mouseWasInsideHandles || m_lastHandle != newDirection) { m_lastHandle = newDirection; m_mouseWasInsideHandles = inside; //repaintDecorations(); } } else { /*if (m_lastHandle != KoFlake::NoHandle) repaintDecorations(); */ m_lastHandle = KoFlake::NoHandle; m_mouseWasInsideHandles = false; // there used to be guides... :'''( } } else { // there used to be guides... :'''( } updateCursor(); } QRectF DefaultTool::handlesSize() { QRectF bound = koSelection()->boundingRect(); // expansion Border if (!canvas() || !canvas()->viewConverter()) { return bound; } QPointF border = canvas()->viewConverter()->viewToDocument(QPointF(HANDLE_DISTANCE, HANDLE_DISTANCE)); bound.adjust(-border.x(), -border.y(), border.x(), border.y()); return bound; } void DefaultTool::mouseReleaseEvent(KoPointerEvent *event) { KoInteractionTool::mouseReleaseEvent(event); updateCursor(); } void DefaultTool::mouseDoubleClickEvent(KoPointerEvent *event) { QList shapes; Q_FOREACH (KoShape *shape, koSelection()->selectedShapes()) { if (shape->boundingRect().contains(event->point) && // first 'cheap' check shape->outline().contains(event->point)) { // this is more expensive but weeds out the almost hits shapes.append(shape); } } if (shapes.count() == 0) { // nothing in the selection was clicked on. KoShape *shape = canvas()->shapeManager()->shapeAt(event->point, KoFlake::ShapeOnTop); if (shape) { shapes.append(shape); } // there used to be guides... :'''( } QList shapes2; foreach (KoShape *shape, shapes) { QSet delegates = shape->toolDelegates(); if (delegates.isEmpty()) { shapes2.append(shape); } else { foreach (KoShape *delegatedShape, delegates) { shapes2.append(delegatedShape); } } } KoToolManager::instance()->switchToolRequested( KoToolManager::instance()->preferredToolForSelection(shapes2)); } bool DefaultTool::moveSelection(int direction, Qt::KeyboardModifiers modifiers) { qreal x = 0.0, y = 0.0; if (direction == Qt::Key_Left) { x = -5; } else if (direction == Qt::Key_Right) { x = 5; } else if (direction == Qt::Key_Up) { y = -5; } else if (direction == Qt::Key_Down) { y = 5; } if (x != 0.0 || y != 0.0) { // actually move if ((modifiers & Qt::ShiftModifier) != 0) { x *= 10; y *= 10; } else if ((modifiers & Qt::AltModifier) != 0) { // more precise x /= 5; y /= 5; } QList prevPos; QList newPos; QList shapes; Q_FOREACH (KoShape *shape, koSelection()->selectedShapes(KoFlake::TopLevelSelection)) { if (shape->isGeometryProtected()) { continue; } shapes.append(shape); QPointF p = shape->position(); prevPos.append(p); p.setX(p.x() + x); p.setY(p.y() + y); newPos.append(p); } if (shapes.count() > 0) { // use a timeout to make sure we don't reuse a command possibly deleted by the commandHistory if (m_lastUsedMoveCommand.msecsTo(QTime::currentTime()) > 5000) { m_moveCommand = 0; } if (m_moveCommand) { // alter previous instead of creating new one. m_moveCommand->setNewPositions(newPos); m_moveCommand->redo(); } else { m_moveCommand = new KoShapeMoveCommand(shapes, prevPos, newPos); canvas()->addCommand(m_moveCommand); } m_lastUsedMoveCommand = QTime::currentTime(); return true; } } return false; } void DefaultTool::keyPressEvent(QKeyEvent *event) { KoInteractionTool::keyPressEvent(event); if (currentStrategy() == 0) { switch (event->key()) { case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Up: case Qt::Key_Down: if (moveSelection(event->key(), event->modifiers())) { event->accept(); } break; case Qt::Key_1: case Qt::Key_2: case Qt::Key_3: case Qt::Key_4: case Qt::Key_5: canvas()->resourceManager()->setResource(HotPosition, event->key() - Qt::Key_1); event->accept(); break; default: return; } } } void DefaultTool::customMoveEvent(KoPointerEvent *event) { if (!koSelection()->count()) { event->ignore(); return; } int move = qMax(qAbs(event->x()), qAbs(event->y())); int zoom = qAbs(event->z()); int rotate = qAbs(event->rotationZ()); const int threshold = 2; if (move < threshold && zoom < threshold && rotate < threshold) { if (m_customEventStrategy) { m_customEventStrategy->finishInteraction(event->modifiers()); KUndo2Command *command = m_customEventStrategy->createCommand(); if (command) { canvas()->addCommand(command); } delete m_customEventStrategy; m_customEventStrategy = 0; repaintDecorations(); } event->accept(); return; } // check if the z-movement is dominant if (zoom > move && zoom > rotate) { // zoom if (!m_customEventStrategy) { m_customEventStrategy = new ShapeResizeStrategy(this, event->point, KoFlake::TopLeftHandle); } } else if (move > zoom && move > rotate) { // check if x-/y-movement is dominant // move if (!m_customEventStrategy) { m_customEventStrategy = new ShapeMoveStrategy(this, event->point); } } else if (rotate > zoom && rotate > move) { // rotation is dominant // rotate if (!m_customEventStrategy) { m_customEventStrategy = new ShapeRotateStrategy(this, event->point, event->buttons()); } } if (m_customEventStrategy) { m_customEventStrategy->handleCustomEvent(event); } event->accept(); } void DefaultTool::repaintDecorations() { Q_ASSERT(koSelection()); if (koSelection()->count() > 0) { canvas()->updateCanvas(handlesSize()); } } void DefaultTool::copy() const { QList shapes = canvas()->shapeManager()->selection()->selectedShapes(KoFlake::TopLevelSelection); if (!shapes.empty()) { KoShapeOdfSaveHelper saveHelper(shapes); KoDrag drag; drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper); drag.addToClipboard(); } } void DefaultTool::deleteSelection() { QList shapes; foreach (KoShape *s, canvas()->shapeManager()->selection()->selectedShapes(KoFlake::TopLevelSelection)) { if (s->isGeometryProtected()) { continue; } shapes << s; } if (!shapes.empty()) { canvas()->addCommand(canvas()->shapeController()->removeShapes(shapes)); } } bool DefaultTool::paste() { // we no longer have to do anything as tool Proxy will do it for us return false; } QStringList DefaultTool::supportedPasteMimeTypes() const { QStringList list; list << KoOdf::mimeType(KoOdf::Text); return list; } KoSelection *DefaultTool::koSelection() { Q_ASSERT(canvas()); Q_ASSERT(canvas()->shapeManager()); return canvas()->shapeManager()->selection(); } KoFlake::SelectionHandle DefaultTool::handleAt(const QPointF &point, bool *innerHandleMeaning) { // check for handles in this order; meaning that when handles overlap the one on top is chosen static const KoFlake::SelectionHandle handleOrder[] = { KoFlake::BottomRightHandle, KoFlake::TopLeftHandle, KoFlake::BottomLeftHandle, KoFlake::TopRightHandle, KoFlake::BottomMiddleHandle, KoFlake::RightMiddleHandle, KoFlake::LeftMiddleHandle, KoFlake::TopMiddleHandle, KoFlake::NoHandle }; if (koSelection()->count() == 0) { return KoFlake::NoHandle; } recalcSelectionBox(); const KoViewConverter *converter = canvas()->viewConverter(); if (!converter) { return KoFlake::NoHandle; } if (innerHandleMeaning != 0) { QPainterPath path; path.addPolygon(m_selectionOutline); *innerHandleMeaning = path.contains(point) || path.intersects(handlePaintRect(point)); } for (int i = 0; i < KoFlake::NoHandle; ++i) { KoFlake::SelectionHandle handle = handleOrder[i]; QPointF pt = converter->documentToView(point) - converter->documentToView(m_selectionBox[handle]); // if just inside the outline if (qAbs(pt.x()) < HANDLE_DISTANCE && qAbs(pt.y()) < HANDLE_DISTANCE) { if (innerHandleMeaning != 0) { if (qAbs(pt.x()) < 4 && qAbs(pt.y()) < 4) { *innerHandleMeaning = true; } } return handle; } } return KoFlake::NoHandle; } void DefaultTool::recalcSelectionBox() { if (koSelection()->count() == 0) { return; } if (koSelection()->count() > 1) { QTransform matrix = koSelection()->absoluteTransformation(0); m_selectionOutline = matrix.map(QPolygonF(QRectF(QPointF(0, 0), koSelection()->size()))); m_angle = 0.0; //koSelection()->rotation(); } else { QTransform matrix = koSelection()->firstSelectedShape()->absoluteTransformation(0); m_selectionOutline = matrix.map(QPolygonF(QRectF(QPointF(0, 0), koSelection()->firstSelectedShape()->size()))); m_angle = 0.0; //koSelection()->firstSelectedShape()->rotation(); } QPolygonF outline = m_selectionOutline; //shorter name in the following :) m_selectionBox[KoFlake::TopMiddleHandle] = (outline.value(0) + outline.value(1)) / 2; m_selectionBox[KoFlake::TopRightHandle] = outline.value(1); m_selectionBox[KoFlake::RightMiddleHandle] = (outline.value(1) + outline.value(2)) / 2; m_selectionBox[KoFlake::BottomRightHandle] = outline.value(2); m_selectionBox[KoFlake::BottomMiddleHandle] = (outline.value(2) + outline.value(3)) / 2; m_selectionBox[KoFlake::BottomLeftHandle] = outline.value(3); m_selectionBox[KoFlake::LeftMiddleHandle] = (outline.value(3) + outline.value(0)) / 2; m_selectionBox[KoFlake::TopLeftHandle] = outline.value(0); if (koSelection()->count() == 1) { #if 0 // TODO detect mirroring KoShape *s = koSelection()->firstSelectedShape(); if (s->scaleX() < 0) { // vertically mirrored: swap left / right qSwap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::TopRightHandle]); qSwap(m_selectionBox[KoFlake::LeftMiddleHandle], m_selectionBox[KoFlake::RightMiddleHandle]); qSwap(m_selectionBox[KoFlake::BottomLeftHandle], m_selectionBox[KoFlake::BottomRightHandle]); } if (s->scaleY() < 0) { // vertically mirrored: swap top / bottom qSwap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::BottomLeftHandle]); qSwap(m_selectionBox[KoFlake::TopMiddleHandle], m_selectionBox[KoFlake::BottomMiddleHandle]); qSwap(m_selectionBox[KoFlake::TopRightHandle], m_selectionBox[KoFlake::BottomRightHandle]); } #endif } } void DefaultTool::activate(ToolActivation, const QSet &) { m_mouseWasInsideHandles = false; m_lastHandle = KoFlake::NoHandle; useCursor(Qt::ArrowCursor); repaintDecorations(); updateActions(); } void DefaultTool::selectionAlignHorizontalLeft() { selectionAlign(KoShapeAlignCommand::HorizontalLeftAlignment); } void DefaultTool::selectionAlignHorizontalCenter() { selectionAlign(KoShapeAlignCommand::HorizontalCenterAlignment); } void DefaultTool::selectionAlignHorizontalRight() { selectionAlign(KoShapeAlignCommand::HorizontalRightAlignment); } void DefaultTool::selectionAlignVerticalTop() { selectionAlign(KoShapeAlignCommand::VerticalTopAlignment); } void DefaultTool::selectionAlignVerticalCenter() { selectionAlign(KoShapeAlignCommand::VerticalCenterAlignment); } void DefaultTool::selectionAlignVerticalBottom() { selectionAlign(KoShapeAlignCommand::VerticalBottomAlignment); } void DefaultTool::selectionGroup() { KoSelection *selection = koSelection(); if (!selection) { return; } QList selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection); QList groupedShapes; // only group shapes with an unselected parent foreach (KoShape *shape, selectedShapes) { if (!selectedShapes.contains(shape->parent()) && shape->isEditable()) { groupedShapes << shape; } } KoShapeGroup *group = new KoShapeGroup(); // TODO what if only one shape is left? KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Group shapes")); canvas()->shapeController()->addShapeDirect(group, cmd); KoShapeGroupCommand::createCommand(group, groupedShapes, cmd); canvas()->addCommand(cmd); // update selection so we can ungroup immediately again selection->deselectAll(); selection->select(group); } void DefaultTool::selectionUngroup() { KoSelection *selection = canvas()->shapeManager()->selection(); if (!selection) { return; } QList selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection); QList containerSet; // only ungroup shape groups with an unselected parent foreach (KoShape *shape, selectedShapes) { if (!selectedShapes.contains(shape->parent()) && shape->isEditable()) { containerSet << shape; } } KUndo2Command *cmd = 0; // add a ungroup command for each found shape container to the macro command Q_FOREACH (KoShape *shape, containerSet) { KoShapeGroup *group = dynamic_cast(shape); if (group) { cmd = cmd ? cmd : new KUndo2Command(kundo2_i18n("Ungroup shapes")); new KoShapeUngroupCommand(group, group->shapes(), group->parent() ? QList() : canvas()->shapeManager()->topLevelShapes(), cmd); canvas()->shapeController()->removeShape(group, cmd); } } if (cmd) { canvas()->addCommand(cmd); } } void DefaultTool::selectionAlign(KoShapeAlignCommand::Align align) { KoSelection *selection = canvas()->shapeManager()->selection(); if (!selection) { return; } QList selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection); if (selectedShapes.count() < 1) { return; } QList editableShapes = filterEditableShapes(selectedShapes); // TODO add an option to the widget so that one can align to the page // with multiple selected shapes too QRectF bb; // single selected shape is automatically aligned to document rect if (editableShapes.count() == 1) { if (!canvas()->resourceManager()->hasResource(KoCanvasResourceManager::PageSize)) { return; } bb = QRectF(QPointF(0, 0), canvas()->resourceManager()->sizeResource(KoCanvasResourceManager::PageSize)); } else { Q_FOREACH (KoShape *shape, editableShapes) { bb |= shape->boundingRect(); } } KoShapeAlignCommand *cmd = new KoShapeAlignCommand(editableShapes, align, bb); canvas()->addCommand(cmd); selection->updateSizeAndPosition(); } void DefaultTool::selectionBringToFront() { selectionReorder(KoShapeReorderCommand::BringToFront); } void DefaultTool::selectionMoveUp() { selectionReorder(KoShapeReorderCommand::RaiseShape); } void DefaultTool::selectionMoveDown() { selectionReorder(KoShapeReorderCommand::LowerShape); } void DefaultTool::selectionSendToBack() { selectionReorder(KoShapeReorderCommand::SendToBack); } void DefaultTool::selectionReorder(KoShapeReorderCommand::MoveShapeType order) { KoSelection *selection = canvas()->shapeManager()->selection(); if (!selection) { return; } QList selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection); if (selectedShapes.count() < 1) { return; } QList editableShapes = filterEditableShapes(selectedShapes); if (editableShapes.count() < 1) { return; } KUndo2Command *cmd = KoShapeReorderCommand::createCommand(editableShapes, canvas()->shapeManager(), order); if (cmd) { canvas()->addCommand(cmd); } } QList > DefaultTool::createOptionWidgets() { QList > widgets; DefaultToolArrangeWidget *defaultArrange = new DefaultToolArrangeWidget(this); defaultArrange->setWindowTitle(i18n("Arrange")); widgets.append(defaultArrange); DefaultToolWidget *defaultTool = new DefaultToolWidget(this); defaultTool->setWindowTitle(i18n("Geometry")); widgets.append(defaultTool); KoStrokeConfigWidget *strokeWidget = new KoStrokeConfigWidget(0); strokeWidget->setWindowTitle(i18n("Line")); strokeWidget->setCanvas(canvas()); widgets.append(strokeWidget); KoFillConfigWidget *fillWidget = new KoFillConfigWidget(0); fillWidget->setWindowTitle(i18n("Fill")); fillWidget->setCanvas(canvas()); widgets.append(fillWidget); KoShadowConfigWidget *shadowWidget = new KoShadowConfigWidget(0); + KisDocumentAwareSpinBoxUnitManager* managerBlur = new KisDocumentAwareSpinBoxUnitManager(shadowWidget); //let the shadow widget be aware of document relative units. + KisDocumentAwareSpinBoxUnitManager* managerOffset = new KisDocumentAwareSpinBoxUnitManager(shadowWidget); //let the shadow widget be aware of document relative units. + shadowWidget->setUnitManager(managerBlur, managerOffset); shadowWidget->setWindowTitle(i18n("Shadow")); shadowWidget->setCanvas(canvas()); widgets.append(shadowWidget); return widgets; } void DefaultTool::canvasResourceChanged(int key, const QVariant &res) { if (key == HotPosition) { m_hotPosition = static_cast(res.toInt()); repaintDecorations(); } } KoInteractionStrategy *DefaultTool::createStrategy(KoPointerEvent *event) { // reset the move by keys when a new strategy is created otherwise we might change the // command after a new command was added. This happend when you where faster than the timer. m_moveCommand = 0; KoShapeManager *shapeManager = canvas()->shapeManager(); KoSelection *select = shapeManager->selection(); bool insideSelection; KoFlake::SelectionHandle handle = handleAt(event->point, &insideSelection); bool editableShape = editableShapesCount(select->selectedShapes()); if (event->buttons() & Qt::MidButton) { // change the hot selection position when middle clicking on a handle KoFlake::Position newHotPosition = m_hotPosition; switch (handle) { case KoFlake::TopLeftHandle: newHotPosition = KoFlake::TopLeftCorner; break; case KoFlake::TopRightHandle: newHotPosition = KoFlake::TopRightCorner; break; case KoFlake::BottomLeftHandle: newHotPosition = KoFlake::BottomLeftCorner; break; case KoFlake::BottomRightHandle: newHotPosition = KoFlake::BottomRightCorner; break; default: { // check if we had hit the center point const KoViewConverter *converter = canvas()->viewConverter(); QPointF pt = converter->documentToView(event->point - select->absolutePosition()); if (qAbs(pt.x()) < HANDLE_DISTANCE && qAbs(pt.y()) < HANDLE_DISTANCE) { newHotPosition = KoFlake::CenteredPosition; } break; } } if (m_hotPosition != newHotPosition) { canvas()->resourceManager()->setResource(HotPosition, newHotPosition); } return 0; } bool selectMultiple = event->modifiers() & Qt::ControlModifier; bool selectNextInStack = event->modifiers() & Qt::ShiftModifier; if (editableShape) { // manipulation of selected shapes goes first if (handle != KoFlake::NoHandle) { if (event->buttons() == Qt::LeftButton) { // resizing or shearing only with left mouse button if (insideSelection) { return new ShapeResizeStrategy(this, event->point, handle); } if (handle == KoFlake::TopMiddleHandle || handle == KoFlake::RightMiddleHandle || handle == KoFlake::BottomMiddleHandle || handle == KoFlake::LeftMiddleHandle) { return new ShapeShearStrategy(this, event->point, handle); } } // rotating is allowed for rigth mouse button too if (handle == KoFlake::TopLeftHandle || handle == KoFlake::TopRightHandle || handle == KoFlake::BottomLeftHandle || handle == KoFlake::BottomRightHandle) { return new ShapeRotateStrategy(this, event->point, event->buttons()); } } if (!(selectMultiple || selectNextInStack) && event->buttons() == Qt::LeftButton) { const QPainterPath outlinePath = select->transformation().map(select->outline()); if (outlinePath.contains(event->point) || outlinePath.intersects(handlePaintRect(event->point))) { return new ShapeMoveStrategy(this, event->point); } } } if ((event->buttons() & Qt::LeftButton) == 0) { return 0; // Nothing to do for middle/right mouse button } KoShape *shape = shapeManager->shapeAt(event->point, selectNextInStack ? KoFlake::NextUnselected : KoFlake::ShapeOnTop); if (!shape && handle == KoFlake::NoHandle) { if (!selectMultiple) { repaintDecorations(); select->deselectAll(); } return new KoShapeRubberSelectStrategy(this, event->point); } if (select->isSelected(shape)) { if (selectMultiple) { repaintDecorations(); select->deselect(shape); } } else if (handle == KoFlake::NoHandle) { // clicked on shape which is not selected repaintDecorations(); if (!selectMultiple) { shapeManager->selection()->deselectAll(); } select->select(shape, selectNextInStack ? false : true); repaintDecorations(); // tablet selection isn't precise and may lead to a move, preventing that if (event->isTabletEvent()) { return new NopInteractionStrategy(this); } return new ShapeMoveStrategy(this, event->point); } return 0; } void DefaultTool::updateActions() { KoSelection *selection(koSelection()); if (!selection) { action("object_order_front")->setEnabled(false); action("object_order_raise")->setEnabled(false); action("object_order_lower")->setEnabled(false); action("object_order_back")->setEnabled(false); action("object_align_horizontal_left")->setEnabled(false); action("object_align_horizontal_center")->setEnabled(false); action("object_align_horizontal_right")->setEnabled(false); action("object_align_vertical_top")->setEnabled(false); action("object_align_vertical_center")->setEnabled(false); action("object_align_vertical_bottom")->setEnabled(false); action("object_group")->setEnabled(false); action("object_ungroup")->setEnabled(false); return; } QList editableShapes = filterEditableShapes(selection->selectedShapes(KoFlake::TopLevelSelection)); bool enable = editableShapes.count() > 0; action("object_order_front")->setEnabled(enable); action("object_order_raise")->setEnabled(enable); action("object_order_lower")->setEnabled(enable); action("object_order_back")->setEnabled(enable); enable = (editableShapes.count() > 1) || (enable && canvas()->resourceManager()->hasResource(KoCanvasResourceManager::PageSize)); action("object_align_horizontal_left")->setEnabled(enable); action("object_align_horizontal_center")->setEnabled(enable); action("object_align_horizontal_right")->setEnabled(enable); action("object_align_vertical_top")->setEnabled(enable); action("object_align_vertical_center")->setEnabled(enable); action("object_align_vertical_bottom")->setEnabled(enable); action("object_group")->setEnabled(editableShapes.count() > 1); bool groupShape = false; foreach (KoShape *shape, editableShapes) { if (dynamic_cast(shape)) { groupShape = true; break; } } action("object_ungroup")->setEnabled(groupShape); emit selectionChanged(selection->count()); } KoToolSelection *DefaultTool::selection() { return m_selectionHandler; } QList DefaultTool::filterEditableShapes(const QList &shapes) { QList editableShapes; Q_FOREACH (KoShape *shape, shapes) { if (shape->isEditable()) { editableShapes.append(shape); } } return editableShapes; } uint DefaultTool::editableShapesCount(const QList &shapes) { uint count = 0; Q_FOREACH (KoShape *shape, shapes) { if (shape->isEditable()) { count++; } } return count; }