diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt
index 4d566a6fe5..248285fb4a 100644
--- a/libs/ui/CMakeLists.txt
+++ b/libs/ui/CMakeLists.txt
@@ -1,596 +1,597 @@
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile
)
include_directories(SYSTEM
${EIGEN3_INCLUDE_DIR}
${OCIO_INCLUDE_DIR}
)
add_subdirectory( tests )
if (APPLE)
find_library(FOUNDATION_LIBRARY Foundation)
find_library(APPKIT_LIBRARY AppKit)
endif ()
set(kritaui_LIB_SRCS
canvas/kis_canvas_widget_base.cpp
canvas/kis_canvas2.cpp
canvas/kis_canvas_updates_compressor.cpp
canvas/kis_canvas_controller.cpp
canvas/kis_paintop_transformation_connector.cpp
canvas/kis_display_color_converter.cpp
canvas/kis_display_filter.cpp
canvas/kis_exposure_gamma_correction_interface.cpp
canvas/kis_tool_proxy.cpp
canvas/kis_canvas_decoration.cc
canvas/kis_coordinates_converter.cpp
canvas/kis_grid_manager.cpp
canvas/kis_grid_decoration.cpp
canvas/kis_grid_config.cpp
canvas/kis_prescaled_projection.cpp
canvas/kis_qpainter_canvas.cpp
canvas/kis_projection_backend.cpp
canvas/kis_update_info.cpp
canvas/kis_image_patch.cpp
canvas/kis_image_pyramid.cpp
canvas/kis_infinity_manager.cpp
canvas/kis_change_guides_command.cpp
canvas/kis_guides_decoration.cpp
canvas/kis_guides_manager.cpp
canvas/kis_guides_config.cpp
canvas/kis_snap_config.cpp
canvas/kis_snap_line_strategy.cpp
canvas/KisSnapPointStrategy.cpp
- dialogs/kis_about_application.cpp
+ canvas/KisMirrorAxisConfig.cpp
+ dialogs/kis_about_application.cpp
dialogs/kis_dlg_adj_layer_props.cc
dialogs/kis_dlg_adjustment_layer.cc
dialogs/kis_dlg_filter.cpp
dialogs/kis_dlg_generator_layer.cpp
dialogs/kis_dlg_file_layer.cpp
dialogs/kis_dlg_filter.cpp
dialogs/kis_dlg_stroke_selection_properties.cpp
dialogs/kis_dlg_image_properties.cc
dialogs/kis_dlg_layer_properties.cc
dialogs/kis_dlg_preferences.cc
dialogs/slider_and_spin_box_sync.cpp
dialogs/kis_dlg_blacklist_cleanup.cpp
dialogs/kis_dlg_layer_style.cpp
dialogs/kis_dlg_png_import.cpp
dialogs/kis_dlg_import_image_sequence.cpp
dialogs/kis_delayed_save_dialog.cpp
dialogs/KisSessionManagerDialog.cpp
dialogs/KisNewWindowLayoutDialog.cpp
flake/kis_node_dummies_graph.cpp
flake/kis_dummies_facade_base.cpp
flake/kis_dummies_facade.cpp
flake/kis_node_shapes_graph.cpp
flake/kis_node_shape.cpp
flake/kis_shape_controller.cpp
flake/kis_shape_layer.cc
flake/kis_shape_layer_canvas.cpp
flake/kis_shape_selection.cpp
flake/kis_shape_selection_canvas.cpp
flake/kis_shape_selection_model.cpp
flake/kis_take_all_shapes_command.cpp
brushhud/kis_uniform_paintop_property_widget.cpp
brushhud/kis_brush_hud.cpp
brushhud/kis_round_hud_button.cpp
brushhud/kis_dlg_brush_hud_config.cpp
brushhud/kis_brush_hud_properties_list.cpp
brushhud/kis_brush_hud_properties_config.cpp
kis_aspect_ratio_locker.cpp
kis_autogradient.cc
kis_bookmarked_configurations_editor.cc
kis_bookmarked_configurations_model.cc
kis_bookmarked_filter_configurations_model.cc
KisPaintopPropertiesBase.cpp
kis_canvas_resource_provider.cpp
kis_derived_resources.cpp
kis_categories_mapper.cpp
kis_categorized_list_model.cpp
kis_categorized_item_delegate.cpp
kis_clipboard.cc
kis_config.cc
kis_control_frame.cpp
kis_composite_ops_model.cc
kis_paint_ops_model.cpp
kis_cursor.cc
kis_cursor_cache.cpp
kis_custom_pattern.cc
kis_file_layer.cpp
kis_change_file_layer_command.h
kis_safe_document_loader.cpp
kis_splash_screen.cpp
kis_filter_manager.cc
kis_filters_model.cc
kis_histogram_view.cc
KisImageBarrierLockerWithFeedback.cpp
kis_image_manager.cc
kis_image_view_converter.cpp
kis_import_catcher.cc
kis_layer_manager.cc
kis_mask_manager.cc
kis_mimedata.cpp
kis_node_commands_adapter.cpp
kis_node_manager.cpp
kis_node_juggler_compressed.cpp
kis_node_selection_adapter.cpp
kis_node_insertion_adapter.cpp
KisNodeDisplayModeAdapter.cpp
kis_node_model.cpp
kis_node_filter_proxy_model.cpp
kis_model_index_converter_base.cpp
kis_model_index_converter.cpp
kis_model_index_converter_show_all.cpp
kis_painting_assistant.cc
kis_painting_assistants_decoration.cpp
KisDecorationsManager.cpp
kis_paintop_box.cc
kis_paintop_option.cpp
kis_paintop_options_model.cpp
kis_paintop_settings_widget.cpp
kis_popup_palette.cpp
kis_png_converter.cpp
kis_preference_set_registry.cpp
KisResourceServerProvider.cpp
KisResourceBundleServerProvider.cpp
KisSelectedShapesProxy.cpp
kis_selection_decoration.cc
kis_selection_manager.cc
KisSelectionActionsAdapter.cpp
kis_statusbar.cc
kis_zoom_manager.cc
kis_favorite_resource_manager.cpp
kis_workspace_resource.cpp
kis_action.cpp
kis_action_manager.cpp
KisActionPlugin.cpp
kis_canvas_controls_manager.cpp
kis_tooltip_manager.cpp
kis_multinode_property.cpp
kis_stopgradient_editor.cpp
KisWelcomePageWidget.cpp
kisexiv2/kis_exif_io.cpp
kisexiv2/kis_exiv2.cpp
kisexiv2/kis_iptc_io.cpp
kisexiv2/kis_xmp_io.cpp
opengl/kis_opengl.cpp
opengl/kis_opengl_canvas2.cpp
opengl/kis_opengl_canvas_debugger.cpp
opengl/kis_opengl_image_textures.cpp
opengl/kis_texture_tile.cpp
opengl/kis_opengl_shader_loader.cpp
opengl/kis_texture_tile_info_pool.cpp
opengl/KisOpenGLUpdateInfoBuilder.cpp
kis_fps_decoration.cpp
tool/KisToolChangesTracker.cpp
tool/KisToolChangesTrackerData.cpp
tool/kis_selection_tool_helper.cpp
tool/kis_selection_tool_config_widget_helper.cpp
tool/kis_rectangle_constraint_widget.cpp
tool/kis_shape_tool_helper.cpp
tool/kis_tool.cc
tool/kis_delegated_tool_policies.cpp
tool/kis_tool_freehand.cc
tool/kis_speed_smoother.cpp
tool/kis_painting_information_builder.cpp
tool/kis_stabilized_events_sampler.cpp
tool/kis_tool_freehand_helper.cpp
tool/kis_tool_multihand_helper.cpp
tool/kis_figure_painting_tool_helper.cpp
tool/kis_tool_paint.cc
tool/kis_tool_shape.cc
tool/kis_tool_ellipse_base.cpp
tool/kis_tool_rectangle_base.cpp
tool/kis_tool_polyline_base.cpp
tool/kis_tool_utils.cpp
tool/kis_resources_snapshot.cpp
tool/kis_smoothing_options.cpp
tool/KisStabilizerDelayedPaintHelper.cpp
tool/KisStrokeSpeedMonitor.cpp
tool/strokes/freehand_stroke.cpp
tool/strokes/KisStrokeEfficiencyMeasurer.cpp
tool/strokes/kis_painter_based_stroke_strategy.cpp
tool/strokes/kis_filter_stroke_strategy.cpp
tool/strokes/kis_color_picker_stroke_strategy.cpp
tool/strokes/KisFreehandStrokeInfo.cpp
tool/strokes/KisMaskedFreehandStrokePainter.cpp
tool/strokes/KisMaskingBrushRenderer.cpp
tool/strokes/KisMaskingBrushCompositeOpFactory.cpp
tool/strokes/move_stroke_strategy.cpp
tool/KisSelectionToolFactoryBase.cpp
tool/KisToolPaintFactoryBase.cpp
widgets/kis_cmb_composite.cc
widgets/kis_cmb_contour.cpp
widgets/kis_cmb_gradient.cpp
widgets/kis_paintop_list_widget.cpp
widgets/kis_cmb_idlist.cc
widgets/kis_color_space_selector.cc
widgets/kis_advanced_color_space_selector.cc
widgets/kis_cie_tongue_widget.cpp
widgets/kis_tone_curve_widget.cpp
widgets/kis_curve_widget.cpp
widgets/kis_custom_image_widget.cc
widgets/kis_image_from_clipboard_widget.cpp
widgets/kis_double_widget.cc
widgets/kis_filter_selector_widget.cc
widgets/kis_gradient_chooser.cc
widgets/kis_iconwidget.cc
widgets/kis_mask_widgets.cpp
widgets/kis_meta_data_merge_strategy_chooser_widget.cc
widgets/kis_multi_bool_filter_widget.cc
widgets/kis_multi_double_filter_widget.cc
widgets/kis_multi_integer_filter_widget.cc
widgets/kis_multipliers_double_slider_spinbox.cpp
widgets/kis_paintop_presets_popup.cpp
widgets/kis_tool_options_popup.cpp
widgets/kis_paintop_presets_chooser_popup.cpp
widgets/kis_paintop_presets_save.cpp
widgets/kis_paintop_preset_icon_library.cpp
widgets/kis_pattern_chooser.cc
widgets/kis_preset_chooser.cpp
widgets/kis_progress_widget.cpp
widgets/kis_selection_options.cc
widgets/kis_scratch_pad.cpp
widgets/kis_scratch_pad_event_filter.cpp
widgets/kis_preset_selector_strip.cpp
widgets/kis_slider_spin_box.cpp
widgets/KisSelectionPropertySlider.cpp
widgets/kis_size_group.cpp
widgets/kis_size_group_p.cpp
widgets/kis_wdg_generator.cpp
widgets/kis_workspace_chooser.cpp
widgets/kis_categorized_list_view.cpp
widgets/kis_widget_chooser.cpp
widgets/kis_tool_button.cpp
widgets/kis_floating_message.cpp
widgets/kis_lod_availability_widget.cpp
widgets/kis_color_label_selector_widget.cpp
widgets/kis_color_filter_combo.cpp
widgets/kis_elided_label.cpp
widgets/kis_stopgradient_slider_widget.cpp
widgets/kis_preset_live_preview_view.cpp
widgets/KisScreenColorPicker.cpp
widgets/KoDualColorButton.cpp
widgets/KoStrokeConfigWidget.cpp
widgets/KoFillConfigWidget.cpp
widgets/KisLayerStyleAngleSelector.cpp
widgets/KisMemoryReportButton.cpp
KisPaletteEditor.cpp
dialogs/KisDlgPaletteEditor.cpp
widgets/KisNewsWidget.cpp
widgets/KisGamutMaskToolbar.cpp
utils/kis_document_aware_spin_box_unit_manager.cpp
input/kis_input_manager.cpp
input/kis_input_manager_p.cpp
input/kis_extended_modifiers_mapper.cpp
input/kis_abstract_input_action.cpp
input/kis_tool_invocation_action.cpp
input/kis_pan_action.cpp
input/kis_alternate_invocation_action.cpp
input/kis_rotate_canvas_action.cpp
input/kis_zoom_action.cpp
input/kis_change_frame_action.cpp
input/kis_gamma_exposure_action.cpp
input/kis_show_palette_action.cpp
input/kis_change_primary_setting_action.cpp
input/kis_abstract_shortcut.cpp
input/kis_native_gesture_shortcut.cpp
input/kis_single_action_shortcut.cpp
input/kis_stroke_shortcut.cpp
input/kis_shortcut_matcher.cpp
input/kis_select_layer_action.cpp
input/KisQtWidgetsTweaker.cpp
input/KisInputActionGroup.cpp
operations/kis_operation.cpp
operations/kis_operation_configuration.cpp
operations/kis_operation_registry.cpp
operations/kis_operation_ui_factory.cpp
operations/kis_operation_ui_widget.cpp
operations/kis_filter_selection_operation.cpp
actions/kis_selection_action_factories.cpp
actions/KisPasteActionFactory.cpp
actions/KisTransformToolActivationCommand.cpp
input/kis_touch_shortcut.cpp
kis_document_undo_store.cpp
kis_gui_context_command.cpp
kis_gui_context_command_p.cpp
input/kis_tablet_debugger.cpp
input/kis_input_profile_manager.cpp
input/kis_input_profile.cpp
input/kis_shortcut_configuration.cpp
input/config/kis_input_configuration_page.cpp
input/config/kis_edit_profiles_dialog.cpp
input/config/kis_input_profile_model.cpp
input/config/kis_input_configuration_page_item.cpp
input/config/kis_action_shortcuts_model.cpp
input/config/kis_input_type_delegate.cpp
input/config/kis_input_mode_delegate.cpp
input/config/kis_input_button.cpp
input/config/kis_input_editor_delegate.cpp
input/config/kis_mouse_input_editor.cpp
input/config/kis_wheel_input_editor.cpp
input/config/kis_key_input_editor.cpp
processing/fill_processing_visitor.cpp
kis_asl_layer_style_serializer.cpp
kis_psd_layer_style_resource.cpp
canvas/kis_mirror_axis.cpp
kis_abstract_perspective_grid.cpp
KisApplication.cpp
KisAutoSaveRecoveryDialog.cpp
KisDetailsPane.cpp
KisDocument.cpp
KisCloneDocumentStroke.cpp
KisNodeDelegate.cpp
kis_node_view_visibility_delegate.cpp
KisNodeToolTip.cpp
KisNodeView.cpp
kis_node_view_color_scheme.cpp
KisImportExportFilter.cpp
KisFilterEntry.cpp
KisImportExportManager.cpp
KisImportExportUtils.cpp
kis_async_action_feedback.cpp
KisMainWindow.cpp
KisOpenPane.cpp
KisPart.cpp
KisPrintJob.cpp
KisTemplate.cpp
KisTemplateCreateDia.cpp
KisTemplateGroup.cpp
KisTemplates.cpp
KisTemplatesPane.cpp
KisTemplateTree.cpp
KisUndoActionsUpdateManager.cpp
KisView.cpp
thememanager.cpp
kis_mainwindow_observer.cpp
KisViewManager.cpp
kis_mirror_manager.cpp
qtlockedfile/qtlockedfile.cpp
qtsingleapplication/qtlocalpeer.cpp
qtsingleapplication/qtsingleapplication.cpp
KisResourceBundle.cpp
KisResourceBundleManifest.cpp
kis_md5_generator.cpp
KisApplicationArguments.cpp
KisNetworkAccessManager.cpp
KisMultiFeedRSSModel.cpp
KisRemoteFileFetcher.cpp
KisSaveGroupVisitor.cpp
KisWindowLayoutResource.cpp
KisWindowLayoutManager.cpp
KisSessionResource.cpp
KisReferenceImagesDecoration.cpp
KisReferenceImage.cpp
flake/KisReferenceImagesLayer.cpp
flake/KisReferenceImagesLayer.h
)
if(WIN32)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
input/wintab/kis_tablet_support_win.cpp
input/wintab/kis_screen_size_choice_dialog.cpp
qtlockedfile/qtlockedfile_win.cpp
input/wintab/kis_tablet_support_win8.cpp
opengl/kis_opengl_win.cpp
)
endif()
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
kis_animation_frame_cache.cpp
kis_animation_cache_populator.cpp
KisAsyncAnimationRendererBase.cpp
KisAsyncAnimationCacheRenderer.cpp
KisAsyncAnimationFramesSavingRenderer.cpp
dialogs/KisAsyncAnimationRenderDialogBase.cpp
dialogs/KisAsyncAnimationCacheRenderDialog.cpp
dialogs/KisAsyncAnimationFramesSaveDialog.cpp
canvas/kis_animation_player.cpp
kis_animation_importer.cpp
KisSyncedAudioPlayback.cpp
KisFrameDataSerializer.cpp
KisFrameCacheStore.cpp
KisFrameCacheSwapper.cpp
KisAbstractFrameCacheSwapper.cpp
KisInMemoryFrameCacheSwapper.cpp
input/wintab/drawpile_tablettester/tablettester.cpp
input/wintab/drawpile_tablettester/tablettest.cpp
)
if (UNIX)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
qtlockedfile/qtlockedfile_unix.cpp
)
if(NOT USE_QT_XCB)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
input/wintab/kis_tablet_support.cpp
)
endif()
if(NOT APPLE AND NOT USE_QT_XCB)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
input/wintab/qxcbconnection_xi2.cpp
input/wintab/qxcbconnection.cpp
input/wintab/kis_xi2_event_filter.cpp
)
endif()
endif()
if(APPLE)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
osx.mm
)
endif()
ki18n_wrap_ui(kritaui_LIB_SRCS
widgets/KoFillConfigWidget.ui
widgets/KoStrokeConfigWidget.ui
forms/wdgdlgpngimport.ui
forms/wdgfullscreensettings.ui
forms/wdgautogradient.ui
forms/wdggeneralsettings.ui
forms/wdgperformancesettings.ui
forms/wdggenerators.ui
forms/wdgbookmarkedconfigurationseditor.ui
forms/wdgapplyprofile.ui
forms/wdgcustompattern.ui
forms/wdglayerproperties.ui
forms/wdgcolorsettings.ui
forms/wdgtabletsettings.ui
forms/wdgcolorspaceselector.ui
forms/wdgcolorspaceselectoradvanced.ui
forms/wdgdisplaysettings.ui
forms/kis_previewwidgetbase.ui
forms/kis_matrix_widget.ui
forms/wdgselectionoptions.ui
forms/wdggeometryoptions.ui
forms/wdgnewimage.ui
forms/wdgimageproperties.ui
forms/wdgmaskfromselection.ui
forms/wdgmasksource.ui
forms/wdgfilterdialog.ui
forms/wdgmetadatamergestrategychooser.ui
forms/wdgpaintoppresets.ui
forms/wdgpaintopsettings.ui
forms/wdgdlggeneratorlayer.ui
forms/wdgdlgfilelayer.ui
forms/wdgfilterselector.ui
forms/wdgfilternodecreation.ui
forms/wdgmultipliersdoublesliderspinbox.ui
forms/wdgnodequerypatheditor.ui
forms/wdgpresetselectorstrip.ui
forms/wdgsavebrushpreset.ui
forms/wdgpreseticonlibrary.ui
forms/wdgdlgblacklistcleanup.ui
forms/wdgrectangleconstraints.ui
forms/wdgimportimagesequence.ui
forms/wdgstrokeselectionproperties.ui
forms/KisDetailsPaneBase.ui
forms/KisOpenPaneBase.ui
forms/wdgstopgradienteditor.ui
forms/wdgsessionmanager.ui
forms/wdgnewwindowlayout.ui
forms/KisWelcomePage.ui
forms/WdgDlgPaletteEditor.ui
forms/KisNewsPage.ui
forms/wdgGamutMaskToolbar.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
layerstyles/wdgKisLayerStyleAngleSelector.ui
wdgsplash.ui
input/wintab/kis_screen_size_choice_dialog.ui
input/wintab/drawpile_tablettester/tablettest.ui
)
QT5_WRAP_CPP(kritaui_HEADERS_MOC KisNodePropertyAction_p.h)
add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} )
generate_export_header(kritaui BASE_NAME kritaui)
target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network
kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} LibExiv2::LibExiv2
)
if (HAVE_QT_MULTIMEDIA)
target_link_libraries(kritaui Qt5::Multimedia)
endif()
if (NOT WIN32 AND NOT APPLE)
target_link_libraries(kritaui ${X11_X11_LIB}
${X11_Xinput_LIB}
${XCB_LIBRARIES})
endif()
if(APPLE)
target_link_libraries(kritaui ${FOUNDATION_LIBRARY})
target_link_libraries(kritaui ${APPKIT_LIBRARY})
endif ()
target_link_libraries(kritaui ${OPENEXR_LIBRARIES})
# Add VSync disable workaround
if(NOT WIN32 AND NOT APPLE)
target_link_libraries(kritaui ${CMAKE_DL_LIBS} Qt5::X11Extras)
endif()
if(X11_FOUND)
target_link_libraries(kritaui Qt5::X11Extras ${X11_LIBRARIES})
endif()
target_include_directories(kritaui
PUBLIC
$
$
$
$
$
$
$
)
set_target_properties(kritaui
PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritaui ${INSTALL_TARGETS_DEFAULT_ARGS})
if (APPLE)
install(FILES osx.stylesheet DESTINATION ${DATA_INSTALL_DIR}/krita)
endif ()
diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp
index 03e3c2bf9d..71f146fabe 100644
--- a/libs/ui/KisDocument.cpp
+++ b/libs/ui/KisDocument.cpp
@@ -1,1934 +1,1955 @@
/* This file is part of the Krita project
*
* Copyright (C) 2014 Boudewijn Rempt
*
* 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 "KisMainWindow.h" // XXX: remove
#include // XXX: remove
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// Krita Image
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_layer_utils.h"
// Local
#include "KisViewManager.h"
#include "kis_clipboard.h"
#include "widgets/kis_custom_image_widget.h"
#include "canvas/kis_canvas2.h"
#include "flake/kis_shape_controller.h"
#include "kis_statusbar.h"
#include "widgets/kis_progress_widget.h"
#include "kis_canvas_resource_provider.h"
#include "KisResourceServerProvider.h"
#include "kis_node_manager.h"
#include "KisPart.h"
#include "KisApplication.h"
#include "KisDocument.h"
#include "KisImportExportManager.h"
#include "KisView.h"
#include "kis_grid_config.h"
#include "kis_guides_config.h"
#include "kis_image_barrier_lock_adapter.h"
#include "KisReferenceImagesLayer.h"
#include
#include "kis_config_notifier.h"
#include "kis_async_action_feedback.h"
#include "KisCloneDocumentStroke.h"
+#include
+
// Define the protocol used here for embedded documents' URL
// This used to "store" but QUrl didn't like it,
// so let's simply make it "tar" !
#define STORE_PROTOCOL "tar"
// The internal path is a hack to make QUrl happy and for document children
#define INTERNAL_PROTOCOL "intern"
#define INTERNAL_PREFIX "intern:/"
// Warning, keep it sync in koStore.cc
#include
using namespace std;
namespace {
constexpr int errorMessageTimeout = 5000;
constexpr int successMessageTimeout = 1000;
}
/**********************************************************
*
* KisDocument
*
**********************************************************/
//static
QString KisDocument::newObjectName()
{
static int s_docIFNumber = 0;
QString name; name.setNum(s_docIFNumber++); name.prepend("document_");
return name;
}
class UndoStack : public KUndo2Stack
{
public:
UndoStack(KisDocument *doc)
: KUndo2Stack(doc),
m_doc(doc)
{
}
void setIndex(int idx) override {
KisImageWSP image = this->image();
image->requestStrokeCancellation();
if(image->tryBarrierLock()) {
KUndo2Stack::setIndex(idx);
image->unlock();
}
}
void notifySetIndexChangedOneCommand() override {
KisImageWSP image = this->image();
image->unlock();
/**
* Some very weird commands may emit blocking signals to
* the GUI (e.g. KisGuiContextCommand). Here is the best thing
* we can do to avoid the deadlock
*/
while(!image->tryBarrierLock()) {
QApplication::processEvents();
}
}
void undo() override {
KisImageWSP image = this->image();
image->requestUndoDuringStroke();
if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) {
return;
}
if(image->tryBarrierLock()) {
KUndo2Stack::undo();
image->unlock();
}
}
void redo() override {
KisImageWSP image = this->image();
if(image->tryBarrierLock()) {
KUndo2Stack::redo();
image->unlock();
}
}
private:
KisImageWSP image() {
KisImageWSP currentImage = m_doc->image();
Q_ASSERT(currentImage);
return currentImage;
}
private:
KisDocument *m_doc;
};
class Q_DECL_HIDDEN KisDocument::Private
{
public:
Private(KisDocument *q)
: docInfo(new KoDocumentInfo(q)) // deleted by QObject
, importExportManager(new KisImportExportManager(q)) // deleted manually
, autoSaveTimer(new QTimer(q))
, undoStack(new UndoStack(q)) // deleted by QObject
, m_bAutoDetectedMime(false)
, modified(false)
, readwrite(true)
, firstMod(QDateTime::currentDateTime())
, lastMod(firstMod)
, nserver(new KisNameServer(1))
, imageIdleWatcher(2000 /*ms*/)
, globalAssistantsColor(KisConfig(true).defaultAssistantsColor())
, savingLock(&savingMutex)
, batchMode(false)
{
if (QLocale().measurementSystem() == QLocale::ImperialSystem) {
unit = KoUnit::Inch;
} else {
unit = KoUnit::Centimeter;
}
}
Private(const Private &rhs, KisDocument *q)
: docInfo(new KoDocumentInfo(*rhs.docInfo, q))
, unit(rhs.unit)
, importExportManager(new KisImportExportManager(q))
, mimeType(rhs.mimeType)
, outputMimeType(rhs.outputMimeType)
, autoSaveTimer(new QTimer(q))
, undoStack(new UndoStack(q))
, guidesConfig(rhs.guidesConfig)
+ , mirrorAxisConfig(rhs.mirrorAxisConfig)
, m_bAutoDetectedMime(rhs.m_bAutoDetectedMime)
, m_url(rhs.m_url)
, m_file(rhs.m_file)
, modified(rhs.modified)
, readwrite(rhs.readwrite)
, firstMod(rhs.firstMod)
, lastMod(rhs.lastMod)
, nserver(new KisNameServer(*rhs.nserver))
, preActivatedNode(0) // the node is from another hierarchy!
, imageIdleWatcher(2000 /*ms*/)
, assistants(rhs.assistants) // WARNING: assistants should not store pointers to the document!
, globalAssistantsColor(rhs.globalAssistantsColor)
, paletteList(rhs.paletteList)
, gridConfig(rhs.gridConfig)
, savingLock(&savingMutex)
, batchMode(rhs.batchMode)
{
// TODO: clone assistants
}
~Private() {
// Don't delete m_d->shapeController because it's in a QObject hierarchy.
delete nserver;
}
KoDocumentInfo *docInfo = 0;
KoUnit unit;
KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options]
QByteArray mimeType; // The actual mimetype of the document
QByteArray outputMimeType; // The mimetype to use when saving
QTimer *autoSaveTimer;
QString lastErrorMessage; // see openFile()
QString lastWarningMessage;
int autoSaveDelay = 300; // in seconds, 0 to disable.
bool modifiedAfterAutosave = false;
bool isAutosaving = false;
bool disregardAutosaveFailure = false;
int autoSaveFailureCount = 0;
KUndo2Stack *undoStack = 0;
KisGuidesConfig guidesConfig;
+ KisMirrorAxisConfig mirrorAxisConfig;
bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself
QUrl m_url; // local url - the one displayed to the user.
QString m_file; // Local file - the only one the part implementation should deal with.
QMutex savingMutex;
bool modified = false;
bool readwrite = false;
QDateTime firstMod;
QDateTime lastMod;
KisNameServer *nserver;
KisImageSP image;
KisImageSP savingImage;
KisNodeWSP preActivatedNode;
KisShapeController* shapeController = 0;
KoShapeController* koShapeController = 0;
KisIdleWatcher imageIdleWatcher;
QScopedPointer imageIdleConnection;
QList assistants;
QColor globalAssistantsColor;
KisSharedPtr referenceImagesLayer;
QList paletteList;
KisGridConfig gridConfig;
StdLockableWrapper savingLock;
bool modifiedWhileSaving = false;
QScopedPointer backgroundSaveDocument;
QPointer savingUpdater;
QFuture childSavingFuture;
KritaUtils::ExportFileJob backgroundSaveJob;
bool isRecovered = false;
bool batchMode { false };
void setImageAndInitIdleWatcher(KisImageSP _image) {
image = _image;
imageIdleWatcher.setTrackedImage(image);
if (image) {
imageIdleConnection.reset(
new KisSignalAutoConnection(
&imageIdleWatcher, SIGNAL(startedIdleMode()),
image.data(), SLOT(explicitRegenerateLevelOfDetail())));
}
}
class StrippedSafeSavingLocker;
};
class KisDocument::Private::StrippedSafeSavingLocker {
public:
StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image)
: m_locked(false)
, m_image(image)
, m_savingLock(savingMutex)
, m_imageLock(image, true)
{
/**
* Initial try to lock both objects. Locking the image guards
* us from any image composition threads running in the
* background, while savingMutex guards us from entering the
* saving code twice by autosave and main threads.
*
* Since we are trying to lock multiple objects, so we should
* do it in a safe manner.
*/
m_locked = std::try_lock(m_imageLock, m_savingLock) < 0;
if (!m_locked) {
m_image->requestStrokeEnd();
QApplication::processEvents();
// one more try...
m_locked = std::try_lock(m_imageLock, m_savingLock) < 0;
}
}
~StrippedSafeSavingLocker() {
if (m_locked) {
m_imageLock.unlock();
m_savingLock.unlock();
}
}
bool successfullyLocked() const {
return m_locked;
}
private:
bool m_locked;
KisImageSP m_image;
StdLockableWrapper m_savingLock;
KisImageBarrierLockAdapter m_imageLock;
};
KisDocument::KisDocument()
: d(new Private(this))
{
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
setObjectName(newObjectName());
// preload the krita resources
KisResourceServerProvider::instance();
d->shapeController = new KisShapeController(this, d->nserver),
d->koShapeController = new KoShapeController(0, d->shapeController),
slotConfigChanged();
}
KisDocument::KisDocument(const KisDocument &rhs)
: QObject(),
d(new Private(*rhs.d, this))
{
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
setObjectName(rhs.objectName());
d->shapeController = new KisShapeController(this, d->nserver),
d->koShapeController = new KoShapeController(0, d->shapeController),
slotConfigChanged();
// clone the image with keeping the GUIDs of the layers intact
// NOTE: we expect the image to be locked!
setCurrentImage(rhs.image()->clone(true), false);
if (rhs.d->preActivatedNode) {
// since we clone uuid's, we can use them for lacating new
// nodes. Otherwise we would need to use findSymmetricClone()
d->preActivatedNode =
KisLayerUtils::findNodeByUuid(d->image->root(), rhs.d->preActivatedNode->uuid());
}
}
KisDocument::~KisDocument()
{
// wait until all the pending operations are in progress
waitForSavingToComplete();
/**
* Push a timebomb, which will try to release the memory after
* the document has been deleted
*/
KisPaintDevice::createMemoryReleaseObject()->deleteLater();
d->autoSaveTimer->disconnect(this);
d->autoSaveTimer->stop();
delete d->importExportManager;
// Despite being QObject they needs to be deleted before the image
delete d->shapeController;
delete d->koShapeController;
if (d->image) {
d->image->notifyAboutToBeDeleted();
/**
* WARNING: We should wait for all the internal image jobs to
* finish before entering KisImage's destructor. The problem is,
* while execution of KisImage::~KisImage, all the weak shared
* pointers pointing to the image enter an inconsistent
* state(!). The shared counter is already zero and destruction
* has started, but the weak reference doesn't know about it,
* because KisShared::~KisShared hasn't been executed yet. So all
* the threads running in background and having weak pointers will
* enter the KisImage's destructor as well.
*/
d->image->requestStrokeCancellation();
d->image->waitForDone();
// clear undo commands that can still point to the image
d->undoStack->clear();
d->image->waitForDone();
KisImageWSP sanityCheckPointer = d->image;
Q_UNUSED(sanityCheckPointer);
// The following line trigger the deletion of the image
d->image.clear();
// check if the image has actually been deleted
KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid());
}
delete d;
}
bool KisDocument::reload()
{
// XXX: reimplement!
return false;
}
KisDocument *KisDocument::clone()
{
return new KisDocument(*this);
}
bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration)
{
QFileInfo filePathInfo(job.filePath);
if (filePathInfo.exists() && !filePathInfo.isWritable()) {
slotCompleteSavingDocument(job,
KisImportExportFilter::CreationError,
i18n("%1 cannot be written to. Please save under a different name.", job.filePath));
return false;
}
KisConfig cfg(true);
if (cfg.backupFile() && filePathInfo.exists()) {
QString backupDir;
switch(cfg.readEntry("backupfilelocation", 0)) {
case 1:
backupDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
break;
case 2:
backupDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
break;
default:
// Do nothing: the empty string is user file location
break;
}
int numOfBackupsKept = cfg.readEntry("numberofbackupfiles", 1);
QString suffix = cfg.readEntry("backupfilesuffix", "~");
if (numOfBackupsKept == 1) {
KBackup::simpleBackupFile(job.filePath, backupDir, suffix);
}
else if (numOfBackupsKept > 2) {
KBackup::numberedBackupFile(job.filePath, backupDir, suffix, numOfBackupsKept);
}
}
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false);
const QString actionName =
job.flags & KritaUtils::SaveIsExporting ?
i18n("Exporting Document...") :
i18n("Saving Document...");
bool started =
initiateSavingInBackground(actionName,
this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)),
job, exportConfiguration);
if (!started) {
emit canceled(QString());
}
return started;
}
bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
using namespace KritaUtils;
SaveFlags flags = SaveIsExporting;
if (showWarnings) {
flags |= SaveShowWarnings;
}
KisUsageLogger::log(QString("Exporting Document: %1 as %2. %3 * %4 pixels, %5 layers, %6 frames, %7 framerate. Export configuration: %8")
.arg(url.toLocalFile())
.arg(QString::fromLatin1(mimeType))
.arg(d->image->width())
.arg(d->image->height())
.arg(d->image->nlayers())
.arg(d->image->animationInterface()->totalLength())
.arg(d->image->animationInterface()->framerate())
.arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration"));
return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(),
mimeType,
flags),
exportConfiguration);
}
bool KisDocument::saveAs(const QUrl &_url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
using namespace KritaUtils;
KisUsageLogger::log(QString("Saving Document %9 as %1 (mime: %2). %3 * %4 pixels, %5 layers. %6 frames, %7 framerate. Export configuration: %8")
.arg(_url.toLocalFile())
.arg(QString::fromLatin1(mimeType))
.arg(d->image->width())
.arg(d->image->height())
.arg(d->image->nlayers())
.arg(d->image->animationInterface()->totalLength())
.arg(d->image->animationInterface()->framerate())
.arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration")
.arg(url().toLocalFile()));
return exportDocumentImpl(ExportFileJob(_url.toLocalFile(),
mimeType,
showWarnings ? SaveShowWarnings : SaveNone),
exportConfiguration);
}
bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
return saveAs(url(), mimeType(), showWarnings, exportConfiguration);
}
QByteArray KisDocument::serializeToNativeByteArray()
{
QByteArray byteArray;
QBuffer buffer(&byteArray);
QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export));
filter->setBatchMode(true);
filter->setMimeType(nativeFormatMimeType());
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return byteArray;
}
d->savingImage = d->image;
if (filter->convert(this, &buffer) != KisImportExportFilter::OK) {
qWarning() << "serializeToByteArray():: Could not export to our native format";
}
return byteArray;
}
void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
{
if (status == KisImportExportFilter::UserCancelled)
return;
const QString fileName = QFileInfo(job.filePath).fileName();
if (status != KisImportExportFilter::OK) {
emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
"Error during saving %1: %2",
fileName,
exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout);
if (!fileBatchMode()) {
const QString filePath = job.filePath;
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, exportErrorToUserMessage(status, errorMessage)));
}
} else {
if (!(job.flags & KritaUtils::SaveIsExporting)) {
const QString existingAutoSaveBaseName = localFilePath();
const bool wasRecovered = isRecovered();
setUrl(QUrl::fromLocalFile(job.filePath));
setLocalFilePath(job.filePath);
setMimeType(job.mimeType);
updateEditingTime(true);
if (!d->modifiedWhileSaving) {
/**
* If undo stack is already clean/empty, it doesn't emit any
* signals, so we might forget update document modified state
* (which was set, e.g. while recovering an autosave file)
*/
if (d->undoStack->isClean()) {
setModified(false);
} else {
d->undoStack->setClean();
}
}
setRecovered(false);
removeAutoSaveFiles(existingAutoSaveBaseName, wasRecovered);
}
emit completed();
emit sigSavingFinished();
emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout);
}
}
QByteArray KisDocument::mimeType() const
{
return d->mimeType;
}
void KisDocument::setMimeType(const QByteArray & mimeType)
{
d->mimeType = mimeType;
}
bool KisDocument::fileBatchMode() const
{
return d->batchMode;
}
void KisDocument::setFileBatchMode(const bool batchMode)
{
d->batchMode = batchMode;
}
KisDocument* KisDocument::lockAndCloneForSaving()
{
// force update of all the asynchronous nodes before cloning
QApplication::processEvents();
KisLayerUtils::forceAllDelayedNodesUpdate(d->image->root());
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window) {
if (window->viewManager()) {
if (!window->viewManager()->blockUntilOperationsFinished(d->image)) {
return 0;
}
}
}
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return 0;
}
return new KisDocument(*this);
}
bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration)
{
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return false;
}
d->savingImage = d->image;
const QString fileName = url.toLocalFile();
KisImportExportFilter::ConversionStatus status =
d->importExportManager->
exportDocument(fileName, fileName, mimeType, false, exportConfiguration);
d->savingImage = 0;
return status == KisImportExportFilter::OK;
}
bool KisDocument::initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration)
{
return initiateSavingInBackground(actionName, receiverObject, receiverMethod,
job, exportConfiguration, std::unique_ptr());
}
bool KisDocument::initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration,
std::unique_ptr &&optionalClonedDocument)
{
KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false);
QScopedPointer clonedDocument;
if (!optionalClonedDocument) {
clonedDocument.reset(lockAndCloneForSaving());
} else {
clonedDocument.reset(optionalClonedDocument.release());
}
// we block saving until the current saving is finished!
if (!clonedDocument || !d->savingMutex.tryLock()) {
return false;
}
KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false);
KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false);
d->backgroundSaveDocument.reset(clonedDocument.take());
d->backgroundSaveJob = job;
d->modifiedWhileSaving = false;
if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
d->backgroundSaveDocument->d->isAutosaving = true;
}
connect(d->backgroundSaveDocument.data(),
SIGNAL(sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus,QString)),
this,
SLOT(slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus,QString)));
connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)),
receiverObject, receiverMethod, Qt::UniqueConnection);
bool started =
d->backgroundSaveDocument->startExportInBackground(actionName,
job.filePath,
job.filePath,
job.mimeType,
job.flags & KritaUtils::SaveShowWarnings,
exportConfiguration);
if (!started) {
// the state should have been deinitialized in slotChildCompletedSavingInBackground()
KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) {
d->backgroundSaveDocument.take()->deleteLater();
d->savingMutex.unlock();
d->backgroundSaveJob = KritaUtils::ExportFileJob();
}
}
return started;
}
void KisDocument::slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
{
KIS_SAFE_ASSERT_RECOVER(!d->savingMutex.tryLock()) {
d->savingMutex.unlock();
return;
}
KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument);
if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
d->backgroundSaveDocument->d->isAutosaving = false;
}
d->backgroundSaveDocument.take()->deleteLater();
d->savingMutex.unlock();
KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid());
const KritaUtils::ExportFileJob job = d->backgroundSaveJob;
d->backgroundSaveJob = KritaUtils::ExportFileJob();
KisUsageLogger::log(QString("Completed saving %1 (mime: %2). Result: %3")
.arg(job.filePath)
.arg(QString::fromLatin1(job.mimeType))
.arg(status != KisImportExportFilter::OK ? exportErrorToUserMessage(status, errorMessage) : "OK"));
emit sigCompleteBackgroundSaving(job, status, errorMessage);
}
void KisDocument::slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument)
{
if (!d->modified || !d->modifiedAfterAutosave) return;
const QString autoSaveFileName = generateAutoSaveFileName(localFilePath());
emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout);
const bool hadClonedDocument = bool(optionalClonedDocument);
bool started = false;
if (d->image->isIdle() || hadClonedDocument) {
started = initiateSavingInBackground(i18n("Autosaving..."),
this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)),
KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode),
0,
std::move(optionalClonedDocument));
} else {
emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout);
}
if (!started && !hadClonedDocument && d->autoSaveFailureCount >= 3) {
KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this);
connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)),
this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)),
Qt::BlockingQueuedConnection);
KisStrokeId strokeId = d->image->startStroke(stroke);
d->image->endStroke(strokeId);
setInfiniteAutoSaveInterval();
} else if (!started) {
setEmergencyAutoSaveInterval();
} else {
d->modifiedAfterAutosave = false;
}
}
void KisDocument::slotAutoSave()
{
slotAutoSaveImpl(std::unique_ptr());
}
void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument)
{
slotAutoSaveImpl(std::unique_ptr(clonedDocument));
}
void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
{
Q_UNUSED(job);
const QString fileName = QFileInfo(job.filePath).fileName();
if (status != KisImportExportFilter::OK) {
setEmergencyAutoSaveInterval();
emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
"Error during autosaving %1: %2",
fileName,
exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout);
} else {
KisConfig cfg(true);
d->autoSaveDelay = cfg.autoSaveInterval();
if (!d->modifiedWhileSaving) {
d->autoSaveTimer->stop(); // until the next change
d->autoSaveFailureCount = 0;
} else {
setNormalAutoSaveInterval();
}
emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout);
}
}
bool KisDocument::startExportInBackground(const QString &actionName,
const QString &location,
const QString &realLocation,
const QByteArray &mimeType,
bool showWarnings,
KisPropertiesConfigurationSP exportConfiguration)
{
d->savingImage = d->image;
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window) {
if (window->viewManager()) {
d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName);
d->importExportManager->setUpdater(d->savingUpdater);
}
}
KisImportExportFilter::ConversionStatus initializationStatus;
d->childSavingFuture =
d->importExportManager->exportDocumentAsyc(location,
realLocation,
mimeType,
initializationStatus,
showWarnings,
exportConfiguration);
if (initializationStatus != KisImportExportFilter::ConversionStatus::OK) {
if (d->savingUpdater) {
d->savingUpdater->cancel();
}
d->savingImage.clear();
emit sigBackgroundSavingFinished(initializationStatus, this->errorMessage());
return false;
}
typedef QFutureWatcher StatusWatcher;
StatusWatcher *watcher = new StatusWatcher();
watcher->setFuture(d->childSavingFuture);
connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground()));
connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater()));
return true;
}
void KisDocument::finishExportInBackground()
{
KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) {
emit sigBackgroundSavingFinished(KisImportExportFilter::InternalError, "");
return;
}
KisImportExportFilter::ConversionStatus status =
d->childSavingFuture.result();
const QString errorMessage = this->errorMessage();
d->savingImage.clear();
d->childSavingFuture = QFuture();
d->lastErrorMessage.clear();
if (d->savingUpdater) {
d->savingUpdater->setProgress(100);
}
emit sigBackgroundSavingFinished(status, errorMessage);
}
void KisDocument::setReadWrite(bool readwrite)
{
d->readwrite = readwrite;
setNormalAutoSaveInterval();
Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) {
mainWindow->setReadWrite(readwrite);
}
}
void KisDocument::setAutoSaveDelay(int delay)
{
if (isReadWrite() && delay > 0) {
d->autoSaveTimer->start(delay * 1000);
} else {
d->autoSaveTimer->stop();
}
}
void KisDocument::setNormalAutoSaveInterval()
{
setAutoSaveDelay(d->autoSaveDelay);
d->autoSaveFailureCount = 0;
}
void KisDocument::setEmergencyAutoSaveInterval()
{
const int emergencyAutoSaveInterval = 10; /* sec */
setAutoSaveDelay(emergencyAutoSaveInterval);
d->autoSaveFailureCount++;
}
void KisDocument::setInfiniteAutoSaveInterval()
{
setAutoSaveDelay(-1);
}
KoDocumentInfo *KisDocument::documentInfo() const
{
return d->docInfo;
}
bool KisDocument::isModified() const
{
return d->modified;
}
QPixmap KisDocument::generatePreview(const QSize& size)
{
KisImageSP image = d->image;
if (d->savingImage) image = d->savingImage;
if (image) {
QRect bounds = image->bounds();
QSize newSize = bounds.size();
newSize.scale(size, Qt::KeepAspectRatio);
QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0));
if (px.size() == QSize(0,0)) {
px = QPixmap(newSize);
QPainter gc(&px);
QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5));
gc.fillRect(px.rect(), checkBrush);
gc.end();
}
return px;
}
return QPixmap(size);
}
QString KisDocument::generateAutoSaveFileName(const QString & path) const
{
QString retval;
// Using the extension allows to avoid relying on the mime magic when opening
const QString extension (".kra");
QString prefix = KisConfig(true).readEntry("autosavefileshidden") ? QString(".") : QString();
QRegularExpression autosavePattern1("^\\..+-autosave.kra$");
QRegularExpression autosavePattern2("^.+-autosave.kra$");
QFileInfo fi(path);
QString dir = fi.absolutePath();
QString filename = fi.fileName();
if (path.isEmpty() || autosavePattern1.match(filename).hasMatch() || autosavePattern2.match(filename).hasMatch()) {
// Never saved?
#ifdef Q_OS_WIN
// On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921)
retval = QString("%1%2%7%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix);
#else
// On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file
retval = QString("%1%2%7%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix);
#endif
} else {
retval = QString("%1%2%5%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension).arg(prefix);
}
//qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval;
return retval;
}
bool KisDocument::importDocument(const QUrl &_url)
{
bool ret;
dbgUI << "url=" << _url.url();
// open...
ret = openUrl(_url);
// reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a
// File --> Import
if (ret) {
dbgUI << "success, resetting url";
resetURL();
setTitleModified();
}
return ret;
}
bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags)
{
if (!_url.isLocalFile()) {
return false;
}
dbgUI << "url=" << _url.url();
d->lastErrorMessage.clear();
// Reimplemented, to add a check for autosave files and to improve error reporting
if (!_url.isValid()) {
d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ?
return false;
}
QUrl url(_url);
bool autosaveOpened = false;
if (url.isLocalFile() && !fileBatchMode()) {
QString file = url.toLocalFile();
QString asf = generateAutoSaveFileName(file);
if (QFile::exists(asf)) {
KisApplication *kisApp = static_cast(qApp);
kisApp->hideSplashScreen();
//dbgUI <<"asf=" << asf;
// ## TODO compare timestamps ?
int res = QMessageBox::warning(0,
i18nc("@title:window", "Krita"),
i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes);
switch (res) {
case QMessageBox::Yes :
url.setPath(asf);
autosaveOpened = true;
break;
case QMessageBox::No :
QFile::remove(asf);
break;
default: // Cancel
return false;
}
}
}
bool ret = openUrlInternal(url);
if (autosaveOpened || flags & RecoveryFile) {
setReadWrite(true); // enable save button
setModified(true);
setRecovered(true);
}
else {
if (!(flags & DontAddToRecent)) {
KisPart::instance()->addRecentURLToAllMainWindows(_url);
}
if (ret) {
// Detect readonly local-files; remote files are assumed to be writable
QFileInfo fi(url.toLocalFile());
setReadWrite(fi.isWritable());
}
setRecovered(false);
}
return ret;
}
class DlgLoadMessages : public KoDialog {
public:
DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) {
setWindowTitle(title);
setWindowIcon(KisIconUtils::loadIcon("warning"));
QWidget *page = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(page);
QHBoxLayout *hlayout = new QHBoxLayout();
QLabel *labelWarning= new QLabel();
labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32));
hlayout->addWidget(labelWarning);
hlayout->addWidget(new QLabel(message));
layout->addLayout(hlayout);
QTextBrowser *browser = new QTextBrowser();
QString warning = "";
if (warnings.size() == 1) {
warning += " Reason:
";
}
else {
warning += " Reasons:
";
}
warning += "";
Q_FOREACH(const QString &w, warnings) {
warning += "\n- " + w + "
";
}
warning += "
";
browser->setHtml(warning);
browser->setMinimumHeight(200);
browser->setMinimumWidth(400);
layout->addWidget(browser);
setMainWidget(page);
setButtons(KoDialog::Ok);
resize(minimumSize());
}
};
bool KisDocument::openFile()
{
//dbgUI <<"for" << localFilePath();
if (!QFile::exists(localFilePath())) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath()));
return false;
}
QString filename = localFilePath();
QString typeName = mimeType();
if (typeName.isEmpty()) {
typeName = KisMimeDatabase::mimeTypeForFile(filename);
}
//qDebug() << "mimetypes 4:" << typeName;
// Allow to open backup files, don't keep the mimetype application/x-trash.
if (typeName == "application/x-trash") {
QString path = filename;
while (path.length() > 0) {
path.chop(1);
typeName = KisMimeDatabase::mimeTypeForFile(path);
//qDebug() << "\t" << path << typeName;
if (!typeName.isEmpty()) {
break;
}
}
//qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName;
}
dbgUI << localFilePath() << "type:" << typeName;
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window && window->viewManager()) {
KoUpdaterPtr updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document"));
d->importExportManager->setUpdater(updater);
}
KisImportExportFilter::ConversionStatus status;
status = d->importExportManager->importDocument(localFilePath(), typeName);
if (status != KisImportExportFilter::OK) {
QString msg = KisImportExportFilter::conversionStatusString(status);
if (!msg.isEmpty()) {
DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()),
errorMessage().split("\n") + warningMessage().split("\n"));
dlg.exec();
}
return false;
}
else if (!warningMessage().isEmpty()) {
DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
i18n("There were problems opening %1.", prettyPathOrUrl()),
warningMessage().split("\n"));
dlg.exec();
setUrl(QUrl());
}
setMimeTypeAfterLoading(typeName);
emit sigLoadingFinished();
undoStack()->clear();
return true;
}
// shared between openFile and koMainWindow's "create new empty document" code
void KisDocument::setMimeTypeAfterLoading(const QString& mimeType)
{
d->mimeType = mimeType.toLatin1();
d->outputMimeType = d->mimeType;
}
bool KisDocument::loadNativeFormat(const QString & file_)
{
return openUrl(QUrl::fromLocalFile(file_));
}
void KisDocument::setModified(bool mod)
{
if (mod) {
updateEditingTime(false);
}
if (d->isAutosaving) // ignore setModified calls due to autosaving
return;
if ( !d->readwrite && d->modified ) {
errKrita << "Can't set a read-only document to 'modified' !" << endl;
return;
}
//dbgUI<<" url:" << url.path();
//dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod)));
d->firstMod = now;
} else if (firstModDelta > 60 || forceStoreElapsed) {
d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta));
d->firstMod = now;
}
d->lastMod = now;
}
QString KisDocument::prettyPathOrUrl() const
{
QString _url(url().toDisplayString());
#ifdef Q_OS_WIN
if (url().isLocalFile()) {
_url = QDir::toNativeSeparators(_url);
}
#endif
return _url;
}
// Get caption from document info (title(), in about page)
QString KisDocument::caption() const
{
QString c;
const QString _url(url().fileName());
// if URL is empty...it is probably an unsaved file
if (_url.isEmpty()) {
c = " [" + i18n("Not Saved") + "] ";
} else {
c = _url; // Fall back to document URL
}
return c;
}
void KisDocument::setTitleModified()
{
emit titleModified(caption(), isModified());
}
QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const
{
return createDomDocument("krita", tagName, version);
}
//static
QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version)
{
QDomImplementation impl;
QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version);
QDomDocumentType dtype = impl.createDocumentType(tagName,
QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version),
url);
// The namespace URN doesn't need to include the version number.
QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName);
QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype);
doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement());
return doc;
}
bool KisDocument::isNativeFormat(const QByteArray& mimetype) const
{
if (mimetype == nativeFormatMimeType())
return true;
return extraNativeMimeTypes().contains(mimetype);
}
void KisDocument::setErrorMessage(const QString& errMsg)
{
d->lastErrorMessage = errMsg;
}
QString KisDocument::errorMessage() const
{
return d->lastErrorMessage;
}
void KisDocument::setWarningMessage(const QString& warningMsg)
{
d->lastWarningMessage = warningMsg;
}
QString KisDocument::warningMessage() const
{
return d->lastWarningMessage;
}
void KisDocument::removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered)
{
//qDebug() << "removeAutoSaveFiles";
// Eliminate any auto-save file
QString asf = generateAutoSaveFileName(autosaveBaseName); // the one in the current dir
//qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf);
if (QFile::exists(asf)) {
//qDebug() << "\tremoving autosavefile" << asf;
QFile::remove(asf);
}
asf = generateAutoSaveFileName(QString()); // and the one in $HOME
//qDebug() << "Autsavefile in $home" << asf;
if (QFile::exists(asf)) {
//qDebug() << "\tremoving autsavefile 2" << asf;
QFile::remove(asf);
}
QList expressions;
expressions << QRegularExpression("^\\..+-autosave.kra$")
<< QRegularExpression("^.+-autosave.kra$");
Q_FOREACH(const QRegularExpression &rex, expressions) {
if (wasRecovered &&
!autosaveBaseName.isEmpty() &&
rex.match(QFileInfo(autosaveBaseName).fileName()).hasMatch() &&
QFile::exists(autosaveBaseName)) {
QFile::remove(autosaveBaseName);
}
}
}
KoUnit KisDocument::unit() const
{
return d->unit;
}
void KisDocument::setUnit(const KoUnit &unit)
{
if (d->unit != unit) {
d->unit = unit;
emit unitChanged(unit);
}
}
KUndo2Stack *KisDocument::undoStack()
{
return d->undoStack;
}
KisImportExportManager *KisDocument::importExportManager() const
{
return d->importExportManager;
}
void KisDocument::addCommand(KUndo2Command *command)
{
if (command)
d->undoStack->push(command);
}
void KisDocument::beginMacro(const KUndo2MagicString & text)
{
d->undoStack->beginMacro(text);
}
void KisDocument::endMacro()
{
d->undoStack->endMacro();
}
void KisDocument::slotUndoStackCleanChanged(bool value)
{
setModified(!value);
}
void KisDocument::slotConfigChanged()
{
KisConfig cfg(true);
d->undoStack->setUndoLimit(cfg.undoStackLimit());
d->autoSaveDelay = cfg.autoSaveInterval();
setNormalAutoSaveInterval();
}
void KisDocument::clearUndoHistory()
{
d->undoStack->clear();
}
KisGridConfig KisDocument::gridConfig() const
{
return d->gridConfig;
}
void KisDocument::setGridConfig(const KisGridConfig &config)
{
d->gridConfig = config;
}
QList &KisDocument::paletteList()
{
return d->paletteList;
}
void KisDocument::setPaletteList(const QList &paletteList)
{
d->paletteList = paletteList;
}
const KisGuidesConfig& KisDocument::guidesConfig() const
{
return d->guidesConfig;
}
void KisDocument::setGuidesConfig(const KisGuidesConfig &data)
{
if (d->guidesConfig == data) return;
d->guidesConfig = data;
emit sigGuidesConfigChanged(d->guidesConfig);
}
+const KisMirrorAxisConfig& KisDocument::mirrorAxisConfig() const
+{
+ return d->mirrorAxisConfig;
+}
+
+void KisDocument::setMirrorAxisConfig(const KisMirrorAxisConfig &config)
+{
+ if (d->mirrorAxisConfig == config) {
+ return;
+ }
+
+ d->mirrorAxisConfig = config;
+ setModified(true);
+
+ emit sigMirrorAxisConfigChanged();
+}
+
void KisDocument::resetURL() {
setUrl(QUrl());
setLocalFilePath(QString());
}
KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const
{
return new KoDocumentInfoDlg(parent, docInfo);
}
bool KisDocument::isReadWrite() const
{
return d->readwrite;
}
QUrl KisDocument::url() const
{
return d->m_url;
}
bool KisDocument::closeUrl(bool promptToSave)
{
if (promptToSave) {
if ( isReadWrite() && isModified()) {
Q_FOREACH (KisView *view, KisPart::instance()->views()) {
if (view && view->document() == this) {
if (!view->queryClose()) {
return false;
}
}
}
}
}
// Not modified => ok and delete temp file.
d->mimeType = QByteArray();
// It always succeeds for a read-only part,
// but the return value exists for reimplementations
// (e.g. pressing cancel for a modified read-write part)
return true;
}
void KisDocument::setUrl(const QUrl &url)
{
d->m_url = url;
}
QString KisDocument::localFilePath() const
{
return d->m_file;
}
void KisDocument::setLocalFilePath( const QString &localFilePath )
{
d->m_file = localFilePath;
}
bool KisDocument::openUrlInternal(const QUrl &url)
{
if ( !url.isValid() )
return false;
if (d->m_bAutoDetectedMime) {
d->mimeType = QByteArray();
d->m_bAutoDetectedMime = false;
}
QByteArray mimetype = d->mimeType;
if ( !closeUrl() )
return false;
d->mimeType = mimetype;
setUrl(url);
d->m_file.clear();
if (d->m_url.isLocalFile()) {
d->m_file = d->m_url.toLocalFile();
bool ret;
// set the mimetype only if it was not already set (for example, by the host application)
if (d->mimeType.isEmpty()) {
// get the mimetype of the file
// using findByUrl() to avoid another string -> url conversion
QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile());
d->mimeType = mime.toLocal8Bit();
d->m_bAutoDetectedMime = true;
}
setUrl(d->m_url);
ret = openFile();
if (ret) {
emit completed();
} else {
emit canceled(QString());
}
return ret;
}
return false;
}
bool KisDocument::newImage(const QString& name,
qint32 width, qint32 height,
const KoColorSpace* cs,
const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle,
int numberOfLayers,
const QString &description, const double imageResolution)
{
Q_ASSERT(cs);
KisImageSP image;
if (!cs) return false;
QApplication::setOverrideCursor(Qt::BusyCursor);
image = new KisImage(createUndoStore(), width, height, cs, name);
Q_CHECK_PTR(image);
connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
image->setResolution(imageResolution, imageResolution);
image->assignImageProfile(cs->profile());
documentInfo()->setAboutInfo("title", name);
documentInfo()->setAboutInfo("abstract", description);
KisLayerSP layer;
if (bgStyle == KisConfig::RASTER_LAYER || bgStyle == KisConfig::FILL_LAYER) {
KoColor strippedAlpha = bgColor;
strippedAlpha.setOpacity(OPACITY_OPAQUE_U8);
if (bgStyle == KisConfig::RASTER_LAYER) {
layer = new KisPaintLayer(image.data(), "Background", OPACITY_OPAQUE_U8, cs);;
layer->paintDevice()->setDefaultPixel(strippedAlpha);
} else if (bgStyle == KisConfig::FILL_LAYER) {
KisFilterConfigurationSP filter_config = KisGeneratorRegistry::instance()->get("color")->defaultConfiguration();
filter_config->setProperty("color", strippedAlpha.toQColor());
layer = new KisGeneratorLayer(image.data(), "Background Fill", filter_config, image->globalSelection());
}
layer->setOpacity(bgColor.opacityU8());
if (numberOfLayers > 1) {
//Lock bg layer if others are present.
layer->setUserLocked(true);
}
}
else { // KisConfig::CANVAS_COLOR (needs an unlocked starting layer).
image->setDefaultProjectionColor(bgColor);
layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
}
Q_CHECK_PTR(layer);
image->addNode(layer.data(), image->rootLayer().data());
layer->setDirty(QRect(0, 0, width, height));
setCurrentImage(image);
for(int i = 1; i < numberOfLayers; ++i) {
KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
image->addNode(layer, image->root(), i);
layer->setDirty(QRect(0, 0, width, height));
}
KisConfig cfg(false);
cfg.defImageWidth(width);
cfg.defImageHeight(height);
cfg.defImageResolution(imageResolution);
cfg.defColorModel(image->colorSpace()->colorModelId().id());
cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id());
cfg.defColorProfile(image->colorSpace()->profile()->name());
KisUsageLogger::log(i18n("Created image \"%1\", %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8"
, name
, width, height
, imageResolution * 72.0
, image->colorSpace()->colorModelId().name(), image->colorSpace()->colorDepthId().name()
, image->colorSpace()->profile()->name()
, numberOfLayers));
QApplication::restoreOverrideCursor();
return true;
}
bool KisDocument::isSaving() const
{
const bool result = d->savingMutex.tryLock();
if (result) {
d->savingMutex.unlock();
}
return !result;
}
void KisDocument::waitForSavingToComplete()
{
if (isSaving()) {
KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0);
f.waitForMutex(&d->savingMutex);
}
}
KoShapeControllerBase *KisDocument::shapeController() const
{
return d->shapeController;
}
KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const
{
return d->shapeController->shapeForNode(layer);
}
QList KisDocument::assistants() const
{
return d->assistants;
}
void KisDocument::setAssistants(const QList &value)
{
d->assistants = value;
}
KisSharedPtr KisDocument::referenceImagesLayer() const
{
return d->referenceImagesLayer.data();
}
void KisDocument::setReferenceImagesLayer(KisSharedPtr layer, bool updateImage)
{
if (d->referenceImagesLayer) {
d->referenceImagesLayer->disconnect(this);
}
if (updateImage) {
if (layer) {
d->image->addNode(layer);
} else {
d->image->removeNode(d->referenceImagesLayer);
}
}
d->referenceImagesLayer = layer;
if (d->referenceImagesLayer) {
connect(d->referenceImagesLayer, SIGNAL(sigUpdateCanvas(QRectF)),
this, SIGNAL(sigReferenceImagesChanged()));
}
}
void KisDocument::setPreActivatedNode(KisNodeSP activatedNode)
{
d->preActivatedNode = activatedNode;
}
KisNodeSP KisDocument::preActivatedNode() const
{
return d->preActivatedNode;
}
KisImageWSP KisDocument::image() const
{
return d->image;
}
KisImageSP KisDocument::savingImage() const
{
return d->savingImage;
}
void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate)
{
if (d->image) {
// Disconnect existing sig/slot connections
d->image->setUndoStore(new KisDumbUndoStore());
d->image->disconnect(this);
d->shapeController->setImage(0);
d->image = 0;
}
if (!image) return;
d->setImageAndInitIdleWatcher(image);
d->image->setUndoStore(new KisDocumentUndoStore(this));
d->shapeController->setImage(image);
setModified(false);
connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
if (forceInitialUpdate) {
d->image->initialRefreshGraph();
}
}
void KisDocument::hackPreliminarySetImage(KisImageSP image)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image);
d->setImageAndInitIdleWatcher(image);
d->shapeController->setImage(image);
}
void KisDocument::setImageModified()
{
setModified(true);
}
KisUndoStore* KisDocument::createUndoStore()
{
return new KisDocumentUndoStore(this);
}
bool KisDocument::isAutosaving() const
{
return d->isAutosaving;
}
QString KisDocument::exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
{
return errorMessage.isEmpty() ? KisImportExportFilter::conversionStatusString(status) : errorMessage;
}
void KisDocument::setAssistantsGlobalColor(QColor color)
{
d->globalAssistantsColor = color;
}
QColor KisDocument::assistantsGlobalColor()
{
return d->globalAssistantsColor;
}
QRectF KisDocument::documentBounds() const
{
QRectF bounds = d->image->bounds();
if (d->referenceImagesLayer) {
bounds |= d->referenceImagesLayer->boundingImageRect();
}
return bounds;
}
diff --git a/libs/ui/KisDocument.h b/libs/ui/KisDocument.h
index eebc152d32..88fe9570c9 100644
--- a/libs/ui/KisDocument.h
+++ b/libs/ui/KisDocument.h
@@ -1,656 +1,662 @@
/* This file is part of the Krita project
*
* Copyright (C) 2014 Boudewijn Rempt
*
* 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 KISDOCUMENT_H
#define KISDOCUMENT_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kritaui_export.h"
#include
class QString;
class KUndo2Command;
class KoUnit;
class KoColor;
class KoColorSpace;
class KoShapeControllerBase;
class KoShapeLayer;
class KoStore;
class KoOdfReadStore;
class KoDocumentInfo;
class KoDocumentInfoDlg;
class KisImportExportManager;
class KisUndoStore;
class KisPart;
class KisGridConfig;
class KisGuidesConfig;
+class KisMirrorAxisConfig;
class QDomDocument;
class KisReferenceImagesLayer;
#define KIS_MIME_TYPE "application/x-krita"
/**
* The %Calligra document class
*
* This class provides some functionality each %Calligra document should have.
*
* @short The %Calligra document class
*/
class KRITAUI_EXPORT KisDocument : public QObject, public KoDocumentBase
{
Q_OBJECT
protected:
explicit KisDocument();
/**
* @brief KisDocument makes a deep copy of the document \p rhs.
* The caller *must* ensure that the image is properly
* locked and is in consistent state before asking for
* cloning.
* @param rhs the source document to copy from
*/
explicit KisDocument(const KisDocument &rhs);
public:
enum OpenFlag {
None = 0,
DontAddToRecent = 0x1,
RecoveryFile = 0x2
};
Q_DECLARE_FLAGS(OpenFlags, OpenFlag)
/**
* Destructor.
*
* The destructor does not delete any attached KisView objects and it does not
* delete the attached widget as returned by widget().
*/
~KisDocument() override;
/**
* @brief reload Reloads the document from the original url
* @return the result of loading the document
*/
bool reload();
/**
* @brief creates a clone of the document and returns it. Please make sure that you
* hold all the necessary locks on the image before asking for a clone!
*/
KisDocument* clone();
/**
* @brief openUrl Open an URL
* @param url The URL to open
* @param flags Control specific behavior
* @return success status
*/
bool openUrl(const QUrl &url, OpenFlags flags = None);
/**
* Opens the document given by @p url, without storing the URL
* in the KisDocument.
* Call this instead of openUrl() to implement KisMainWindow's
* File --> Import feature.
*
* @note This will call openUrl(). To differentiate this from an ordinary
* Open operation (in any reimplementation of openUrl() or openFile())
* call isImporting().
*/
bool importDocument(const QUrl &url);
/**
* Saves the document as @p url without changing the state of the
* KisDocument (URL, modified flag etc.). Call this instead of
* KisParts::ReadWritePart::saveAs() to implement KisMainWindow's
* File --> Export feature.
*/
bool exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings = false, KisPropertiesConfigurationSP exportConfiguration = 0);
bool exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration = 0);
private:
bool exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration);
public:
/**
* @brief Sets whether the document can be edited or is read only.
*
* This recursively applied to all child documents and
* KisView::updateReadWrite is called for every attached
* view.
*/
void setReadWrite(bool readwrite = true);
/**
* To be preferred when a document exists. It is fast when calling
* it multiple times since it caches the result that readNativeFormatMimeType()
* delivers.
* This comes from the X-KDE-NativeMimeType key in the .desktop file.
*/
static QByteArray nativeFormatMimeType() { return KIS_MIME_TYPE; }
/// Checks whether a given mimetype can be handled natively.
bool isNativeFormat(const QByteArray& mimetype) const;
/// Returns a list of the mimetypes considered "native", i.e. which can
/// be saved by KisDocument without a filter, in *addition* to the main one
static QStringList extraNativeMimeTypes() { return QStringList() << KIS_MIME_TYPE; }
/**
* Returns the actual mimetype of the document
*/
QByteArray mimeType() const override;
/**
* @brief Sets the mime type for the document.
*
* When choosing "save as" this is also the mime type
* selected by default.
*/
void setMimeType(const QByteArray & mimeType) override;
/**
* @return true if file operations should inhibit the option dialog
*/
bool fileBatchMode() const;
/**
* @param batchMode if true, do not show the option dialog for file operations.
*/
void setFileBatchMode(const bool batchMode);
/**
* Sets the error message to be shown to the user (use i18n()!)
* when loading or saving fails.
* If you asked the user about something and they chose "Cancel",
*/
void setErrorMessage(const QString& errMsg);
/**
* Return the last error message. Usually KisDocument takes care of
* showing it; this method is mostly provided for non-interactive use.
*/
QString errorMessage() const;
/**
* Sets the warning message to be shown to the user (use i18n()!)
* when loading or saving fails.
*/
void setWarningMessage(const QString& warningMsg);
/**
* Return the last warning message set by loading or saving. Warnings
* mean that the document could not be completely loaded, but the errors
* were not absolutely fatal.
*/
QString warningMessage() const;
/**
* @brief Generates a preview picture of the document
* @note The preview is used in the File Dialog and also to create the Thumbnail
*/
QPixmap generatePreview(const QSize& size);
/**
* Tells the document that its title has been modified, either because
* the modified status changes (this is done by setModified() ) or
* because the URL or the document-info's title changed.
*/
void setTitleModified();
/**
* @brief Sets the document to empty.
*
* Used after loading a template
* (which is not empty, but not the user's input).
*
* @see isEmpty()
*/
void setEmpty(bool empty = true);
/**
* Return a correctly created QDomDocument for this KisDocument,
* including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element.
* @param tagName the name of the tag for the root element
* @param version the DTD version (usually the application's version).
*/
QDomDocument createDomDocument(const QString& tagName, const QString& version) const;
/**
* Return a correctly created QDomDocument for an old (1.3-style) %Calligra document,
* including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element.
* This static method can be used e.g. by filters.
* @param appName the app's instance name, e.g. words, kspread, kpresenter etc.
* @param tagName the name of the tag for the root element, e.g. DOC for words/kpresenter.
* @param version the DTD version (usually the application's version).
*/
static QDomDocument createDomDocument(const QString& appName, const QString& tagName, const QString& version);
/**
* Loads a document in the native format from a given URL.
* Reimplement if your native format isn't XML.
*
* @param file the file to load - usually KReadOnlyPart::m_file or the result of a filter
*/
bool loadNativeFormat(const QString & file);
/**
* Set standard autosave interval that is set by a config file
*/
void setNormalAutoSaveInterval();
/**
* Set emergency interval that autosave uses when the image is busy,
* by default it is 10 sec
*/
void setEmergencyAutoSaveInterval();
/**
* Disable autosave
*/
void setInfiniteAutoSaveInterval();
/**
* @return the information concerning this document.
* @see KoDocumentInfo
*/
KoDocumentInfo *documentInfo() const;
/**
* Performs a cleanup of unneeded backup files
*/
void removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered);
/**
* Returns true if this document or any of its internal child documents are modified.
*/
bool isModified() const override;
/**
* @return caption of the document
*
* Caption is of the form "[title] - [url]",
* built out of the document info (title) and pretty-printed
* document URL.
* If the title is not present, only the URL it returned.
*/
QString caption() const;
/**
* Sets the document URL to empty URL
* KParts doesn't allow this, but %Calligra apps have e.g. templates
* After using loadNativeFormat on a template, one wants
* to set the url to QUrl()
*/
void resetURL();
/**
* @internal (public for KisMainWindow)
*/
void setMimeTypeAfterLoading(const QString& mimeType);
/**
* Returns the unit used to display all measures/distances.
*/
KoUnit unit() const;
/**
* Sets the unit used to display all measures/distances.
*/
void setUnit(const KoUnit &unit);
KisGridConfig gridConfig() const;
void setGridConfig(const KisGridConfig &config);
/// returns the guides data for this document.
const KisGuidesConfig& guidesConfig() const;
void setGuidesConfig(const KisGuidesConfig &data);
+ const KisMirrorAxisConfig& mirrorAxisConfig() const;
+ void setMirrorAxisConfig(const KisMirrorAxisConfig& config);
+
QList &paletteList();
void setPaletteList(const QList &paletteList);
void clearUndoHistory();
/**
* Sets the modified flag on the document. This means that it has
* to be saved or not before deleting it.
*/
void setModified(bool _mod);
void setRecovered(bool value);
bool isRecovered() const;
void updateEditingTime(bool forceStoreElapsed);
/**
* Returns the global undo stack
*/
KUndo2Stack *undoStack();
/**
* @brief importExportManager gives access to the internal import/export manager
* @return the document's import/export manager
*/
KisImportExportManager *importExportManager() const;
/**
* @brief serializeToNativeByteArray daves the document into a .kra file wtitten
* to a memory-based byte-array
* @return a byte array containing the .kra file
*/
QByteArray serializeToNativeByteArray();
/**
* @brief isInSaving shown if the document has any (background) saving process or not
* @return true if there is some saving in action
*/
bool isInSaving() const;
public Q_SLOTS:
/**
* Adds a command to the undo stack and executes it by calling the redo() function.
* @param command command to add to the undo stack
*/
void addCommand(KUndo2Command *command);
/**
* Begins recording of a macro command. At the end endMacro needs to be called.
* @param text command description
*/
void beginMacro(const KUndo2MagicString &text);
/**
* Ends the recording of a macro command.
*/
void endMacro();
Q_SIGNALS:
/**
* This signal is emitted when the unit is changed by setUnit().
* It is common to connect views to it, in order to change the displayed units
* (e.g. in the rulers)
*/
void unitChanged(const KoUnit &unit);
/**
* Emitted e.g. at the beginning of a save operation
* This is emitted by KisDocument and used by KisView to display a statusbar message
*/
void statusBarMessage(const QString& text, int timeout = 0);
/**
* Emitted e.g. at the end of a save operation
* This is emitted by KisDocument and used by KisView to clear the statusbar message
*/
void clearStatusBarMessage();
/**
* Emitted when the document is modified
*/
void modified(bool);
void titleModified(const QString &caption, bool isModified);
void sigLoadingFinished();
void sigSavingFinished();
void sigGuidesConfigChanged(const KisGuidesConfig &config);
void sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
void sigCompleteBackgroundSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
void sigReferenceImagesChanged();
+ void sigMirrorAxisConfigChanged();
+
private Q_SLOTS:
void finishExportInBackground();
void slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
void slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
void slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
void slotInitiateAsyncAutosaving(KisDocument *clonedDocument);
private:
friend class KisPart;
friend class SafeSavingLocker;
bool initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration,
std::unique_ptr &&optionalClonedDocument);
bool initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration);
bool startExportInBackground(const QString &actionName, const QString &location,
const QString &realLocation,
const QByteArray &mimeType,
bool showWarnings,
KisPropertiesConfigurationSP exportConfiguration);
/**
* Activate/deactivate/configure the autosave feature.
* @param delay in seconds, 0 to disable
*/
void setAutoSaveDelay(int delay);
/**
* Generate a name for the document.
*/
QString newObjectName();
QString generateAutoSaveFileName(const QString & path) const;
/**
* Loads a document
*
* Applies a filter if necessary, and calls loadNativeFormat in any case
* You should not have to reimplement, except for very special cases.
*
* NOTE: this method also creates a new KisView instance!
*
* This method is called from the KReadOnlyPart::openUrl method.
*/
bool openFile();
public:
bool isAutosaving() const override;
public:
QString localFilePath() const override;
void setLocalFilePath( const QString &localFilePath );
KoDocumentInfoDlg* createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const;
bool isReadWrite() const;
QUrl url() const override;
void setUrl(const QUrl &url) override;
bool closeUrl(bool promptToSave = true);
bool saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfigration = 0);
/**
* Create a new image that has this document as a parent and
* replace the current image with this image.
*/
bool newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace * cs, const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle,
int numberOfLayers, const QString &imageDescription, const double imageResolution);
bool isSaving() const;
void waitForSavingToComplete();
KisImageWSP image() const;
/**
* @brief savingImage provides a detached, shallow copy of the original image that must be used when saving.
* Any strokes in progress will not be applied to this image, so the result might be missing some data. On
* the other hand, it won't block.
*
* @return a shallow copy of the original image, or 0 is saving is not in progress
*/
KisImageSP savingImage() const;
/**
* Set the current image to the specified image and turn undo on.
*/
void setCurrentImage(KisImageSP image, bool forceInitialUpdate = true);
/**
* Set the image of the document preliminary, before the document
* has completed loading. Some of the document items (shapes) may want
* to access image properties (bounds and resolution), so we should provide
* it to them even before the entire image is loaded.
*
* Right now, the only use by KoShapeRegistry::createShapeFromOdf(), remove
* after it is deprecated.
*/
void hackPreliminarySetImage(KisImageSP image);
KisUndoStore* createUndoStore();
/**
* The shape controller matches internal krita image layers with
* the flake shape hierarchy.
*/
KoShapeControllerBase * shapeController() const;
KoShapeLayer* shapeForNode(KisNodeSP layer) const;
/**
* Set the list of nodes that was marked as currently active. Used *only*
* for saving loading. Never use it for tools or processing.
*/
void setPreActivatedNode(KisNodeSP activatedNode);
/**
* @return the node that was set as active during loading. Used *only*
* for saving loading. Never use it for tools or processing.
*/
KisNodeSP preActivatedNode() const;
/// @return the list of assistants associated with this document
QList assistants() const;
/// @replace the current list of assistants with @param value
void setAssistants(const QList &value);
void setAssistantsGlobalColor(QColor color);
QColor assistantsGlobalColor();
/**
* Get existing reference images layer or null if none exists.
*/
KisSharedPtr referenceImagesLayer() const;
void setReferenceImagesLayer(KisSharedPtr layer, bool updateImage);
bool save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration);
/**
* Return the bounding box of the image and associated elements (e.g. reference images)
*/
QRectF documentBounds() const;
Q_SIGNALS:
void completed();
void canceled(const QString &);
private Q_SLOTS:
void setImageModified();
void slotAutoSave();
void slotUndoStackCleanChanged(bool value);
void slotConfigChanged();
private:
/**
* @brief try to clone the image. This method handles all the locking for you. If locking
* has failed, no cloning happens
* @return cloned document on success, null otherwise
*/
KisDocument *lockAndCloneForSaving();
QString exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
QString prettyPathOrUrl() const;
bool openUrlInternal(const QUrl &url);
void slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument);
class Private;
Private *const d;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KisDocument::OpenFlags)
Q_DECLARE_METATYPE(KisDocument*)
#endif
diff --git a/libs/ui/canvas/KisMirrorAxisConfig.cpp b/libs/ui/canvas/KisMirrorAxisConfig.cpp
new file mode 100644
index 0000000000..b6d5102426
--- /dev/null
+++ b/libs/ui/canvas/KisMirrorAxisConfig.cpp
@@ -0,0 +1,246 @@
+/*
+ * Copyright (c) 2019 Anna Medonosova
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 2.1 of the License.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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
+#include
+
+#include "KisMirrorAxisConfig.h"
+
+
+class Q_DECL_HIDDEN KisMirrorAxisConfig::Private
+{
+public:
+ Private()
+ : mirrorHorizontal(false)
+ , mirrorVertical(false)
+ , lockHorizontal(false)
+ , lockVertical(false)
+ , hideVerticalDecoration(false)
+ , hideHorizontalDecoration(false)
+ , handleSize(32.f)
+ , horizontalHandlePosition(64.f)
+ , verticalHandlePosition(64.f)
+ {}
+
+ bool operator==(const Private& rhs) {
+ return mirrorHorizontal == rhs.mirrorHorizontal &&
+ mirrorVertical == rhs.mirrorVertical &&
+ lockHorizontal == rhs.lockHorizontal &&
+ lockVertical == rhs.lockVertical &&
+ hideHorizontalDecoration == rhs.hideHorizontalDecoration &&
+ hideVerticalDecoration == rhs.hideVerticalDecoration &&
+ handleSize == rhs.handleSize &&
+ horizontalHandlePosition == rhs.horizontalHandlePosition &&
+ verticalHandlePosition == rhs.verticalHandlePosition &&
+ axisPosition == rhs.axisPosition;
+ }
+
+ bool mirrorHorizontal;
+ bool mirrorVertical;
+ bool lockHorizontal;
+ bool lockVertical;
+ bool hideVerticalDecoration;
+ bool hideHorizontalDecoration;
+
+ float handleSize;
+ float horizontalHandlePosition;
+ float verticalHandlePosition;
+
+ QPointF axisPosition;
+};
+
+
+KisMirrorAxisConfig::KisMirrorAxisConfig()
+ : QObject()
+ , d(new Private())
+{
+
+}
+
+KisMirrorAxisConfig::~KisMirrorAxisConfig()
+{
+
+}
+
+KisMirrorAxisConfig::KisMirrorAxisConfig(const KisMirrorAxisConfig &rhs)
+ : QObject()
+ , d(new Private(*rhs.d))
+{
+
+}
+
+KisMirrorAxisConfig &KisMirrorAxisConfig::operator=(const KisMirrorAxisConfig &rhs)
+{
+ if (&rhs != this) {
+ *d = *rhs.d;
+ }
+
+ return *this;
+}
+
+bool KisMirrorAxisConfig::operator==(const KisMirrorAxisConfig &rhs) const
+{
+ return *d == *rhs.d;
+}
+
+bool KisMirrorAxisConfig::mirrorHorizontal()
+{
+ return d->mirrorHorizontal;
+}
+
+void KisMirrorAxisConfig::setMirrorHorizontal(bool state)
+{
+ d->mirrorHorizontal = state;
+}
+
+bool KisMirrorAxisConfig::mirrorVertical()
+{
+ return d->mirrorVertical;
+}
+
+void KisMirrorAxisConfig::setMirrorVertical(bool state)
+{
+ d->mirrorVertical = state;
+}
+
+bool KisMirrorAxisConfig::lockHorizontal()
+{
+ return d->lockHorizontal;
+}
+
+void KisMirrorAxisConfig::setLockHorizontal(bool state)
+{
+ d->lockHorizontal = state;
+}
+
+bool KisMirrorAxisConfig::lockVertical()
+{
+ return d->lockVertical;
+}
+
+void KisMirrorAxisConfig::setLockVertical(bool state)
+{
+ d->lockVertical = state;
+}
+
+bool KisMirrorAxisConfig::hideVerticalDecoration()
+{
+ return d->hideVerticalDecoration;
+}
+
+void KisMirrorAxisConfig::setHideVerticalDecoration(bool state)
+{
+ d->hideVerticalDecoration = state;
+}
+
+bool KisMirrorAxisConfig::hideHorizontalDecoration()
+{
+ return d->hideHorizontalDecoration;
+}
+
+void KisMirrorAxisConfig::setHideHorizontalDecoration(bool state)
+{
+ d->hideHorizontalDecoration = state;
+}
+
+float KisMirrorAxisConfig::handleSize()
+{
+ return d->handleSize;
+}
+
+void KisMirrorAxisConfig::setHandleSize(float size)
+{
+ d->handleSize = size;
+}
+
+float KisMirrorAxisConfig::horizontalHandlePosition()
+{
+ return d->horizontalHandlePosition;
+}
+
+void KisMirrorAxisConfig::setHorizontalHandlePosition(float position)
+{
+ d->horizontalHandlePosition = position;
+}
+
+float KisMirrorAxisConfig::verticalHandlePosition()
+{
+ return d->verticalHandlePosition;
+}
+
+void KisMirrorAxisConfig::setVerticalHandlePosition(float position)
+{
+ d->verticalHandlePosition = position;
+}
+
+QPointF KisMirrorAxisConfig::axisPosition()
+{
+ return d->axisPosition;
+}
+
+void KisMirrorAxisConfig::setAxisPosition(QPointF position)
+{
+ d->axisPosition = position;
+}
+
+QDomElement KisMirrorAxisConfig::saveToXml(QDomDocument &doc, const QString &tag) const
+{
+ QDomElement mirrorAxisElement = doc.createElement(tag);
+ KisDomUtils::saveValue(&mirrorAxisElement, "mirrorHorizontal", d->mirrorHorizontal);
+ KisDomUtils::saveValue(&mirrorAxisElement, "mirrorVertical", d->mirrorVertical);
+ KisDomUtils::saveValue(&mirrorAxisElement, "lockHorizontal", d->lockHorizontal);
+ KisDomUtils::saveValue(&mirrorAxisElement, "lockVertical", d->lockVertical);
+
+ KisDomUtils::saveValue(&mirrorAxisElement, "hideHorizontalDecoration", d->hideHorizontalDecoration);
+ KisDomUtils::saveValue(&mirrorAxisElement, "hideVerticalDecoration", d->hideVerticalDecoration);
+
+ KisDomUtils::saveValue(&mirrorAxisElement, "handleSize", d->handleSize);
+
+ KisDomUtils::saveValue(&mirrorAxisElement, "horizontalHandlePosition", d->horizontalHandlePosition);
+ KisDomUtils::saveValue(&mirrorAxisElement, "verticalHandlePosition", d->verticalHandlePosition);
+
+ KisDomUtils::saveValue(&mirrorAxisElement, "axisPosition", d->axisPosition);
+
+ return mirrorAxisElement;
+}
+
+bool KisMirrorAxisConfig::loadFromXml(const QDomElement &parent)
+{
+ bool result = true;
+
+ result &= KisDomUtils::loadValue(parent, "mirrorHorizontal", &d->mirrorHorizontal);
+ result &= KisDomUtils::loadValue(parent, "mirrorVertical", &d->mirrorVertical);
+ result &= KisDomUtils::loadValue(parent, "lockHorizontal", &d->lockHorizontal);
+ result &= KisDomUtils::loadValue(parent, "lockVertical", &d->lockVertical);
+
+ result &= KisDomUtils::loadValue(parent, "hideHorizontalDecoration", &d->hideHorizontalDecoration);
+ result &= KisDomUtils::loadValue(parent, "hideVerticalDecoration", &d->hideVerticalDecoration);
+
+ result &= KisDomUtils::loadValue(parent, "handleSize", &d->handleSize);
+
+ result &= KisDomUtils::loadValue(parent, "horizontalHandlePosition", &d->horizontalHandlePosition);
+ result &= KisDomUtils::loadValue(parent, "verticalHandlePosition", &d->verticalHandlePosition);
+ result &= KisDomUtils::loadValue(parent, "axisPosition", &d->axisPosition);
+
+ return result;
+}
+
+bool KisMirrorAxisConfig::isDefault() const
+{
+ KisMirrorAxisConfig defaultConfig;
+ return *this == defaultConfig;
+}
diff --git a/libs/ui/canvas/KisMirrorAxisConfig.h b/libs/ui/canvas/KisMirrorAxisConfig.h
new file mode 100644
index 0000000000..d2421297c3
--- /dev/null
+++ b/libs/ui/canvas/KisMirrorAxisConfig.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2019 Anna Medonosova
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 2.1 of the License.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 KISMIRRORAXISCONFIG_H
+#define KISMIRRORAXISCONFIG_H
+
+#include
+
+#include "kritaui_export.h"
+#include
+
+class QDomElement;
+class QDomDocument;
+
+/**
+ * @brief The KisMirrorAxisConfig class stores configuration for the KisMirrorAxis
+ * canvas decoration. Contents are saved to/loaded from KRA documents.
+ */
+
+class KRITAUI_EXPORT KisMirrorAxisConfig : public QObject, boost::equality_comparable
+{
+ Q_OBJECT
+
+public:
+ KisMirrorAxisConfig();
+ ~KisMirrorAxisConfig();
+
+ KisMirrorAxisConfig(const KisMirrorAxisConfig &rhs);
+ KisMirrorAxisConfig& operator=(const KisMirrorAxisConfig& rhs);
+ bool operator==(const KisMirrorAxisConfig& rhs) const;
+
+ bool mirrorHorizontal();
+ void setMirrorHorizontal(bool state);
+
+ bool mirrorVertical();
+ void setMirrorVertical(bool state);
+
+ bool lockHorizontal();
+ void setLockHorizontal(bool state);
+
+ bool lockVertical();
+ void setLockVertical(bool state);
+
+ bool hideVerticalDecoration();
+ void setHideVerticalDecoration(bool state);
+
+ bool hideHorizontalDecoration();
+ void setHideHorizontalDecoration(bool state);
+
+ float handleSize();
+ void setHandleSize(float size);
+
+ float horizontalHandlePosition();
+ void setHorizontalHandlePosition(float position);
+
+ float verticalHandlePosition();
+ void setVerticalHandlePosition(float position);
+
+ QPointF axisPosition();
+ void setAxisPosition(QPointF position);
+
+ /**
+ * @brief saveToXml() function for KisKraSaver
+ * @param doc
+ * @param tag
+ * @return
+ */
+ QDomElement saveToXml(QDomDocument& doc, const QString &tag) const;
+
+ /**
+ * @brief loadFromXml() function for KisKraLoader
+ * @param parent element
+ * @return
+ */
+ bool loadFromXml(const QDomElement &parent);
+
+ /**
+ * @brief Check whether the config object was changed, or is the class default.
+ * @return true, if the object is default; false, if the config was changed
+ */
+ bool isDefault() const;
+
+private:
+ class Private;
+ const QScopedPointer d;
+};
+
+#endif // KISMIRRORAXISCONFIG_H
diff --git a/libs/ui/canvas/kis_mirror_axis.cpp b/libs/ui/canvas/kis_mirror_axis.cpp
index 36070a5042..39fc58b5dc 100644
--- a/libs/ui/canvas/kis_mirror_axis.cpp
+++ b/libs/ui/canvas/kis_mirror_axis.cpp
@@ -1,404 +1,467 @@
/*
* Copyright (c) 2014 Arjen Hiemstra
*
* 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_mirror_axis.h"
#include "KoConfig.h"
#include
#include
#include
#include
#include
#include
+#include
#include
#include "kis_canvas2.h"
#include "kis_canvas_resource_provider.h"
#include "KisViewManager.h"
#include "KisView.h"
#include "kis_image.h"
#include "canvas/kis_canvas_controller.h"
#include "input/kis_input_manager.h"
#include "kis_algebra_2d.h"
+#include
+#include
+#include
+
class KisMirrorAxis::Private
{
public:
Private(KisMirrorAxis* qq)
: q(qq)
, resourceProvider(0)
- , mirrorHorizontal(false)
- , mirrorVertical(false)
- , lockHorizontal(false)
- , lockVertical(false)
- , hideVerticalDecoration(false)
- , hideHorizontalDecoration(false)
- , handleSize(32.f)
, xActive(false)
, yActive(false)
- , horizontalHandlePosition(64.f)
- , verticalHandlePosition(64.f)
, sideMargin(10.f)
, minHandlePosition(10.f + 32.f)
, horizontalContainsCursor(false)
, verticalContainsCursor(false)
, horizontalAxis(QLineF())
, verticalAxis(QLineF())
+ , config(KisMirrorAxisConfig())
{ }
void setAxisPosition(float x, float y);
void recomputeVisibleAxes(QRect viewport);
KisMirrorAxis* q;
KisCanvasResourceProvider* resourceProvider;
KisImageWSP image;
- bool mirrorHorizontal;
- bool mirrorVertical;
-
- bool lockHorizontal;
- bool lockVertical;
- bool hideVerticalDecoration;
- bool hideHorizontalDecoration;
-
-
- float handleSize;
QPixmap horizontalHandleIcon;
QPixmap verticalHandleIcon;
QPixmap horizontalIcon;
QPixmap verticalIcon;
- QPointF axisPosition;
+
QRectF horizontalHandle;
QRectF verticalHandle;
bool xActive;
bool yActive;
- float horizontalHandlePosition;
- float verticalHandlePosition;
+
float sideMargin;
float minHandlePosition;
bool horizontalContainsCursor;
bool verticalContainsCursor;
QLineF horizontalAxis;
QLineF verticalAxis;
+
+ KisMirrorAxisConfig config;
};
KisMirrorAxis::KisMirrorAxis(KisCanvasResourceProvider* provider, QPointerparent)
: KisCanvasDecoration("mirror_axis", parent)
, d(new Private(this))
{
d->resourceProvider = provider;
connect(d->resourceProvider, SIGNAL(mirrorModeChanged()), SLOT(mirrorModeChanged()));
connect(d->resourceProvider, SIGNAL(moveMirrorVerticalCenter()), SLOT(moveVerticalAxisToCenter()));
connect(d->resourceProvider, SIGNAL(moveMirrorHorizontalCenter()), SLOT(moveHorizontalAxisToCenter()));
- d->mirrorHorizontal = d->resourceProvider->mirrorHorizontal();
- d->mirrorVertical = d->resourceProvider->mirrorVertical();
- d->horizontalIcon = KisIconUtils::loadIcon("mirrorAxis-HorizontalMove").pixmap(d->handleSize, QIcon::Normal, QIcon::On);
- d->verticalIcon = KisIconUtils::loadIcon("mirrorAxis-VerticalMove").pixmap(d->handleSize, QIcon::Normal, QIcon::On);
- d->horizontalHandleIcon = KisIconUtils::loadIcon("transform-move").pixmap(d->handleSize, QIcon::Normal, QIcon::On);
- d->verticalHandleIcon = KisIconUtils::loadIcon("transform-move").pixmap(d->handleSize, QIcon::Normal, QIcon::On);
- setVisible(d->mirrorHorizontal || d->mirrorVertical);
-
+ d->config.setMirrorHorizontal(d->resourceProvider->mirrorHorizontal());
+ d->config.setMirrorVertical(d->resourceProvider->mirrorVertical());
+ d->horizontalIcon = KisIconUtils::loadIcon("mirrorAxis-HorizontalMove").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On);
+ d->verticalIcon = KisIconUtils::loadIcon("mirrorAxis-VerticalMove").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On);
+ d->horizontalHandleIcon = KisIconUtils::loadIcon("transform-move").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On);
+ d->verticalHandleIcon = KisIconUtils::loadIcon("transform-move").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On);
+ setVisible(d->config.mirrorHorizontal() || d->config.mirrorVertical());
d->image = parent->canvasBase()->image();
}
KisMirrorAxis::~KisMirrorAxis()
{
delete d;
}
float KisMirrorAxis::handleSize() const
{
- return d->handleSize;
+ return d->config.handleSize();
}
void KisMirrorAxis::setHandleSize(float newSize)
{
- if(d->handleSize != newSize) {
- d->handleSize = newSize;
- d->horizontalIcon = KisIconUtils::loadIcon("symmetry-horyzontal").pixmap(d->handleSize, QIcon::Normal, QIcon::On);
- d->verticalIcon = KisIconUtils::loadIcon("symmetry-vertical").pixmap(d->handleSize, QIcon::Normal, QIcon::On);
- d->horizontalHandleIcon = KisIconUtils::loadIcon("transform-move").pixmap(d->handleSize, QIcon::Normal, QIcon::On);
- d->verticalHandleIcon = KisIconUtils::loadIcon("transform-move").pixmap(d->handleSize, QIcon::Normal, QIcon::On);
+ if(d->config.handleSize() != newSize) {
+ d->config.setHandleSize(newSize);
+ d->horizontalIcon = KisIconUtils::loadIcon("symmetry-horyzontal").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On);
+ d->verticalIcon = KisIconUtils::loadIcon("symmetry-vertical").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On);
+ d->horizontalHandleIcon = KisIconUtils::loadIcon("transform-move").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On);
+ d->verticalHandleIcon = KisIconUtils::loadIcon("transform-move").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On);
d->minHandlePosition = d->sideMargin + newSize;
emit handleSizeChanged();
+ emit sigConfigChanged();
}
}
void KisMirrorAxis::drawDecoration(QPainter& gc, const QRectF& updateArea, const KisCoordinatesConverter* converter, KisCanvas2* canvas)
{
Q_UNUSED(updateArea);
Q_UNUSED(converter);
Q_UNUSED(canvas);
+ if (!view()->isCurrent()) {
+ return;
+ }
+
gc.save();
gc.setPen(QPen(QColor(0, 0, 0, 128), 1));
gc.setBrush(Qt::white);
gc.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
QOpenGLContext *ctx = QOpenGLContext::currentContext();
bool hasMultisample = ((gc.paintEngine()->type() == QPaintEngine::OpenGL2) &&
(ctx->hasExtension("GL_ARB_multisample")));
// QPainter cannot anti-alias the edges of circles etc. when using OpenGL
// So instead, use native OpenGL anti-aliasing when available.
if (hasMultisample) {
gc.beginNativePainting();
ctx->functions()->glEnable(GL_MULTISAMPLE);
gc.endNativePainting();
}
- float halfHandleSize = d->handleSize / 2;
+ float halfHandleSize = d->config.handleSize() / 2;
d->recomputeVisibleAxes(gc.viewport());
- if(d->mirrorHorizontal && !d->hideHorizontalDecoration) {
+ if(d->config.mirrorHorizontal() && !d->config.hideHorizontalDecoration()) {
if (!d->horizontalAxis.isNull()) {
// QPointF horizontalIndicatorCenter = d->horizontalAxis.unitVector().pointAt(15);
- // QRectF horizontalIndicator = QRectF(horizontalIndicatorCenter.x() - halfHandleSize, horizontalIndicatorCenter.y() - halfHandleSize, d->handleSize, d->handleSize);
+ // QRectF horizontalIndicator = QRectF(horizontalIndicatorCenter.x() - halfHandleSize, horizontalIndicatorCenter.y() - halfHandleSize, d->config.handleSize(), d->config.handleSize());
- float horizontalHandlePosition = qBound(d->minHandlePosition, d->horizontalHandlePosition, d->horizontalAxis.length() - d->minHandlePosition);
+ float horizontalHandlePosition = qBound(d->minHandlePosition, d->config.horizontalHandlePosition(), d->horizontalAxis.length() - d->minHandlePosition);
QPointF horizontalHandleCenter = d->horizontalAxis.unitVector().pointAt(horizontalHandlePosition);
- d->horizontalHandle = QRectF(horizontalHandleCenter.x() - halfHandleSize, horizontalHandleCenter.y() - halfHandleSize, d->handleSize, d->handleSize);
+ d->horizontalHandle = QRectF(horizontalHandleCenter.x() - halfHandleSize, horizontalHandleCenter.y() - halfHandleSize, d->config.handleSize(), d->config.handleSize());
gc.setPen(QPen(QColor(0, 0, 0, 64), 2, Qt::DashDotDotLine, Qt::RoundCap, Qt::RoundJoin));
gc.drawLine(d->horizontalAxis);
// gc.drawEllipse(horizontalIndicator);
// gc.drawPixmap(horizontalIndicator.adjusted(5, 5, -5, -5).toRect(), d->horizontalIcon);
// don't draw the handles if we are locking the axis for movement
- if (!d->lockHorizontal) {
+ if (!d->config.lockHorizontal()) {
gc.setPen(QPen(QColor(0, 0, 0, 128), 2));
gc.drawEllipse(d->horizontalHandle);
gc.drawPixmap(d->horizontalHandle.adjusted(5, 5, -5, -5).toRect(), d->horizontalIcon);
}
} else {
d->horizontalHandle = QRectF();
}
}
- if(d->mirrorVertical && !d->hideVerticalDecoration) {
+ if(d->config.mirrorVertical() && !d->config.hideVerticalDecoration()) {
if (!d->verticalAxis.isNull()) {
gc.setPen(QPen(QColor(0, 0, 0, 64), 2, Qt::DashDotDotLine, Qt::RoundCap, Qt::RoundJoin));
gc.drawLine(d->verticalAxis);
// QPointF verticalIndicatorCenter = d->verticalAxis.unitVector().pointAt(15);
- // QRectF verticalIndicator = QRectF(verticalIndicatorCenter.x() - halfHandleSize, verticalIndicatorCenter.y() - halfHandleSize, d->handleSize, d->handleSize);
+ // QRectF verticalIndicator = QRectF(verticalIndicatorCenter.x() - halfHandleSize, verticalIndicatorCenter.y() - halfHandleSize, d->config.handleSize(), d->config.handleSize());
- float verticalHandlePosition = qBound(d->minHandlePosition, d->verticalHandlePosition, d->verticalAxis.length() - d->minHandlePosition);
+ float verticalHandlePosition = qBound(d->minHandlePosition, d->config.verticalHandlePosition(), d->verticalAxis.length() - d->minHandlePosition);
QPointF verticalHandleCenter = d->verticalAxis.unitVector().pointAt(verticalHandlePosition);
- d->verticalHandle = QRectF(verticalHandleCenter.x() - halfHandleSize, verticalHandleCenter.y() - halfHandleSize, d->handleSize, d->handleSize);
+ d->verticalHandle = QRectF(verticalHandleCenter.x() - halfHandleSize, verticalHandleCenter.y() - halfHandleSize, d->config.handleSize(), d->config.handleSize());
// don't draw the handles if we are locking the axis for movement
- if (!d->lockVertical) {
+ if (!d->config.lockVertical()) {
gc.setPen(QPen(QColor(0, 0, 0, 128), 2));
gc.drawEllipse(d->verticalHandle);
gc.drawPixmap(d->verticalHandle.adjusted(5, 5, -5, -5).toRect(), d->verticalIcon);
}
} else {
d->verticalHandle = QRectF();
}
}
if (hasMultisample) {
gc.beginNativePainting();
ctx->functions()->glDisable(GL_MULTISAMPLE);
gc.endNativePainting();
}
gc.restore();
}
bool KisMirrorAxis::eventFilter(QObject* target, QEvent* event)
{
if (!visible()) return false;
QObject *expectedCanvasWidget = view() ?
view()->canvasBase()->canvasWidget() : 0;
if (!expectedCanvasWidget || target != expectedCanvasWidget) return false;
if(event->type() == QEvent::MouseButtonPress || event->type() == QEvent::TabletPress) {
QMouseEvent *me = dynamic_cast(event);
QTabletEvent *te = dynamic_cast(event);
QPoint pos = me ? me->pos() : (te ? te->pos() : QPoint(77,77));
-
-
-
- if(d->mirrorHorizontal && d->horizontalHandle.contains(pos) && !d->lockHorizontal && !d->hideHorizontalDecoration ) {
+ if(d->config.mirrorHorizontal() && d->horizontalHandle.contains(pos) && !d->config.lockHorizontal() && !d->config.hideHorizontalDecoration() ) {
d->xActive = true;
QApplication::setOverrideCursor(Qt::ClosedHandCursor);
event->accept();
return true;
}
- if(d->mirrorVertical && d->verticalHandle.contains(pos) && !d->lockVertical && !d->hideVerticalDecoration) {
+ if(d->config.mirrorVertical() && d->verticalHandle.contains(pos) && !d->config.lockVertical() && !d->config.hideVerticalDecoration()) {
d->yActive = true;
QApplication::setOverrideCursor(Qt::ClosedHandCursor);
event->accept();
return true;
}
}
if(event->type() == QEvent::MouseMove || event->type() == QEvent::TabletMove) {
QMouseEvent *me = dynamic_cast(event);
QTabletEvent *te = dynamic_cast(event);
QPoint pos = me ? me->pos() : (te ? te->pos() : QPoint(77,77));
if(d->xActive) {
float axisX = view()->viewConverter()->widgetToImage(pos).x();
- d->setAxisPosition(axisX, d->axisPosition.y());
- d->horizontalHandlePosition = KisAlgebra2D::dotProduct(pos - d->horizontalAxis.p1(), d->horizontalAxis.unitVector().p2() - d->horizontalAxis.p1());
+ d->setAxisPosition(axisX, d->config.axisPosition().y());
+ d->config.setHorizontalHandlePosition(KisAlgebra2D::dotProduct(pos - d->horizontalAxis.p1(), d->horizontalAxis.unitVector().p2() - d->horizontalAxis.p1()));
+ emit sigConfigChanged();
event->accept();
return true;
}
if(d->yActive) {
float axisY = view()->viewConverter()->widgetToImage(pos).y();
- d->setAxisPosition(d->axisPosition.x(), axisY);
- d->verticalHandlePosition = KisAlgebra2D::dotProduct(pos - d->verticalAxis.p1(), d->verticalAxis.unitVector().p2() - d->verticalAxis.p1());
+ d->setAxisPosition(d->config.axisPosition().x(), axisY);
+ d->config.setVerticalHandlePosition(KisAlgebra2D::dotProduct(pos - d->verticalAxis.p1(), d->verticalAxis.unitVector().p2() - d->verticalAxis.p1()));
+ emit sigConfigChanged();
event->accept();
return true;
}
- if(d->mirrorHorizontal && !d->hideHorizontalDecoration) {
- if(d->horizontalHandle.contains(pos) && !d->lockHorizontal) {
+ if(d->config.mirrorHorizontal() && !d->config.hideHorizontalDecoration()) {
+ if(d->horizontalHandle.contains(pos) && !d->config.lockHorizontal()) {
if(!d->horizontalContainsCursor) {
QApplication::setOverrideCursor(Qt::OpenHandCursor);
d->horizontalContainsCursor = true;
}
} else if(d->horizontalContainsCursor) {
QApplication::restoreOverrideCursor();
d->horizontalContainsCursor = false;
}
}
- if(d->mirrorVertical && !d->hideVerticalDecoration) {
- if(d->verticalHandle.contains(pos) && !d->lockVertical) {
+ if(d->config.mirrorVertical() && !d->config.hideVerticalDecoration()) {
+ if(d->verticalHandle.contains(pos) && !d->config.lockVertical()) {
if(!d->verticalContainsCursor) {
QApplication::setOverrideCursor(Qt::OpenHandCursor);
d->verticalContainsCursor = true;
}
} else if(d->verticalContainsCursor) {
QApplication::restoreOverrideCursor();
d->verticalContainsCursor = false;
}
}
}
if(event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::TabletRelease) {
if(d->xActive) {
QApplication::restoreOverrideCursor();
d->xActive = false;
event->accept();
return true;
}
if(d->yActive) {
QApplication::restoreOverrideCursor();
d->yActive = false;
event->accept();
return true;
}
}
return QObject::eventFilter(target, event);
}
void KisMirrorAxis::mirrorModeChanged()
{
- d->mirrorHorizontal = d->resourceProvider->mirrorHorizontal();
- d->mirrorVertical = d->resourceProvider->mirrorVertical();
+ if (!view()->isCurrent()) {
+ return;
+ }
- d->lockHorizontal = d->resourceProvider->mirrorHorizontalLock();
- d->lockVertical = d->resourceProvider->mirrorVerticalLock();
+ d->config.setMirrorHorizontal(d->resourceProvider->mirrorHorizontal());
+ d->config.setMirrorVertical(d->resourceProvider->mirrorVertical());
- d->hideHorizontalDecoration = d->resourceProvider->mirrorHorizontalHideDecorations();
- d->hideVerticalDecoration = d->resourceProvider->mirrorVerticalHideDecorations();
+ d->config.setLockHorizontal(d->resourceProvider->mirrorHorizontalLock());
+ d->config.setLockVertical(d->resourceProvider->mirrorVerticalLock());
- setVisible(d->mirrorHorizontal || d->mirrorVertical);
+ d->config.setHideHorizontalDecoration(d->resourceProvider->mirrorHorizontalHideDecorations());
+ d->config.setHideVerticalDecoration(d->resourceProvider->mirrorVerticalHideDecorations());
+ setVisible(d->config.mirrorHorizontal() || d->config.mirrorVertical());
+
+ emit sigConfigChanged();
}
void KisMirrorAxis::setVisible(bool v)
{
KisCanvasDecoration::setVisible(v);
KisInputManager *inputManager = view() ? view()->canvasBase()->globalInputManager() : 0;
if (!inputManager) return;
if (v) {
inputManager->attachPriorityEventFilter(this);
} else {
inputManager->detachPriorityEventFilter(this);
}
}
+void KisMirrorAxis::setMirrorAxisConfig(const KisMirrorAxisConfig &config)
+{
+ if (config != d->config) {
+ KisSignalsBlocker blocker(d->resourceProvider);
+
+ d->config = config;
+
+ d->setAxisPosition(d->config.axisPosition().x(), d->config.axisPosition().y());
+
+ d->resourceProvider->setMirrorHorizontal(d->config.mirrorHorizontal());
+ d->resourceProvider->setMirrorVertical(d->config.mirrorVertical());
+
+ d->resourceProvider->setMirrorHorizontalLock(d->config.lockHorizontal());
+ d->resourceProvider->setMirrorVerticalLock(d->config.lockVertical());
+
+ d->resourceProvider->setMirrorHorizontal(d->config.mirrorHorizontal());
+ d->resourceProvider->setMirrorVertical(d->config.mirrorVertical());
+
+ d->resourceProvider->setMirrorHorizontalHideDecorations(d->config.hideHorizontalDecoration());
+ d->resourceProvider->setMirrorVerticalHideDecorations(d->config.hideVerticalDecoration());
+ }
+
+ toggleMirrorActions();
+ setVisible(d->config.mirrorHorizontal() || d->config.mirrorVertical());
+}
+
+const KisMirrorAxisConfig &KisMirrorAxis::mirrorAxisConfig() const
+{
+ return d->config;
+}
+
+void KisMirrorAxis::toggleMirrorActions()
+{
+ KActionCollection* collection = view()->viewManager()->actionCollection();
+ // first uncheck the action, then set according to config;
+ // otherwise the connected KisHighlightedToolButton's highlight color is not
+ // properly set
+ collection->action("hmirror_action")->setChecked(false);
+ collection->action("vmirror_action")->setChecked(false);
+
+ if (d->config.mirrorHorizontal()) {
+ collection->action("hmirror_action")->setChecked(d->config.mirrorHorizontal());
+ }
+
+ if (d->config.mirrorVertical()) {
+ collection->action("vmirror_action")->setChecked(d->config.mirrorVertical());
+ }
+
+ collection->action("mirrorX-lock")->setChecked(d->config.lockHorizontal());
+ collection->action("mirrorY-lock")->setChecked(d->config.lockVertical());
+
+ collection->action("mirrorX-hideDecorations")->setChecked(d->config.hideHorizontalDecoration());
+ collection->action("mirrorY-hideDecorations")->setChecked(d->config.hideVerticalDecoration());
+}
+
void KisMirrorAxis::moveHorizontalAxisToCenter()
{
- d->setAxisPosition(d->image->width()/2, d->axisPosition.y());
+ if (!view()->isCurrent()) {
+ return;
+ }
+
+ d->setAxisPosition(d->image->width()/2, d->config.axisPosition().y());
+ emit sigConfigChanged();
}
void KisMirrorAxis::moveVerticalAxisToCenter()
{
- d->setAxisPosition(d->axisPosition.x(), d->image->height()/2 );
+ if (!view()->isCurrent()) {
+ return;
+ }
+
+ d->setAxisPosition(d->config.axisPosition().x(), d->image->height()/2 );
+ emit sigConfigChanged();
}
void KisMirrorAxis::Private::setAxisPosition(float x, float y)
{
QPointF newPosition = QPointF(x, y);
+ config.setAxisPosition(newPosition);
+
const QPointF relativePosition = KisAlgebra2D::absoluteToRelative(newPosition, image->bounds());
image->setMirrorAxesCenter(relativePosition);
q->view()->canvasBase()->updateCanvas();
}
void KisMirrorAxis::Private::recomputeVisibleAxes(QRect viewport)
{
KisCoordinatesConverter *converter = q->view()->viewConverter();
- axisPosition = KisAlgebra2D::relativeToAbsolute(image->mirrorAxesCenter(), image->bounds());
+ config.setAxisPosition(KisAlgebra2D::relativeToAbsolute(image->mirrorAxesCenter(), image->bounds()));
- QPointF samplePt1 = converter->imageToWidget(axisPosition);
- QPointF samplePt2 = converter->imageToWidget(QPointF(axisPosition.x(), axisPosition.y() - 100));
+ QPointF samplePt1 = converter->imageToWidget(config.axisPosition());
+ QPointF samplePt2 = converter->imageToWidget(QPointF(config.axisPosition().x(), config.axisPosition().y() - 100));
horizontalAxis = QLineF(samplePt1, samplePt2);
if (!KisAlgebra2D::intersectLineRect(horizontalAxis, viewport)) horizontalAxis = QLineF();
- samplePt2 = converter->imageToWidget(QPointF(axisPosition.x() - 100, axisPosition.y()));
+ samplePt2 = converter->imageToWidget(QPointF(config.axisPosition().x() - 100, config.axisPosition().y()));
verticalAxis = QLineF(samplePt1, samplePt2);
if (!KisAlgebra2D::intersectLineRect(verticalAxis, viewport)) verticalAxis = QLineF();
}
diff --git a/libs/ui/canvas/kis_mirror_axis.h b/libs/ui/canvas/kis_mirror_axis.h
index 69d39b3976..1176f0e737 100644
--- a/libs/ui/canvas/kis_mirror_axis.h
+++ b/libs/ui/canvas/kis_mirror_axis.h
@@ -1,58 +1,64 @@
/*
* Copyright (c) 2014 Arjen Hiemstra
*
* 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 KISMIRRORAXIS_H
#define KISMIRRORAXIS_H
#include "kis_canvas_decoration.h"
class KisView;
class KisCanvasResourceProvider;
+class KisMirrorAxisConfig;
class KisMirrorAxis : public KisCanvasDecoration
{
Q_OBJECT
Q_PROPERTY(float handleSize READ handleSize WRITE setHandleSize NOTIFY handleSizeChanged)
public:
KisMirrorAxis(KisCanvasResourceProvider* provider, QPointer parent);
~KisMirrorAxis() override;
float handleSize() const;
void setHandleSize(float newSize);
void setVisible(bool v) override;
+ void setMirrorAxisConfig(const KisMirrorAxisConfig& config);
+ const KisMirrorAxisConfig& mirrorAxisConfig() const;
+
Q_SIGNALS:
void handleSizeChanged();
+ void sigConfigChanged();
protected:
void drawDecoration(QPainter& gc, const QRectF& updateArea, const KisCoordinatesConverter* converter, KisCanvas2* canvas) override;
bool eventFilter(QObject* target, QEvent* event) override;
+ void toggleMirrorActions();
private:
class Private;
Private * const d;
private Q_SLOTS:
void mirrorModeChanged();
void moveHorizontalAxisToCenter();
void moveVerticalAxisToCenter();
};
#endif // KISMIRRORAXIS_H
diff --git a/libs/ui/kis_mirror_manager.cpp b/libs/ui/kis_mirror_manager.cpp
index 4cf19d7fc6..c278a142e1 100644
--- a/libs/ui/kis_mirror_manager.cpp
+++ b/libs/ui/kis_mirror_manager.cpp
@@ -1,89 +1,128 @@
/*
* Copyright (c) 2009 Cyrille Berger
* Copyright (c) 2014 Sven Langkamp
*
* 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_mirror_manager.h"
#include "KisViewManager.h"
#include
#include
#include
#include
#include
#include
#include
#include "kis_canvas2.h"
#include "kis_mirror_axis.h"
+#include
+#include
+#include
+
+class KisMirrorManager::Private
+{
+public:
+ Private()
+ : mirrorAxisDecoration(nullptr)
+ {}
+
+ KisMirrorAxis* mirrorAxisDecoration;
+// KisMirrorAxisConfig mirrorAxisConfig() {}
+};
KisMirrorManager::KisMirrorManager(KisViewManager* view) : QObject(view)
+ , d(new Private())
, m_imageView(0)
{
}
KisMirrorManager::~KisMirrorManager()
{
}
void KisMirrorManager::setup(KActionCollection * collection)
{
m_mirrorCanvas = new KToggleAction(i18n("Mirror View"), this);
m_mirrorCanvas->setChecked(false);
m_mirrorCanvas->setIcon(KisIconUtils::loadIcon("mirror-view"));
collection->addAction("mirror_canvas", m_mirrorCanvas);
collection->setDefaultShortcut(m_mirrorCanvas, QKeySequence(Qt::Key_M));
updateAction();
}
void KisMirrorManager::setView(QPointer imageView)
{
if (m_imageView) {
m_mirrorCanvas->disconnect();
+ m_imageView->document()->disconnect();
}
m_imageView = imageView;
if (m_imageView) {
connect(m_mirrorCanvas, SIGNAL(toggled(bool)), dynamic_cast(m_imageView->canvasController()), SLOT(mirrorCanvas(bool)));
+ connect(m_imageView->document(), SIGNAL(sigMirrorAxisConfigChanged()), this, SLOT(slotDocumentConfigChanged()), Qt::UniqueConnection);
if (!hasDecoration()) {
- m_imageView->canvasBase()->addDecoration(new KisMirrorAxis(m_imageView->viewManager()->resourceProvider(), m_imageView));
+ d->mirrorAxisDecoration = new KisMirrorAxis(m_imageView->viewManager()->resourceProvider(), m_imageView);
+ connect(d->mirrorAxisDecoration, SIGNAL(sigConfigChanged()), this, SLOT(slotMirrorAxisConfigChanged()), Qt::UniqueConnection);
+ m_imageView->canvasBase()->addDecoration(d->mirrorAxisDecoration);
}
+
+ d->mirrorAxisDecoration->setMirrorAxisConfig(mirrorAxisConfig());
}
updateAction();
}
void KisMirrorManager::updateAction()
{
if (m_imageView) {
m_mirrorCanvas->setEnabled(true);
m_mirrorCanvas->setChecked(m_imageView->canvasIsMirrored());
}
else {
m_mirrorCanvas->setEnabled(false);
m_mirrorCanvas->setChecked(false);
}
}
+void KisMirrorManager::slotDocumentConfigChanged()
+{
+ d->mirrorAxisDecoration->setMirrorAxisConfig(mirrorAxisConfig());
+}
+
+void KisMirrorManager::slotMirrorAxisConfigChanged()
+{
+ if (m_imageView) {
+ KisSignalsBlocker blocker(m_imageView->document());
+ m_imageView->document()->setMirrorAxisConfig(d->mirrorAxisDecoration->mirrorAxisConfig());
+ }
+}
+
KisMirrorAxis* KisMirrorManager::hasDecoration() {
if (m_imageView && m_imageView->canvasBase() && m_imageView->canvasBase()->decoration("mirror_axis")) {
return dynamic_cast(m_imageView->canvasBase()->decoration("mirror_axis").data());
}
return 0;
}
+
+const KisMirrorAxisConfig& KisMirrorManager::mirrorAxisConfig() const
+{
+ return m_imageView->document()->mirrorAxisConfig();
+}
diff --git a/libs/ui/kis_mirror_manager.h b/libs/ui/kis_mirror_manager.h
index ed915bc983..080d8cc5db 100644
--- a/libs/ui/kis_mirror_manager.h
+++ b/libs/ui/kis_mirror_manager.h
@@ -1,56 +1,61 @@
/*
* Copyright (c) 2009 Cyrille Berger
* Copyright (c) 2014 Sven Langkamp
*
* 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_MIRROR_MANAGER_H
#define KIS_MIRROR_MANAGER_H
#include
#include
+#include
#include "KisView.h"
class KisViewManager;
class KActionCollection;
class KisMirrorAxis;
-
+class KisMirrorAxisConfig;
class KisMirrorManager : public QObject
{
Q_OBJECT
public:
KisMirrorManager(KisViewManager* view);
~KisMirrorManager() override;
void setup(KActionCollection* collection);
void setView(QPointer imageView);
private Q_SLOTS:
void updateAction();
+ void slotDocumentConfigChanged();
+ void slotMirrorAxisConfigChanged();
private:
+ class Private;
+ const QScopedPointer d;
QPointer m_imageView;
QAction *m_mirrorCanvas;
KisMirrorAxis* hasDecoration();
-
+ const KisMirrorAxisConfig &mirrorAxisConfig() const;
};
-#endif // KIS_PAINTING_ASSISTANTS_MANAGER_H
+#endif // KIS__MANAGER_H
diff --git a/libs/ui/kis_paintop_box.cc b/libs/ui/kis_paintop_box.cc
index f63946d9ce..cd1d3e595d 100644
--- a/libs/ui/kis_paintop_box.cc
+++ b/libs/ui/kis_paintop_box.cc
@@ -1,1348 +1,1356 @@
/*
* kis_paintop_box.cc - part of KImageShop/Krayon/Krita
*
* Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org)
* Copyright (c) 2009-2011 Sven Langkamp (sven.langkamp@gmail.com)
* Copyright (c) 2010 Lukáš Tvrdý
* Copyright (C) 2011 Silvio Heinrich
* Copyright (C) 2011 Srikanth Tiyyagura
* Copyright (c) 2014 Mohit Goyal
*
* 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_paintop_box.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_canvas2.h"
#include "kis_node_manager.h"
#include "KisViewManager.h"
#include "kis_canvas_resource_provider.h"
#include "KisResourceServerProvider.h"
#include "kis_favorite_resource_manager.h"
#include "kis_config.h"
#include "kis_popup_button.h"
#include "widgets/kis_iconwidget.h"
#include "widgets/kis_tool_options_popup.h"
#include "widgets/kis_paintop_presets_popup.h"
#include "widgets/kis_paintop_presets_chooser_popup.h"
#include "widgets/kis_workspace_chooser.h"
#include "widgets/kis_paintop_list_widget.h"
#include "widgets/kis_slider_spin_box.h"
#include "widgets/kis_cmb_composite.h"
#include "widgets/kis_widget_chooser.h"
#include "tool/kis_tool.h"
#include "kis_signals_blocker.h"
#include "kis_action_manager.h"
#include "kis_highlighted_button.h"
typedef KoResourceServerSimpleConstruction > KisPaintOpPresetResourceServer;
typedef KoResourceServerAdapter > KisPaintOpPresetResourceServerAdapter;
KisPaintopBox::KisPaintopBox(KisViewManager *view, QWidget *parent, const char *name)
: QWidget(parent)
, m_resourceProvider(view->resourceProvider())
, m_optionWidget(0)
, m_toolOptionsPopupButton(0)
, m_brushEditorPopupButton(0)
, m_presetSelectorPopupButton(0)
, m_toolOptionsPopup(0)
, m_viewManager(view)
, m_previousNode(0)
, m_currTabletToolID(KoInputDevice::invalid())
, m_presetsEnabled(true)
, m_blockUpdate(false)
, m_dirtyPresetsEnabled(false)
, m_eraserBrushSizeEnabled(false)
, m_eraserBrushOpacityEnabled(false)
{
Q_ASSERT(view != 0);
setObjectName(name);
KisConfig cfg(true);
m_dirtyPresetsEnabled = cfg.useDirtyPresets();
m_eraserBrushSizeEnabled = cfg.useEraserBrushSize();
m_eraserBrushOpacityEnabled = cfg.useEraserBrushOpacity();
KAcceleratorManager::setNoAccel(this);
setWindowTitle(i18n("Painter's Toolchest"));
m_favoriteResourceManager = new KisFavoriteResourceManager(this);
KConfigGroup grp = KSharedConfig::openConfig()->group("krita").group("Toolbar BrushesAndStuff");
int iconsize = grp.readEntry("IconSize", 32);
if (!cfg.toolOptionsInDocker()) {
m_toolOptionsPopupButton = new KisPopupButton(this);
m_toolOptionsPopupButton->setIcon(KisIconUtils::loadIcon("configure"));
m_toolOptionsPopupButton->setToolTip(i18n("Tool Settings"));
m_toolOptionsPopupButton->setFixedSize(iconsize, iconsize);
}
m_brushEditorPopupButton = new KisIconWidget(this);
m_brushEditorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02"));
m_brushEditorPopupButton->setToolTip(i18n("Edit brush settings"));
m_brushEditorPopupButton->setFixedSize(iconsize, iconsize);
m_presetSelectorPopupButton = new KisPopupButton(this);
m_presetSelectorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_01"));
m_presetSelectorPopupButton->setToolTip(i18n("Choose brush preset"));
m_presetSelectorPopupButton->setFixedSize(iconsize, iconsize);
m_eraseModeButton = new KisHighlightedToolButton(this);
m_eraseModeButton->setFixedSize(iconsize, iconsize);
m_eraseModeButton->setCheckable(true);
m_eraseAction = m_viewManager->actionManager()->createAction("erase_action");
m_eraseModeButton->setDefaultAction(m_eraseAction);
m_reloadButton = new QToolButton(this);
m_reloadButton->setFixedSize(iconsize, iconsize);
m_reloadAction = m_viewManager->actionManager()->createAction("reload_preset_action");
m_reloadButton->setDefaultAction(m_reloadAction);
m_alphaLockButton = new KisHighlightedToolButton(this);
m_alphaLockButton->setFixedSize(iconsize, iconsize);
m_alphaLockButton->setCheckable(true);
KisAction* alphaLockAction = m_viewManager->actionManager()->createAction("preserve_alpha");
m_alphaLockButton->setDefaultAction(alphaLockAction);
// horizontal and vertical mirror toolbar buttons
// mirror tool options for the X Mirror
QMenu *toolbarMenuXMirror = new QMenu();
hideCanvasDecorationsX = m_viewManager->actionManager()->createAction("mirrorX-hideDecorations");
toolbarMenuXMirror->addAction(hideCanvasDecorationsX);
lockActionX = m_viewManager->actionManager()->createAction("mirrorX-lock");
toolbarMenuXMirror->addAction(lockActionX);
moveToCenterActionX = m_viewManager->actionManager()->createAction("mirrorX-moveToCenter");
toolbarMenuXMirror->addAction(moveToCenterActionX);
// mirror tool options for the Y Mirror
QMenu *toolbarMenuYMirror = new QMenu();
hideCanvasDecorationsY = m_viewManager->actionManager()->createAction("mirrorY-hideDecorations");
toolbarMenuYMirror->addAction(hideCanvasDecorationsY);
lockActionY = m_viewManager->actionManager()->createAction("mirrorY-lock");
toolbarMenuYMirror->addAction(lockActionY);
moveToCenterActionY = m_viewManager->actionManager()->createAction("mirrorY-moveToCenter");
toolbarMenuYMirror->addAction(moveToCenterActionY);
// create horizontal and vertical mirror buttons
m_hMirrorButton = new KisHighlightedToolButton(this);
int menuPadding = 10;
m_hMirrorButton->setFixedSize(iconsize + menuPadding, iconsize);
m_hMirrorButton->setCheckable(true);
m_hMirrorAction = m_viewManager->actionManager()->createAction("hmirror_action");
m_hMirrorButton->setDefaultAction(m_hMirrorAction);
m_hMirrorButton->setMenu(toolbarMenuXMirror);
m_hMirrorButton->setPopupMode(QToolButton::MenuButtonPopup);
m_vMirrorButton = new KisHighlightedToolButton(this);
m_vMirrorButton->setFixedSize(iconsize + menuPadding, iconsize);
m_vMirrorButton->setCheckable(true);
m_vMirrorAction = m_viewManager->actionManager()->createAction("vmirror_action");
m_vMirrorButton->setDefaultAction(m_vMirrorAction);
m_vMirrorButton->setMenu(toolbarMenuYMirror);
m_vMirrorButton->setPopupMode(QToolButton::MenuButtonPopup);
// add connections for horizontal and mirrror buttons
connect(lockActionX, SIGNAL(toggled(bool)), this, SLOT(slotLockXMirrorToggle(bool)));
connect(lockActionY, SIGNAL(toggled(bool)), this, SLOT(slotLockYMirrorToggle(bool)));
connect(moveToCenterActionX, SIGNAL(triggered(bool)), this, SLOT(slotMoveToCenterMirrorX()));
connect(moveToCenterActionY, SIGNAL(triggered(bool)), this, SLOT(slotMoveToCenterMirrorY()));
connect(hideCanvasDecorationsX, SIGNAL(toggled(bool)), this, SLOT(slotHideDecorationMirrorX(bool)));
connect(hideCanvasDecorationsY, SIGNAL(toggled(bool)), this, SLOT(slotHideDecorationMirrorY(bool)));
const bool sliderLabels = cfg.sliderLabels();
int sliderWidth;
if (sliderLabels) {
sliderWidth = 150 * logicalDpiX() / 96;
}
else {
sliderWidth = 120 * logicalDpiX() / 96;
}
for (int i = 0; i < 3; ++i) {
m_sliderChooser[i] = new KisWidgetChooser(i + 1);
KisDoubleSliderSpinBox* slOpacity;
KisDoubleSliderSpinBox* slFlow;
KisDoubleSliderSpinBox* slSize;
if (sliderLabels) {
slOpacity = m_sliderChooser[i]->addWidget("opacity");
slFlow = m_sliderChooser[i]->addWidget("flow");
slSize = m_sliderChooser[i]->addWidget("size");
slOpacity->setPrefix(QString("%1 ").arg(i18n("Opacity:")));
slFlow->setPrefix(QString("%1 ").arg(i18n("Flow:")));
slSize->setPrefix(QString("%1 ").arg(i18n("Size:")));
}
else {
slOpacity = m_sliderChooser[i]->addWidget("opacity", i18n("Opacity:"));
slFlow = m_sliderChooser[i]->addWidget("flow", i18n("Flow:"));
slSize = m_sliderChooser[i]->addWidget("size", i18n("Size:"));
}
slOpacity->setRange(0, 100, 0);
slOpacity->setValue(100);
slOpacity->setSingleStep(5);
slOpacity->setSuffix("%");
slOpacity->setMinimumWidth(qMax(sliderWidth, slOpacity->sizeHint().width()));
slOpacity->setFixedHeight(iconsize);
slOpacity->setBlockUpdateSignalOnDrag(true);
slFlow->setRange(0, 100, 0);
slFlow->setValue(100);
slFlow->setSingleStep(5);
slFlow->setSuffix("%");
slFlow->setMinimumWidth(qMax(sliderWidth, slFlow->sizeHint().width()));
slFlow->setFixedHeight(iconsize);
slFlow->setBlockUpdateSignalOnDrag(true);
slSize->setRange(0, cfg.readEntry("maximumBrushSize", 1000), 2);
slSize->setValue(100);
slSize->setSingleStep(1);
slSize->setExponentRatio(3.0);
slSize->setSuffix(i18n(" px"));
slSize->setMinimumWidth(qMax(sliderWidth, slSize->sizeHint().width()));
slSize->setFixedHeight(iconsize);
slSize->setBlockUpdateSignalOnDrag(true);
m_sliderChooser[i]->chooseWidget(cfg.toolbarSlider(i + 1));
}
m_cmbCompositeOp = new KisCompositeOpComboBox();
m_cmbCompositeOp->setFixedHeight(iconsize);
Q_FOREACH (KisAction * a, m_cmbCompositeOp->blendmodeActions()) {
m_viewManager->actionManager()->addAction(a->text(), a);
}
m_workspaceWidget = new KisPopupButton(this);
m_workspaceWidget->setIcon(KisIconUtils::loadIcon("view-choose"));
m_workspaceWidget->setToolTip(i18n("Choose workspace"));
m_workspaceWidget->setFixedSize(iconsize, iconsize);
m_workspaceWidget->setPopupWidget(new KisWorkspaceChooser(view));
QHBoxLayout* baseLayout = new QHBoxLayout(this);
m_paintopWidget = new QWidget(this);
baseLayout->addWidget(m_paintopWidget);
baseLayout->setSpacing(4);
baseLayout->setContentsMargins(0, 0, 0, 0);
m_layout = new QHBoxLayout(m_paintopWidget);
if (!cfg.toolOptionsInDocker()) {
m_layout->addWidget(m_toolOptionsPopupButton);
}
m_layout->addWidget(m_brushEditorPopupButton);
m_layout->addWidget(m_presetSelectorPopupButton);
m_layout->setSpacing(4);
m_layout->setContentsMargins(0, 0, 0, 0);
QWidget* compositeActions = new QWidget(this);
QHBoxLayout* compositeLayout = new QHBoxLayout(compositeActions);
compositeLayout->addWidget(m_cmbCompositeOp);
compositeLayout->addWidget(m_eraseModeButton);
compositeLayout->addWidget(m_alphaLockButton);
compositeLayout->setSpacing(4);
compositeLayout->setContentsMargins(0, 0, 0, 0);
compositeLayout->addWidget(m_reloadButton);
QWidgetAction * action;
action = new QWidgetAction(this);
view->actionCollection()->addAction("composite_actions", action);
action->setText(i18n("Brush composite"));
action->setDefaultWidget(compositeActions);
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("brushslider1", action);
view->actionCollection()->addAction("brushslider1", action);
action->setDefaultWidget(m_sliderChooser[0]);
connect(action, SIGNAL(triggered()), m_sliderChooser[0], SLOT(showPopupWidget()));
connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[0], SLOT(updateThemedIcons()));
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("brushslider2", action);
view->actionCollection()->addAction("brushslider2", action);
action->setDefaultWidget(m_sliderChooser[1]);
connect(action, SIGNAL(triggered()), m_sliderChooser[1], SLOT(showPopupWidget()));
connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[1], SLOT(updateThemedIcons()));
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("brushslider3", action);
view->actionCollection()->addAction("brushslider3", action);
action->setDefaultWidget(m_sliderChooser[2]);
connect(action, SIGNAL(triggered()), m_sliderChooser[2], SLOT(showPopupWidget()));
connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[2], SLOT(updateThemedIcons()));
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("next_favorite_preset", action);
view->actionCollection()->addAction("next_favorite_preset", action);
connect(action, SIGNAL(triggered()), this, SLOT(slotNextFavoritePreset()));
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("previous_favorite_preset", action);
view->actionCollection()->addAction("previous_favorite_preset", action);
connect(action, SIGNAL(triggered()), this, SLOT(slotPreviousFavoritePreset()));
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("previous_preset", action);
view->actionCollection()->addAction("previous_preset", action);
connect(action, SIGNAL(triggered()), this, SLOT(slotSwitchToPreviousPreset()));
if (!cfg.toolOptionsInDocker()) {
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("show_tool_options", action);
view->actionCollection()->addAction("show_tool_options", action);
connect(action, SIGNAL(triggered()), m_toolOptionsPopupButton, SLOT(showPopupWidget()));
}
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("show_brush_editor", action);
view->actionCollection()->addAction("show_brush_editor", action);
connect(action, SIGNAL(triggered()), m_brushEditorPopupButton, SLOT(showPopupWidget()));
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("show_brush_presets", action);
view->actionCollection()->addAction("show_brush_presets", action);
connect(action, SIGNAL(triggered()), m_presetSelectorPopupButton, SLOT(showPopupWidget()));
QWidget* mirrorActions = new QWidget(this);
QHBoxLayout* mirrorLayout = new QHBoxLayout(mirrorActions);
mirrorLayout->addWidget(m_hMirrorButton);
mirrorLayout->addWidget(m_vMirrorButton);
mirrorLayout->setSpacing(4);
mirrorLayout->setContentsMargins(0, 0, 0, 0);
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("mirror_actions", action);
action->setDefaultWidget(mirrorActions);
view->actionCollection()->addAction("mirror_actions", action);
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("workspaces", action);
view->actionCollection()->addAction("workspaces", action);
action->setDefaultWidget(m_workspaceWidget);
if (!cfg.toolOptionsInDocker()) {
m_toolOptionsPopup = new KisToolOptionsPopup();
m_toolOptionsPopupButton->setPopupWidget(m_toolOptionsPopup);
m_toolOptionsPopup->switchDetached(false);
}
m_savePresetWidget = new KisPresetSaveWidget(this);
m_presetsPopup = new KisPaintOpPresetsPopup(m_resourceProvider, m_favoriteResourceManager, m_savePresetWidget);
m_brushEditorPopupButton->setPopupWidget(m_presetsPopup);
m_presetsPopup->parentWidget()->setWindowTitle(i18n("Brush Editor"));
connect(m_presetsPopup, SIGNAL(brushEditorShown()), SLOT(slotUpdateOptionsWidgetPopup()));
connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_presetsPopup, SLOT(updateThemedIcons()));
m_presetsChooserPopup = new KisPaintOpPresetsChooserPopup();
m_presetsChooserPopup->setMinimumHeight(550);
m_presetsChooserPopup->setMinimumWidth(450);
m_presetSelectorPopupButton->setPopupWidget(m_presetsChooserPopup);
m_currCompositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id();
slotNodeChanged(view->activeNode());
// Get all the paintops
QList keys = KisPaintOpRegistry::instance()->keys();
QList factoryList;
Q_FOREACH (const QString & paintopId, keys) {
factoryList.append(KisPaintOpRegistry::instance()->get(paintopId));
}
m_presetsPopup->setPaintOpList(factoryList);
connect(m_presetsPopup , SIGNAL(paintopActivated(QString)) , SLOT(slotSetPaintop(QString)));
connect(m_presetsPopup , SIGNAL(defaultPresetClicked()) , SLOT(slotSetupDefaultPreset()));
connect(m_presetsPopup , SIGNAL(signalResourceSelected(KoResource*)), SLOT(resourceSelected(KoResource*)));
connect(m_presetsPopup , SIGNAL(reloadPresetClicked()) , SLOT(slotReloadPreset()));
connect(m_presetsPopup , SIGNAL(dirtyPresetToggled(bool)) , SLOT(slotDirtyPresetToggled(bool)));
connect(m_presetsPopup , SIGNAL(eraserBrushSizeToggled(bool)) , SLOT(slotEraserBrushSizeToggled(bool)));
connect(m_presetsPopup , SIGNAL(eraserBrushOpacityToggled(bool)) , SLOT(slotEraserBrushOpacityToggled(bool)));
connect(m_presetsPopup, SIGNAL(createPresetFromScratch(QString)), this, SLOT(slotCreatePresetFromScratch(QString)));
connect(m_presetsChooserPopup, SIGNAL(resourceSelected(KoResource*)) , SLOT(resourceSelected(KoResource*)));
connect(m_presetsChooserPopup, SIGNAL(resourceClicked(KoResource*)) , SLOT(resourceSelected(KoResource*)));
connect(m_resourceProvider , SIGNAL(sigNodeChanged(KisNodeSP)) , SLOT(slotNodeChanged(KisNodeSP)));
connect(m_cmbCompositeOp , SIGNAL(currentIndexChanged(int)) , SLOT(slotSetCompositeMode(int)));
connect(m_eraseAction , SIGNAL(toggled(bool)) , SLOT(slotToggleEraseMode(bool)));
connect(alphaLockAction , SIGNAL(toggled(bool)) , SLOT(slotToggleAlphaLockMode(bool)));
m_disablePressureAction = m_viewManager->actionManager()->createAction("disable_pressure");
connect(m_disablePressureAction , SIGNAL(toggled(bool)) , SLOT(slotDisablePressureMode(bool)));
m_disablePressureAction->setChecked(true);
connect(m_hMirrorAction , SIGNAL(toggled(bool)) , SLOT(slotHorizontalMirrorChanged(bool)));
connect(m_vMirrorAction , SIGNAL(toggled(bool)) , SLOT(slotVerticalMirrorChanged(bool)));
connect(m_reloadAction , SIGNAL(triggered()) , SLOT(slotReloadPreset()));
connect(m_sliderChooser[0]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed()));
connect(m_sliderChooser[0]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed()));
connect(m_sliderChooser[0]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed()));
connect(m_sliderChooser[1]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed()));
connect(m_sliderChooser[1]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed()));
connect(m_sliderChooser[1]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed()));
connect(m_sliderChooser[2]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed()));
connect(m_sliderChooser[2]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed()));
connect(m_sliderChooser[2]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed()));
//Needed to connect canvas to favorite resource manager
connect(m_viewManager->resourceProvider(), SIGNAL(sigFGColorChanged(KoColor)), SLOT(slotUnsetEraseMode()));
connect(m_resourceProvider, SIGNAL(sigFGColorUsed(KoColor)), m_favoriteResourceManager, SLOT(slotAddRecentColor(KoColor)));
connect(m_resourceProvider, SIGNAL(sigFGColorChanged(KoColor)), m_favoriteResourceManager, SLOT(slotChangeFGColorSelector(KoColor)));
connect(m_resourceProvider, SIGNAL(sigBGColorChanged(KoColor)), m_favoriteResourceManager, SLOT(slotSetBGColor(KoColor)));
// cold initialization
m_favoriteResourceManager->slotChangeFGColorSelector(m_resourceProvider->fgColor());
m_favoriteResourceManager->slotSetBGColor(m_resourceProvider->bgColor());
connect(m_favoriteResourceManager, SIGNAL(sigSetFGColor(KoColor)), m_resourceProvider, SLOT(slotSetFGColor(KoColor)));
connect(m_favoriteResourceManager, SIGNAL(sigSetBGColor(KoColor)), m_resourceProvider, SLOT(slotSetBGColor(KoColor)));
connect(m_favoriteResourceManager, SIGNAL(sigEnableChangeColor(bool)), m_resourceProvider, SLOT(slotResetEnableFGChange(bool)));
connect(view->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateSelectionIcon()));
connect(m_resourceProvider->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
this, SLOT(slotCanvasResourceChanged(int,QVariant)));
slotInputDeviceChanged(KoToolManager::instance()->currentInputDevice());
KisPaintOpPresetResourceServer *rserver = KisResourceServerProvider::instance()->paintOpPresetServer();
m_eraserName = "eraser_circle";
m_defaultPresetName = "basic_tip_default";
bool foundEraser = false;
bool foundTip = false;
for (int i=0; iresourceCount(); i++) {
KisPaintOpPresetSP resource = rserver->resources().at(i);
if (resource->name().toLower().contains("eraser_circle")) {
m_eraserName = resource->name();
foundEraser = true;
} else if (foundEraser == false && (resource->name().toLower().contains("eraser") ||
resource->filename().toLower().contains("eraser"))) {
m_eraserName = resource->name();
foundEraser = true;
}
if (resource->name().toLower().contains("basic_tip_default")) {
m_defaultPresetName = resource->name();
foundTip = true;
} else if (foundTip == false && (resource->name().toLower().contains("default") ||
resource->filename().toLower().contains("default"))) {
m_defaultPresetName = resource->name();
foundTip = true;
}
}
}
KisPaintopBox::~KisPaintopBox()
{
KisConfig cfg(false);
QMapIterator iter(m_tabletToolMap);
while (iter.hasNext()) {
iter.next();
//qDebug() << "Writing last used preset for" << iter.key().pointer << iter.key().uniqueID << iter.value().preset->name();
if ((iter.key().pointer) == QTabletEvent::Eraser) {
cfg.writeEntry(QString("LastEraser_%1").arg(iter.key().uniqueID) , iter.value().preset->name());
}
else {
cfg.writeEntry(QString("LastPreset_%1").arg(iter.key().uniqueID) , iter.value().preset->name());
}
}
// Do not delete the widget, since it is global to the application, not owned by the view
m_presetsPopup->setPaintOpSettingsWidget(0);
qDeleteAll(m_paintopOptionWidgets);
delete m_favoriteResourceManager;
for (int i = 0; i < 3; ++i) {
delete m_sliderChooser[i];
}
}
void KisPaintopBox::restoreResource(KoResource* resource)
{
KisPaintOpPreset* preset = dynamic_cast(resource);
if (preset) {
setCurrentPaintop(preset);
m_presetsPopup->setPresetImage(preset->image());
m_presetsPopup->resourceSelected(resource);
}
}
void KisPaintopBox::newOptionWidgets(const QList > &optionWidgetList)
{
if (m_toolOptionsPopup) {
m_toolOptionsPopup->newOptionWidgets(optionWidgetList);
}
}
void KisPaintopBox::resourceSelected(KoResource* resource)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_optionWidget);
m_presetsPopup->setCreatingBrushFromScratch(false); // show normal UI elements when we are not creating
KisPaintOpPreset* preset = dynamic_cast(resource);
if (preset && preset != m_resourceProvider->currentPreset()) {
if (!preset->settings()->isLoadable())
return;
if (!m_dirtyPresetsEnabled) {
KisSignalsBlocker blocker(m_optionWidget);
if (!preset->load()) {
warnKrita << "failed to load the preset.";
}
}
//qDebug() << "resourceSelected" << resource->name();
setCurrentPaintop(preset);
m_presetsPopup->setPresetImage(preset->image());
m_presetsPopup->resourceSelected(resource);
}
}
void KisPaintopBox::setCurrentPaintop(const KoID& paintop)
{
KisPaintOpPresetSP preset = activePreset(paintop);
Q_ASSERT(preset && preset->settings());
//qDebug() << "setCurrentPaintop();" << paintop << preset;
setCurrentPaintop(preset);
}
void KisPaintopBox::setCurrentPaintop(KisPaintOpPresetSP preset)
{
//qDebug() << "setCurrentPaintop(); " << preset->name();
if (preset == m_resourceProvider->currentPreset()) {
if (preset == m_tabletToolMap[m_currTabletToolID].preset) {
return;
}
}
Q_ASSERT(preset);
const KoID& paintop = preset->paintOp();
m_presetConnections.clear();
if (m_resourceProvider->currentPreset()) {
m_resourceProvider->setPreviousPaintOpPreset(m_resourceProvider->currentPreset());
if (m_optionWidget) {
m_optionWidget->hide();
}
}
if (!m_paintopOptionWidgets.contains(paintop))
m_paintopOptionWidgets[paintop] = KisPaintOpRegistry::instance()->get(paintop.id())->createConfigWidget(this);
m_optionWidget = m_paintopOptionWidgets[paintop];
KisSignalsBlocker b(m_optionWidget);
preset->setOptionsWidget(m_optionWidget);
m_optionWidget->setImage(m_viewManager->image());
m_optionWidget->setNode(m_viewManager->activeNode());
m_presetsPopup->setPaintOpSettingsWidget(m_optionWidget);
m_resourceProvider->setPaintOpPreset(preset);
Q_ASSERT(m_optionWidget && m_presetSelectorPopupButton);
m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigConfigurationUpdated()), this, SLOT(slotGuiChangedCurrentPreset()));
m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigSaveLockedConfig(KisPropertiesConfigurationSP)), this, SLOT(slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP)));
m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigDropLockedConfig(KisPropertiesConfigurationSP)), this, SLOT(slotDropLockedOption(KisPropertiesConfigurationSP)));
// load the current brush engine icon for the brush editor toolbar button
m_brushEditorPopupButton->slotSetItem(preset.data());
m_presetsPopup->setCurrentPaintOpId(paintop.id());
////qDebug() << "\tsetting the new preset for" << m_currTabletToolID.uniqueID << "to" << preset->name();
m_paintOpPresetMap[m_resourceProvider->currentPreset()->paintOp()] = preset;
m_tabletToolMap[m_currTabletToolID].preset = preset;
m_tabletToolMap[m_currTabletToolID].paintOpID = preset->paintOp();
if (m_presetsPopup->currentPaintOpId() != paintop.id()) {
// Must change the paintop as the current one is not supported
// by the new colorspace.
dbgKrita << "current paintop " << paintop.name() << " was not set, not supported by colorspace";
}
}
void KisPaintopBox::slotUpdateOptionsWidgetPopup()
{
KisPaintOpPresetSP preset = m_resourceProvider->currentPreset();
KIS_SAFE_ASSERT_RECOVER_RETURN(preset);
KIS_SAFE_ASSERT_RECOVER_RETURN(m_optionWidget);
m_optionWidget->setConfigurationSafe(preset->settings());
m_presetsPopup->resourceSelected(preset.data());
m_presetsPopup->updateViewSettings();
// the m_viewManager->image() is set earlier, but the reference will be missing when the stamp button is pressed
// need to later do some research on how and when we should be using weak shared pointers (WSP) that creates this situation
m_optionWidget->setImage(m_viewManager->image());
}
KisPaintOpPresetSP KisPaintopBox::defaultPreset(const KoID& paintOp)
{
QString defaultName = paintOp.id() + ".kpp";
QString path = KoResourcePaths::findResource("kis_defaultpresets", defaultName);
KisPaintOpPresetSP preset = new KisPaintOpPreset(path);
if (!preset->load()) {
preset = KisPaintOpRegistry::instance()->defaultPreset(paintOp);
}
Q_ASSERT(preset);
Q_ASSERT(preset->valid());
return preset;
}
KisPaintOpPresetSP KisPaintopBox::activePreset(const KoID& paintOp)
{
if (m_paintOpPresetMap[paintOp] == 0) {
m_paintOpPresetMap[paintOp] = defaultPreset(paintOp);
}
return m_paintOpPresetMap[paintOp];
}
void KisPaintopBox::updateCompositeOp(QString compositeOpID)
{
if (!m_optionWidget) return;
KisSignalsBlocker blocker(m_optionWidget);
KisNodeSP node = m_resourceProvider->currentNode();
if (node && node->paintDevice()) {
if (!node->paintDevice()->colorSpace()->hasCompositeOp(compositeOpID))
compositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id();
{
KisSignalsBlocker b1(m_cmbCompositeOp);
m_cmbCompositeOp->selectCompositeOp(KoID(compositeOpID));
}
if (compositeOpID != m_currCompositeOpID) {
m_currCompositeOpID = compositeOpID;
}
if (compositeOpID == COMPOSITE_ERASE || m_resourceProvider->eraserMode()) {
m_eraseModeButton->setChecked(true);
}
else {
m_eraseModeButton->setChecked(false);
}
}
}
void KisPaintopBox::setWidgetState(int flags)
{
if (flags & (ENABLE_COMPOSITEOP | DISABLE_COMPOSITEOP)) {
m_cmbCompositeOp->setEnabled(flags & ENABLE_COMPOSITEOP);
m_eraseModeButton->setEnabled(flags & ENABLE_COMPOSITEOP);
}
if (flags & (ENABLE_PRESETS | DISABLE_PRESETS)) {
m_presetSelectorPopupButton->setEnabled(flags & ENABLE_PRESETS);
m_brushEditorPopupButton->setEnabled(flags & ENABLE_PRESETS);
}
for (int i = 0; i < 3; ++i) {
if (flags & (ENABLE_OPACITY | DISABLE_OPACITY))
m_sliderChooser[i]->getWidget("opacity")->setEnabled(flags & ENABLE_OPACITY);
if (flags & (ENABLE_FLOW | DISABLE_FLOW))
m_sliderChooser[i]->getWidget("flow")->setEnabled(flags & ENABLE_FLOW);
if (flags & (ENABLE_SIZE | DISABLE_SIZE))
m_sliderChooser[i]->getWidget("size")->setEnabled(flags & ENABLE_SIZE);
}
}
void KisPaintopBox::setSliderValue(const QString& sliderID, qreal value)
{
for (int i = 0; i < 3; ++i) {
KisDoubleSliderSpinBox* slider = m_sliderChooser[i]->getWidget(sliderID);
KisSignalsBlocker b(slider);
if (sliderID == "opacity" || sliderID == "flow") { // opacity and flows UI stored at 0-100%
slider->setValue(value*100);
} else {
slider->setValue(value); // brush size
}
}
}
void KisPaintopBox::slotSetPaintop(const QString& paintOpId)
{
if (KisPaintOpRegistry::instance()->get(paintOpId) != 0) {
KoID id(paintOpId, KisPaintOpRegistry::instance()->get(paintOpId)->name());
//qDebug() << "slotsetpaintop" << id;
setCurrentPaintop(id);
}
}
void KisPaintopBox::slotInputDeviceChanged(const KoInputDevice& inputDevice)
{
TabletToolMap::iterator toolData = m_tabletToolMap.find(inputDevice);
//qDebug() << "slotInputDeviceChanged()" << inputDevice.device() << inputDevice.uniqueTabletId();
m_currTabletToolID = TabletToolID(inputDevice);
if (toolData == m_tabletToolMap.end()) {
KisConfig cfg(true);
KisPaintOpPresetResourceServer *rserver = KisResourceServerProvider::instance()->paintOpPresetServer();
KisPaintOpPresetSP preset;
if (inputDevice.pointer() == QTabletEvent::Eraser) {
preset = rserver->resourceByName(cfg.readEntry(QString("LastEraser_%1").arg(inputDevice.uniqueTabletId()), m_eraserName));
}
else {
preset = rserver->resourceByName(cfg.readEntry(QString("LastPreset_%1").arg(inputDevice.uniqueTabletId()), m_defaultPresetName));
//if (preset)
//qDebug() << "found stored preset " << preset->name() << "for" << inputDevice.uniqueTabletId();
//else
//qDebug() << "no preset found for" << inputDevice.uniqueTabletId();
}
if (!preset) {
preset = rserver->resourceByName(m_defaultPresetName);
}
if (preset) {
//qDebug() << "inputdevicechanged 1" << preset;
setCurrentPaintop(preset);
}
}
else {
if (toolData->preset) {
//qDebug() << "inputdevicechanged 2" << toolData->preset;
setCurrentPaintop(toolData->preset);
}
else {
//qDebug() << "inputdevicechanged 3" << toolData->paintOpID;
setCurrentPaintop(toolData->paintOpID);
}
}
}
void KisPaintopBox::slotCreatePresetFromScratch(QString paintop)
{
//First try to select an available default preset for that engine. If it doesn't exist, then
//manually set the engine to use a new preset.
KoID id(paintop, KisPaintOpRegistry::instance()->get(paintop)->name());
KisPaintOpPresetSP preset = defaultPreset(id);
slotSetPaintop(paintop); // change the paintop settings area and update the UI
if (!preset) {
m_presetsPopup->setCreatingBrushFromScratch(true); // disable UI elements while creating from scratch
preset = m_resourceProvider->currentPreset();
} else {
m_resourceProvider->setPaintOpPreset(preset);
preset->setOptionsWidget(m_optionWidget);
}
m_presetsPopup->resourceSelected(preset.data()); // this helps update the UI on the brush editor
}
void KisPaintopBox::slotCanvasResourceChanged(int key, const QVariant &value)
{
if (m_viewManager) {
sender()->blockSignals(true);
KisPaintOpPresetSP preset = m_viewManager->resourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value();
if (preset && m_resourceProvider->currentPreset()->name() != preset->name()) {
QString compositeOp = preset->settings()->getString("CompositeOp");
updateCompositeOp(compositeOp);
resourceSelected(preset.data());
}
/**
* Update currently selected preset in both the popup widgets
*/
m_presetsChooserPopup->canvasResourceChanged(preset);
m_presetsPopup->currentPresetChanged(preset);
if (key == KisCanvasResourceProvider::CurrentCompositeOp) {
if (m_resourceProvider->currentCompositeOp() != m_currCompositeOpID) {
updateCompositeOp(m_resourceProvider->currentCompositeOp());
}
}
if (key == KisCanvasResourceProvider::Size) {
setSliderValue("size", m_resourceProvider->size());
}
if (key == KisCanvasResourceProvider::Opacity) {
setSliderValue("opacity", m_resourceProvider->opacity());
}
if (key == KisCanvasResourceProvider::Flow) {
setSliderValue("flow", m_resourceProvider->flow());
}
if (key == KisCanvasResourceProvider::EraserMode) {
m_eraseAction->setChecked(value.toBool());
}
if (key == KisCanvasResourceProvider::DisablePressure) {
m_disablePressureAction->setChecked(value.toBool());
}
+ if (key == KisCanvasResourceProvider::MirrorHorizontal) {
+ m_hMirrorAction->setChecked(value.toBool());
+ }
+
+ if (key == KisCanvasResourceProvider::MirrorVertical) {
+ m_vMirrorAction->setChecked(value.toBool());
+ }
+
sender()->blockSignals(false);
}
}
void KisPaintopBox::slotUpdatePreset()
{
if (!m_resourceProvider->currentPreset()) return;
// block updates of avoid some over updating of the option widget
m_blockUpdate = true;
setSliderValue("size", m_resourceProvider->size());
{
qreal opacity = m_resourceProvider->currentPreset()->settings()->paintOpOpacity();
m_resourceProvider->setOpacity(opacity);
setSliderValue("opacity", opacity);
setWidgetState(ENABLE_OPACITY);
}
{
setSliderValue("flow", m_resourceProvider->currentPreset()->settings()->paintOpFlow());
setWidgetState(ENABLE_FLOW);
}
{
updateCompositeOp(m_resourceProvider->currentPreset()->settings()->paintOpCompositeOp());
setWidgetState(ENABLE_COMPOSITEOP);
}
m_blockUpdate = false;
}
void KisPaintopBox::slotSetupDefaultPreset()
{
KisPaintOpPresetSP preset = defaultPreset(m_resourceProvider->currentPreset()->paintOp());
preset->setOptionsWidget(m_optionWidget);
m_resourceProvider->setPaintOpPreset(preset);
// tell the brush editor that the resource has changed
// so it can update everything
m_presetsPopup->resourceSelected(preset.data());
}
void KisPaintopBox::slotNodeChanged(const KisNodeSP node)
{
if (m_previousNode.isValid() && m_previousNode->paintDevice())
disconnect(m_previousNode->paintDevice().data(), SIGNAL(colorSpaceChanged(const KoColorSpace*)), this, SLOT(slotColorSpaceChanged(const KoColorSpace*)));
// Reconnect colorspace change of node
if (node && node->paintDevice()) {
connect(node->paintDevice().data(), SIGNAL(colorSpaceChanged(const KoColorSpace*)), this, SLOT(slotColorSpaceChanged(const KoColorSpace*)));
m_resourceProvider->setCurrentCompositeOp(m_currCompositeOpID);
m_previousNode = node;
slotColorSpaceChanged(node->colorSpace());
}
if (m_optionWidget) {
m_optionWidget->setNode(node);
}
}
void KisPaintopBox::slotColorSpaceChanged(const KoColorSpace* colorSpace)
{
m_cmbCompositeOp->validate(colorSpace);
}
void KisPaintopBox::slotToggleEraseMode(bool checked)
{
const bool oldEraserMode = m_resourceProvider->eraserMode();
m_resourceProvider->setEraserMode(checked);
if (oldEraserMode != checked && m_eraserBrushSizeEnabled) {
const qreal currentSize = m_resourceProvider->size();
KisPaintOpSettingsSP settings = m_resourceProvider->currentPreset()->settings();
// remember brush size. set the eraser size to the normal brush size if not set
if (checked) {
settings->setSavedBrushSize(currentSize);
if (qFuzzyIsNull(settings->savedEraserSize())) {
settings->setSavedEraserSize(currentSize);
}
} else {
settings->setSavedEraserSize(currentSize);
if (qFuzzyIsNull(settings->savedBrushSize())) {
settings->setSavedBrushSize(currentSize);
}
}
//update value in UI (this is the main place the value is 'stored' in memory)
qreal newSize = checked ? settings->savedEraserSize() : settings->savedBrushSize();
m_resourceProvider->setSize(newSize);
}
if (oldEraserMode != checked && m_eraserBrushOpacityEnabled) {
const qreal currentOpacity = m_resourceProvider->opacity();
KisPaintOpSettingsSP settings = m_resourceProvider->currentPreset()->settings();
// remember brush opacity. set the eraser opacity to the normal brush opacity if not set
if (checked) {
settings->setSavedBrushOpacity(currentOpacity);
if (qFuzzyIsNull(settings->savedEraserOpacity())) {
settings->setSavedEraserOpacity(currentOpacity);
}
} else {
settings->setSavedEraserOpacity(currentOpacity);
if (qFuzzyIsNull(settings->savedBrushOpacity())) {
settings->setSavedBrushOpacity(currentOpacity);
}
}
//update value in UI (this is the main place the value is 'stored' in memory)
qreal newOpacity = checked ? settings->savedEraserOpacity() : settings->savedBrushOpacity();
m_resourceProvider->setOpacity(newOpacity);
}
}
void KisPaintopBox::slotSetCompositeMode(int index)
{
Q_UNUSED(index);
QString compositeOp = m_cmbCompositeOp->selectedCompositeOp().id();
m_resourceProvider->setCurrentCompositeOp(compositeOp);
}
void KisPaintopBox::slotHorizontalMirrorChanged(bool value)
{
m_resourceProvider->setMirrorHorizontal(value);
}
void KisPaintopBox::slotVerticalMirrorChanged(bool value)
{
m_resourceProvider->setMirrorVertical(value);
}
void KisPaintopBox::sliderChanged(int n)
{
if (!m_optionWidget) // widget will not exist if the are no documents open
return;
KisSignalsBlocker blocker(m_optionWidget);
// flow and opacity are shown as 0-100% on the UI, but their data is actually 0-1. Convert those two values
// back for further work
qreal opacity = m_sliderChooser[n]->getWidget("opacity")->value()/100;
qreal flow = m_sliderChooser[n]->getWidget("flow")->value()/100;
qreal size = m_sliderChooser[n]->getWidget("size")->value();
setSliderValue("opacity", opacity);
setSliderValue("flow" , flow);
setSliderValue("size" , size);
if (m_presetsEnabled) {
// IMPORTANT: set the PaintOp size before setting the other properties
// it won't work the other way
// TODO: why?!
m_resourceProvider->setSize(size);
m_resourceProvider->setOpacity(opacity);
m_resourceProvider->setFlow(flow);
KisLockedPropertiesProxySP propertiesProxy = KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(m_resourceProvider->currentPreset()->settings());
propertiesProxy->setProperty("OpacityValue", opacity);
propertiesProxy->setProperty("FlowValue", flow);
m_optionWidget->setConfigurationSafe(m_resourceProvider->currentPreset()->settings().data());
} else {
m_resourceProvider->setOpacity(opacity);
}
m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data());
}
void KisPaintopBox::slotSlider1Changed()
{
sliderChanged(0);
}
void KisPaintopBox::slotSlider2Changed()
{
sliderChanged(1);
}
void KisPaintopBox::slotSlider3Changed()
{
sliderChanged(2);
}
void KisPaintopBox::slotToolChanged(KoCanvasController* canvas, int toolId)
{
Q_UNUSED(canvas);
Q_UNUSED(toolId);
if (!m_viewManager->canvasBase()) return;
QString id = KoToolManager::instance()->activeToolId();
KisTool* tool = dynamic_cast(KoToolManager::instance()->toolById(m_viewManager->canvasBase(), id));
if (tool) {
int flags = tool->flags();
if (flags & KisTool::FLAG_USES_CUSTOM_COMPOSITEOP) {
setWidgetState(ENABLE_COMPOSITEOP | ENABLE_OPACITY);
} else {
setWidgetState(DISABLE_COMPOSITEOP | DISABLE_OPACITY);
}
if (flags & KisTool::FLAG_USES_CUSTOM_PRESET) {
setWidgetState(ENABLE_PRESETS);
slotUpdatePreset();
m_presetsEnabled = true;
} else {
setWidgetState(DISABLE_PRESETS);
m_presetsEnabled = false;
}
if (flags & KisTool::FLAG_USES_CUSTOM_SIZE) {
setWidgetState(ENABLE_SIZE | ENABLE_FLOW);
} else {
setWidgetState(DISABLE_SIZE | DISABLE_FLOW);
}
} else setWidgetState(DISABLE_ALL);
}
void KisPaintopBox::slotPreviousFavoritePreset()
{
if (!m_favoriteResourceManager) return;
QVector presets = m_favoriteResourceManager->favoritePresetList();
for (int i=0; i < presets.size(); ++i) {
if (m_resourceProvider->currentPreset() &&
m_resourceProvider->currentPreset()->name() == presets[i]->name()) {
if (i > 0) {
m_favoriteResourceManager->slotChangeActivePaintop(i - 1);
} else {
m_favoriteResourceManager->slotChangeActivePaintop(m_favoriteResourceManager->numFavoritePresets() - 1);
}
//floating message should have least 2 lines, otherwise
//preset thumbnail will be too small to distinguish
//(because size of image on floating message depends on amount of lines in msg)
m_viewManager->showFloatingMessage(
i18n("%1\nselected",
m_resourceProvider->currentPreset()->name()),
QIcon(QPixmap::fromImage(m_resourceProvider->currentPreset()->image())));
return;
}
}
}
void KisPaintopBox::slotNextFavoritePreset()
{
if (!m_favoriteResourceManager) return;
QVector presets = m_favoriteResourceManager->favoritePresetList();
for(int i = 0; i < presets.size(); ++i) {
if (m_resourceProvider->currentPreset()->name() == presets[i]->name()) {
if (i < m_favoriteResourceManager->numFavoritePresets() - 1) {
m_favoriteResourceManager->slotChangeActivePaintop(i + 1);
} else {
m_favoriteResourceManager->slotChangeActivePaintop(0);
}
m_viewManager->showFloatingMessage(
i18n("%1\nselected",
m_resourceProvider->currentPreset()->name()),
QIcon(QPixmap::fromImage(m_resourceProvider->currentPreset()->image())));
return;
}
}
}
void KisPaintopBox::slotSwitchToPreviousPreset()
{
if (m_resourceProvider->previousPreset()) {
//qDebug() << "slotSwitchToPreviousPreset();" << m_resourceProvider->previousPreset();
setCurrentPaintop(m_resourceProvider->previousPreset());
m_viewManager->showFloatingMessage(
i18n("%1\nselected",
m_resourceProvider->currentPreset()->name()),
QIcon(QPixmap::fromImage(m_resourceProvider->currentPreset()->image())));
}
}
void KisPaintopBox::slotUnsetEraseMode()
{
m_eraseAction->setChecked(false);
}
void KisPaintopBox::slotToggleAlphaLockMode(bool checked)
{
if (checked) {
m_alphaLockButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transparency-locked"));
} else {
m_alphaLockButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transparency-unlocked"));
}
m_resourceProvider->setGlobalAlphaLock(checked);
}
void KisPaintopBox::slotDisablePressureMode(bool checked)
{
if (checked) {
m_disablePressureAction->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure"));
} else {
m_disablePressureAction->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure_locked"));
}
m_resourceProvider->setDisablePressure(checked);
}
void KisPaintopBox::slotReloadPreset()
{
KisSignalsBlocker blocker(m_optionWidget);
//Here using the name and fetching the preset from the server was the only way the load was working. Otherwise it was not loading.
KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer();
KisPaintOpPresetSP preset = rserver->resourceByName(m_resourceProvider->currentPreset()->name());
if (preset) {
preset->load();
}
}
void KisPaintopBox::slotGuiChangedCurrentPreset() // Called only when UI is changed and not when preset is changed
{
KisPaintOpPresetSP preset = m_resourceProvider->currentPreset();
{
/**
* Here we postpone all the settings updates events until the entire writing
* operation will be finished. As soon as it is finished, the updates will be
* emitted happily (if there were any).
*/
KisPaintOpPreset::UpdatedPostponer postponer(preset.data());
QStringList preserveProperties;
preserveProperties << "lodUserAllowed";
preserveProperties << "lodSizeThreshold";
// clear all the properties before dumping the stuff into the preset,
// some of the options add the values incrementally
// (e.g. KisPaintOpUtils::RequiredBrushFilesListTag), therefore they
// may add up if we pass the same preset multiple times
preset->settings()->resetSettings(preserveProperties);
m_optionWidget->writeConfigurationSafe(const_cast(preset->settings().data()));
}
// we should also update the preset strip to update the status of the "dirty" mark
m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data());
// TODO!!!!!!!!
//m_presetsPopup->updateViewSettings();
}
void KisPaintopBox::slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP p)
{
QMapIterator i(p->getProperties());
while (i.hasNext()) {
i.next();
m_resourceProvider->currentPreset()->settings()->setProperty(i.key(), QVariant(i.value()));
if (m_resourceProvider->currentPreset()->settings()->hasProperty(i.key() + "_previous")) {
m_resourceProvider->currentPreset()->settings()->removeProperty(i.key() + "_previous");
}
}
slotGuiChangedCurrentPreset();
}
void KisPaintopBox::slotDropLockedOption(KisPropertiesConfigurationSP p)
{
KisSignalsBlocker blocker(m_optionWidget);
KisPaintOpPresetSP preset = m_resourceProvider->currentPreset();
{
KisPaintOpPreset::DirtyStateSaver dirtySaver(preset.data());
QMapIterator i(p->getProperties());
while (i.hasNext()) {
i.next();
if (preset->settings()->hasProperty(i.key() + "_previous")) {
preset->settings()->setProperty(i.key(), preset->settings()->getProperty(i.key() + "_previous"));
preset->settings()->removeProperty(i.key() + "_previous");
}
}
}
//slotUpdatePreset();
}
void KisPaintopBox::slotDirtyPresetToggled(bool value)
{
if (!value) {
slotReloadPreset();
m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data());
m_presetsPopup->updateViewSettings();
}
m_dirtyPresetsEnabled = value;
KisConfig cfg(false);
cfg.setUseDirtyPresets(m_dirtyPresetsEnabled);
}
void KisPaintopBox::slotEraserBrushSizeToggled(bool value)
{
m_eraserBrushSizeEnabled = value;
KisConfig cfg(false);
cfg.setUseEraserBrushSize(m_eraserBrushSizeEnabled);
}
void KisPaintopBox::slotEraserBrushOpacityToggled(bool value)
{
m_eraserBrushOpacityEnabled = value;
KisConfig cfg(false);
cfg.setUseEraserBrushOpacity(m_eraserBrushOpacityEnabled);
}
void KisPaintopBox::slotUpdateSelectionIcon()
{
m_hMirrorAction->setIcon(KisIconUtils::loadIcon("symmetry-horizontal"));
m_vMirrorAction->setIcon(KisIconUtils::loadIcon("symmetry-vertical"));
KisConfig cfg(true);
if (!cfg.toolOptionsInDocker() && m_toolOptionsPopupButton) {
m_toolOptionsPopupButton->setIcon(KisIconUtils::loadIcon("configure"));
}
m_presetSelectorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_01"));
m_brushEditorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02"));
m_workspaceWidget->setIcon(KisIconUtils::loadIcon("view-choose"));
m_eraseAction->setIcon(KisIconUtils::loadIcon("draw-eraser"));
m_reloadAction->setIcon(KisIconUtils::loadIcon("view-refresh"));
if (m_disablePressureAction->isChecked()) {
m_disablePressureAction->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure"));
} else {
m_disablePressureAction->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure_locked"));
}
}
void KisPaintopBox::slotLockXMirrorToggle(bool toggleLock) {
m_resourceProvider->setMirrorHorizontalLock(toggleLock);
}
void KisPaintopBox::slotLockYMirrorToggle(bool toggleLock) {
m_resourceProvider->setMirrorVerticalLock(toggleLock);
}
void KisPaintopBox::slotHideDecorationMirrorX(bool toggled) {
m_resourceProvider->setMirrorHorizontalHideDecorations(toggled);
}
void KisPaintopBox::slotHideDecorationMirrorY(bool toggled) {
m_resourceProvider->setMirrorVerticalHideDecorations(toggled);
}
void KisPaintopBox::slotMoveToCenterMirrorX() {
m_resourceProvider->mirrorHorizontalMoveCanvasToCenter();
}
void KisPaintopBox::slotMoveToCenterMirrorY() {
m_resourceProvider->mirrorVerticalMoveCanvasToCenter();
}
diff --git a/plugins/impex/libkra/kis_kra_loader.cpp b/plugins/impex/libkra/kis_kra_loader.cpp
index dd86b6ba8b..0c347eb449 100644
--- a/plugins/impex/libkra/kis_kra_loader.cpp
+++ b/plugins/impex/libkra/kis_kra_loader.cpp
@@ -1,1247 +1,1261 @@
/* This file is part of the KDE project
* Copyright (C) Boudewijn Rempt , (C) 2007
*
* 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 "kis_kra_loader.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "lazybrush/kis_colorize_mask.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "KisResourceServerProvider.h"
#include "kis_keyframe_channel.h"
#include
#include "KisReferenceImagesLayer.h"
#include "KisReferenceImage.h"
#include
#include "KisDocument.h"
#include "kis_config.h"
#include "kis_kra_tags.h"
#include "kis_kra_utils.h"
#include "kis_kra_load_visitor.h"
#include "kis_dom_utils.h"
#include "kis_image_animation_interface.h"
#include "kis_time_range.h"
#include "kis_grid_config.h"
#include "kis_guides_config.h"
#include "kis_image_config.h"
#include "KisProofingConfiguration.h"
#include "kis_layer_properties_icons.h"
#include "kis_node_view_color_scheme.h"
+#include "KisMirrorAxisConfig.h"
/*
Color model id comparison through the ages:
2.4 2.5 2.6 ideal
ALPHA ALPHA ALPHA ALPHAU8
CMYK CMYK CMYK CMYKAU8
CMYKAF32 CMYKAF32
CMYKA16 CMYKAU16 CMYKAU16
GRAYA GRAYA GRAYA GRAYAU8
GrayF32 GRAYAF32 GRAYAF32
GRAYA16 GRAYAU16 GRAYAU16
LABA LABA LABA LABAU16
LABAF32 LABAF32
LABAU8 LABAU8
RGBA RGBA RGBA RGBAU8
RGBA16 RGBA16 RGBA16 RGBAU16
RgbAF32 RGBAF32 RGBAF32
RgbAF16 RgbAF16 RGBAF16
XYZA16 XYZA16 XYZA16 XYZAU16
XYZA8 XYZA8 XYZAU8
XyzAF16 XyzAF16 XYZAF16
XyzAF32 XYZAF32 XYZAF32
YCbCrA YCBCRA8 YCBCRA8 YCBCRAU8
YCbCrAU16 YCBCRAU16 YCBCRAU16
YCBCRF32 YCBCRF32
*/
using namespace KRA;
struct KisKraLoader::Private
{
public:
KisDocument* document;
QString imageName; // used to be stored in the image, is now in the documentInfo block
QString imageComment; // used to be stored in the image, is now in the documentInfo block
QMap layerFilenames; // temp storage during loading
int syntaxVersion; // version of the fileformat we are loading
vKisNodeSP selectedNodes; // the nodes that were active when saving the document.
QMap assistantsFilenames;
QList assistants;
QMap keyframeFilenames;
QVector paletteFilenames;
QStringList errorMessages;
QStringList warningMessages;
};
void convertColorSpaceNames(QString &colorspacename, QString &profileProductName) {
if (colorspacename == "Grayscale + Alpha") {
colorspacename = "GRAYA";
profileProductName.clear();
}
else if (colorspacename == "RgbAF32") {
colorspacename = "RGBAF32";
profileProductName.clear();
}
else if (colorspacename == "RgbAF16") {
colorspacename = "RGBAF16";
profileProductName.clear();
}
else if (colorspacename == "CMYKA16") {
colorspacename = "CMYKAU16";
}
else if (colorspacename == "GrayF32") {
colorspacename = "GRAYAF32";
profileProductName.clear();
}
else if (colorspacename == "GRAYA16") {
colorspacename = "GRAYAU16";
}
else if (colorspacename == "XyzAF16") {
colorspacename = "XYZAF16";
profileProductName.clear();
}
else if (colorspacename == "XyzAF32") {
colorspacename = "XYZAF32";
profileProductName.clear();
}
else if (colorspacename == "YCbCrA") {
colorspacename = "YCBCRA8";
}
else if (colorspacename == "YCbCrAU16") {
colorspacename = "YCBCRAU16";
}
}
KisKraLoader::KisKraLoader(KisDocument * document, int syntaxVersion)
: m_d(new Private())
{
m_d->document = document;
m_d->syntaxVersion = syntaxVersion;
}
KisKraLoader::~KisKraLoader()
{
delete m_d;
}
KisImageSP KisKraLoader::loadXML(const KoXmlElement& element)
{
QString attr;
KisImageSP image = 0;
qint32 width;
qint32 height;
QString profileProductName;
double xres;
double yres;
QString colorspacename;
const KoColorSpace * cs;
if ((attr = element.attribute(MIME)) == NATIVE_MIMETYPE) {
if ((m_d->imageName = element.attribute(NAME)).isNull()) {
m_d->errorMessages << i18n("Image does not have a name.");
return KisImageSP(0);
}
if ((attr = element.attribute(WIDTH)).isNull()) {
m_d->errorMessages << i18n("Image does not specify a width.");
return KisImageSP(0);
}
width = KisDomUtils::toInt(attr);
if ((attr = element.attribute(HEIGHT)).isNull()) {
m_d->errorMessages << i18n("Image does not specify a height.");
return KisImageSP(0);
}
height = KisDomUtils::toInt(attr);
m_d->imageComment = element.attribute(DESCRIPTION);
xres = 100.0 / 72.0;
if (!(attr = element.attribute(X_RESOLUTION)).isNull()) {
qreal value = KisDomUtils::toDouble(attr);
if (value > 1.0) {
xres = value / 72.0;
}
}
yres = 100.0 / 72.0;
if (!(attr = element.attribute(Y_RESOLUTION)).isNull()) {
qreal value = KisDomUtils::toDouble(attr);
if (value > 1.0) {
yres = value / 72.0;
}
}
if ((colorspacename = element.attribute(COLORSPACE_NAME)).isNull()) {
// An old file: take a reasonable default.
// Krita didn't support anything else in those
// days anyway.
colorspacename = "RGBA";
}
profileProductName = element.attribute(PROFILE);
// A hack for an old colorspacename
convertColorSpaceNames(colorspacename, profileProductName);
QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id();
QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id();
if (profileProductName.isNull()) {
// no mention of profile so get default profile";
cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, "");
} else {
cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, profileProductName);
}
if (cs == 0) {
// try once more without the profile
cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, "");
if (cs == 0) {
m_d->errorMessages << i18n("Image specifies an unsupported color model: %1.", colorspacename);
return KisImageSP(0);
}
}
KisProofingConfigurationSP proofingConfig = KisImageConfig(true).defaultProofingconfiguration();
if (!(attr = element.attribute(PROOFINGPROFILENAME)).isNull()) {
proofingConfig->proofingProfile = attr;
}
if (!(attr = element.attribute(PROOFINGMODEL)).isNull()) {
proofingConfig->proofingModel = attr;
}
if (!(attr = element.attribute(PROOFINGDEPTH)).isNull()) {
proofingConfig->proofingDepth = attr;
}
if (!(attr = element.attribute(PROOFINGINTENT)).isNull()) {
proofingConfig->intent = (KoColorConversionTransformation::Intent) KisDomUtils::toInt(attr);
}
if (!(attr = element.attribute(PROOFINGADAPTATIONSTATE)).isNull()) {
proofingConfig->adaptationState = KisDomUtils::toDouble(attr);
}
if (m_d->document) {
image = new KisImage(m_d->document->createUndoStore(), width, height, cs, m_d->imageName);
}
else {
image = new KisImage(0, width, height, cs, m_d->imageName);
}
image->setResolution(xres, yres);
loadNodes(element, image, const_cast(image->rootLayer().data()));
KoXmlNode child;
for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) {
KoXmlElement e = child.toElement();
if(e.tagName() == CANVASPROJECTIONCOLOR) {
if (e.hasAttribute(COLORBYTEDATA)) {
QByteArray colorData = QByteArray::fromBase64(e.attribute(COLORBYTEDATA).toLatin1());
KoColor color((const quint8*)colorData.data(), image->colorSpace());
image->setDefaultProjectionColor(color);
}
}
if(e.tagName() == GLOBALASSISTANTSCOLOR) {
if (e.hasAttribute(SIMPLECOLORDATA)) {
QString colorData = e.attribute(SIMPLECOLORDATA);
m_d->document->setAssistantsGlobalColor(KisDomUtils::qStringToQColor(colorData));
}
}
if(e.tagName()== PROOFINGWARNINGCOLOR) {
QDomDocument dom;
KoXml::asQDomElement(dom, e);
QDomElement eq = dom.firstChildElement();
proofingConfig->warningColor = KoColor::fromXML(eq.firstChildElement(), Integer8BitsColorDepthID.id());
}
if (e.tagName().toLower() == "animation") {
loadAnimationMetadata(e, image);
}
}
image->setProofingConfiguration(proofingConfig);
for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) {
KoXmlElement e = child.toElement();
if(e.tagName() == "compositions") {
loadCompositions(e, image);
}
}
}
KoXmlNode child;
for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) {
KoXmlElement e = child.toElement();
if (e.tagName() == "grid") {
loadGrid(e);
} else if (e.tagName() == "guides") {
loadGuides(e);
+ } else if (e.tagName() == MIRROR_AXIS) {
+ loadMirrorAxis(e);
} else if (e.tagName() == "assistants") {
loadAssistantsList(e);
} else if (e.tagName() == "audio") {
loadAudio(e, image);
}
}
// reading palettes from XML
for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) {
QDomElement e = child.toElement();
if (e.tagName() == PALETTES) {
for (QDomElement paletteElement = e.lastChildElement();
!paletteElement.isNull();
paletteElement = paletteElement.previousSiblingElement()) {
QString paletteName = paletteElement.attribute("filename");
m_d->paletteFilenames.append(paletteName);
}
break;
}
}
return image;
}
void KisKraLoader::loadBinaryData(KoStore * store, KisImageSP image, const QString & uri, bool external)
{
// icc profile: if present, this overrides the profile product name loaded in loadXML.
QString location = external ? QString() : uri;
location += m_d->imageName + ICC_PATH;
if (store->hasFile(location)) {
if (store->open(location)) {
QByteArray data; data.resize(store->size());
bool res = (store->read(data.data(), store->size()) > -1);
store->close();
if (res) {
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(image->colorSpace()->colorModelId().id(), image->colorSpace()->colorDepthId().id(), data);
if (profile && profile->valid()) {
res = image->assignImageProfile(profile);
}
if (!res) {
const QString defaultProfileId = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(image->colorSpace()->id());
profile = KoColorSpaceRegistry::instance()->profileByName(defaultProfileId);
Q_ASSERT(profile && profile->valid());
image->assignImageProfile(profile);
}
}
}
}
//load the embed proofing profile, it only needs to be loaded into Krita, not assigned.
location = external ? QString() : uri;
location += m_d->imageName + ICC_PROOFING_PATH;
if (store->hasFile(location)) {
if (store->open(location)) {
QByteArray proofingData;
proofingData.resize(store->size());
bool proofingProfileRes = (store->read(proofingData.data(), store->size())>-1);
store->close();
KisProofingConfigurationSP proofingConfig = image->proofingConfiguration();
if (!proofingConfig) {
proofingConfig = KisImageConfig(true).defaultProofingconfiguration();
}
if (proofingProfileRes) {
const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->createColorProfile(proofingConfig->proofingModel, proofingConfig->proofingDepth, proofingData);
if (proofingProfile->valid()){
KoColorSpaceRegistry::instance()->addProfile(proofingProfile);
}
}
}
}
// Load the layers data: if there is a profile associated with a layer it will be set now.
KisKraLoadVisitor visitor(image, store, m_d->document->shapeController(), m_d->layerFilenames, m_d->keyframeFilenames, m_d->imageName, m_d->syntaxVersion);
if (external) {
visitor.setExternalUri(uri);
}
image->rootLayer()->accept(visitor);
if (!visitor.errorMessages().isEmpty()) {
m_d->errorMessages.append(visitor.errorMessages());
}
if (!visitor.warningMessages().isEmpty()) {
m_d->warningMessages.append(visitor.warningMessages());
}
// annotations
// exif
location = external ? QString() : uri;
location += m_d->imageName + EXIF_PATH;
if (store->hasFile(location)) {
QByteArray data;
store->open(location);
data = store->read(store->size());
store->close();
image->addAnnotation(KisAnnotationSP(new KisAnnotation("exif", "", data)));
}
// layer styles
location = external ? QString() : uri;
location += m_d->imageName + LAYER_STYLES_PATH;
if (store->hasFile(location)) {
KisPSDLayerStyleCollectionResource *collection =
new KisPSDLayerStyleCollectionResource("Embedded Styles.asl");
collection->setName(i18nc("Auto-generated layer style collection name for embedded styles (collection)", "<%1> (embedded)", m_d->imageName));
KIS_ASSERT_RECOVER_NOOP(!collection->valid());
store->open(location);
{
KoStoreDevice device(store);
device.open(QIODevice::ReadOnly);
/**
* ASL loading code cannot work with non-sequential IO devices,
* so convert the device beforehand!
*/
QByteArray buf = device.readAll();
QBuffer raDevice(&buf);
raDevice.open(QIODevice::ReadOnly);
collection->loadFromDevice(&raDevice);
}
store->close();
if (collection->valid()) {
KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer();
server->addResource(collection, false);
collection->assignAllLayerStyles(image->root());
} else {
warnKrita << "WARNING: Couldn't load layer styles library from .kra!";
delete collection;
}
}
if (m_d->document && m_d->document->documentInfo()->aboutInfo("title").isNull())
m_d->document->documentInfo()->setAboutInfo("title", m_d->imageName);
if (m_d->document && m_d->document->documentInfo()->aboutInfo("comment").isNull())
m_d->document->documentInfo()->setAboutInfo("comment", m_d->imageComment);
loadAssistants(store, uri, external);
}
void KisKraLoader::loadPalettes(KoStore *store, KisDocument *doc)
{
QList list;
Q_FOREACH (const QString &filename, m_d->paletteFilenames) {
KoColorSet *newPalette = new KoColorSet(filename);
store->open(m_d->imageName + PALETTE_PATH + filename);
QByteArray data = store->read(store->size());
newPalette->fromByteArray(data);
newPalette->setIsGlobal(false);
newPalette->setIsEditable(true);
store->close();
list.append(newPalette);
}
doc->setPaletteList(list);
}
vKisNodeSP KisKraLoader::selectedNodes() const
{
return m_d->selectedNodes;
}
QList KisKraLoader::assistants() const
{
return m_d->assistants;
}
QStringList KisKraLoader::errorMessages() const
{
return m_d->errorMessages;
}
QStringList KisKraLoader::warningMessages() const
{
return m_d->warningMessages;
}
void KisKraLoader::loadAssistants(KoStore *store, const QString &uri, bool external)
{
QString file_path;
QString location;
QMap handleMap;
KisPaintingAssistant* assistant = 0;
const QColor globalColor = m_d->document->assistantsGlobalColor();
QMap::const_iterator loadedAssistant = m_d->assistantsFilenames.constBegin();
while (loadedAssistant != m_d->assistantsFilenames.constEnd()){
const KisPaintingAssistantFactory* factory = KisPaintingAssistantFactoryRegistry::instance()->get(loadedAssistant.value());
if (factory) {
assistant = factory->createPaintingAssistant();
location = external ? QString() : uri;
location += m_d->imageName + ASSISTANTS_PATH;
file_path = location + loadedAssistant.key();
assistant->loadXml(store, handleMap, file_path);
assistant->setAssistantGlobalColorCache(globalColor);
//If an assistant has too few handles than it should according to it's own setup, just don't load it//
if (assistant->handles().size()==assistant->numHandles()){
m_d->assistants.append(toQShared(assistant));
}
}
loadedAssistant++;
}
}
void KisKraLoader::loadAnimationMetadata(const KoXmlElement &element, KisImageSP image)
{
QDomDocument qDom;
KoXml::asQDomElement(qDom, element);
QDomElement qElement = qDom.firstChildElement();
float framerate;
KisTimeRange range;
int currentTime;
KisImageAnimationInterface *animation = image->animationInterface();
if (KisDomUtils::loadValue(qElement, "framerate", &framerate)) {
animation->setFramerate(framerate);
}
if (KisDomUtils::loadValue(qElement, "range", &range)) {
animation->setFullClipRange(range);
}
if (KisDomUtils::loadValue(qElement, "currentTime", ¤tTime)) {
animation->switchCurrentTimeAsync(currentTime);
}
}
KisNodeSP KisKraLoader::loadNodes(const KoXmlElement& element, KisImageSP image, KisNodeSP parent)
{
KoXmlNode node = element.firstChild();
KoXmlNode child;
if (!node.isNull()) {
if (node.isElement()) {
if (node.nodeName().toUpper() == LAYERS.toUpper() || node.nodeName().toUpper() == MASKS.toUpper()) {
for (child = node.lastChild(); !child.isNull(); child = child.previousSibling()) {
KisNodeSP node = loadNode(child.toElement(), image);
if (node) {
image->nextLayerName(); // Make sure the nameserver is current with the number of nodes.
image->addNode(node, parent);
if (node->inherits("KisLayer") && KoXml::childNodesCount(child) > 0) {
loadNodes(child.toElement(), image, node);
}
}
}
}
}
}
return parent;
}
KisNodeSP KisKraLoader::loadNode(const KoXmlElement& element, KisImageSP image)
{
// Nota bene: If you add new properties to layers, you should
// ALWAYS define a default value in case the property is not
// present in the layer definition: this helps a LOT with backward
// compatibility.
QString name = element.attribute(NAME, "No Name");
QUuid id = QUuid(element.attribute(UUID, QUuid().toString()));
qint32 x = element.attribute(X, "0").toInt();
qint32 y = element.attribute(Y, "0").toInt();
qint32 opacity = element.attribute(OPACITY, QString::number(OPACITY_OPAQUE_U8)).toInt();
if (opacity < OPACITY_TRANSPARENT_U8) opacity = OPACITY_TRANSPARENT_U8;
if (opacity > OPACITY_OPAQUE_U8) opacity = OPACITY_OPAQUE_U8;
const KoColorSpace* colorSpace = 0;
if ((element.attribute(COLORSPACE_NAME)).isNull()) {
dbgFile << "No attribute color space for layer: " << name;
colorSpace = image->colorSpace();
}
else {
QString colorspacename = element.attribute(COLORSPACE_NAME);
QString profileProductName;
convertColorSpaceNames(colorspacename, profileProductName);
QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id();
QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id();
dbgFile << "Searching color space: " << colorspacename << colorspaceModel << colorspaceDepth << " for layer: " << name;
// use default profile - it will be replaced later in completeLoading
colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, "");
dbgFile << "found colorspace" << colorSpace;
if (!colorSpace) {
m_d->warningMessages << i18n("Layer %1 specifies an unsupported color model: %2.", name, colorspacename);
return 0;
}
}
const bool visible = element.attribute(VISIBLE, "1") == "0" ? false : true;
const bool locked = element.attribute(LOCKED, "0") == "0" ? false : true;
const bool collapsed = element.attribute(COLLAPSED, "0") == "0" ? false : true;
int colorLabelIndex = element.attribute(COLOR_LABEL, "0").toInt();
QVector labels = KisNodeViewColorScheme::instance()->allColorLabels();
if (colorLabelIndex >= labels.size()) {
colorLabelIndex = labels.size() - 1;
}
// Now find out the layer type and do specific handling
QString nodeType;
if (m_d->syntaxVersion == 1) {
nodeType = element.attribute("layertype");
if (nodeType.isEmpty()) {
nodeType = PAINT_LAYER;
}
}
else {
nodeType = element.attribute(NODE_TYPE);
}
if (nodeType.isEmpty()) {
m_d->warningMessages << i18n("Layer %1 has an unsupported type.", name);
return 0;
}
KisNodeSP node = 0;
if (nodeType == PAINT_LAYER)
node = loadPaintLayer(element, image, name, colorSpace, opacity);
else if (nodeType == GROUP_LAYER)
node = loadGroupLayer(element, image, name, colorSpace, opacity);
else if (nodeType == ADJUSTMENT_LAYER)
node = loadAdjustmentLayer(element, image, name, colorSpace, opacity);
else if (nodeType == SHAPE_LAYER)
node = loadShapeLayer(element, image, name, colorSpace, opacity);
else if (nodeType == GENERATOR_LAYER)
node = loadGeneratorLayer(element, image, name, colorSpace, opacity);
else if (nodeType == CLONE_LAYER)
node = loadCloneLayer(element, image, name, colorSpace, opacity);
else if (nodeType == FILTER_MASK)
node = loadFilterMask(element);
else if (nodeType == TRANSFORM_MASK)
node = loadTransformMask(element);
else if (nodeType == TRANSPARENCY_MASK)
node = loadTransparencyMask(element);
else if (nodeType == SELECTION_MASK)
node = loadSelectionMask(image, element);
else if (nodeType == COLORIZE_MASK)
node = loadColorizeMask(image, element, colorSpace);
else if (nodeType == FILE_LAYER)
node = loadFileLayer(element, image, name, opacity);
else if (nodeType == REFERENCE_IMAGES_LAYER)
node = loadReferenceImagesLayer(element, image);
else {
m_d->warningMessages << i18n("Layer %1 has an unsupported type: %2.", name, nodeType);
return 0;
}
// Loading the node went wrong. Return empty node and leave to
// upstream to complain to the user
if (!node) {
m_d->warningMessages << i18n("Failure loading layer %1 of type: %2.", name, nodeType);
return 0;
}
node->setVisible(visible, true);
node->setUserLocked(locked);
node->setCollapsed(collapsed);
node->setColorLabelIndex(colorLabelIndex);
node->setX(x);
node->setY(y);
node->setName(name);
if (! id.isNull()) // if no uuid in file, new one has been generated already
node->setUuid(id);
if (node->inherits("KisLayer") || node->inherits("KisColorizeMask")) {
QString compositeOpName = element.attribute(COMPOSITE_OP, "normal");
node->setCompositeOpId(compositeOpName);
}
if (node->inherits("KisLayer")) {
KisLayer* layer = qobject_cast(node.data());
QBitArray channelFlags = stringToFlags(element.attribute(CHANNEL_FLAGS, ""), colorSpace->channelCount());
layer->setChannelFlags(channelFlags);
if (element.hasAttribute(LAYER_STYLE_UUID)) {
QString uuidString = element.attribute(LAYER_STYLE_UUID);
QUuid uuid(uuidString);
if (!uuid.isNull()) {
KisPSDLayerStyleSP dumbLayerStyle(new KisPSDLayerStyle());
dumbLayerStyle->setUuid(uuid);
layer->setLayerStyle(dumbLayerStyle);
} else {
warnKrita << "WARNING: Layer style for layer" << layer->name() << "contains invalid UUID" << uuidString;
}
}
}
if (node->inherits("KisGroupLayer")) {
if (element.hasAttribute(PASS_THROUGH_MODE)) {
bool value = element.attribute(PASS_THROUGH_MODE, "0") != "0";
KisGroupLayer *group = qobject_cast(node.data());
group->setPassThroughMode(value);
}
}
const bool timelineEnabled = element.attribute(VISIBLE_IN_TIMELINE, "0") == "0" ? false : true;
node->setUseInTimeline(timelineEnabled);
if (node->inherits("KisPaintLayer")) {
KisPaintLayer* layer = qobject_cast(node.data());
QBitArray channelLockFlags = stringToFlags(element.attribute(CHANNEL_LOCK_FLAGS, ""), colorSpace->channelCount());
layer->setChannelLockFlags(channelLockFlags);
bool onionEnabled = element.attribute(ONION_SKIN_ENABLED, "0") == "0" ? false : true;
layer->setOnionSkinEnabled(onionEnabled);
}
if (element.attribute(FILE_NAME).isNull()) {
m_d->layerFilenames[node.data()] = name;
}
else {
m_d->layerFilenames[node.data()] = element.attribute(FILE_NAME);
}
if (element.hasAttribute("selected") && element.attribute("selected") == "true") {
m_d->selectedNodes.append(node);
}
if (element.hasAttribute(KEYFRAME_FILE)) {
m_d->keyframeFilenames.insert(node.data(), element.attribute(KEYFRAME_FILE));
}
return node;
}
KisNodeSP KisKraLoader::loadPaintLayer(const KoXmlElement& element, KisImageSP image,
const QString& name, const KoColorSpace* cs, quint32 opacity)
{
Q_UNUSED(element);
KisPaintLayer* layer;
layer = new KisPaintLayer(image, name, opacity, cs);
Q_CHECK_PTR(layer);
return layer;
}
KisNodeSP KisKraLoader::loadFileLayer(const KoXmlElement& element, KisImageSP image, const QString& name, quint32 opacity)
{
QString filename = element.attribute("source", QString());
if (filename.isNull()) return 0;
bool scale = (element.attribute("scale", "true") == "true");
int scalingMethod = element.attribute("scalingmethod", "-1").toInt();
if (scalingMethod < 0) {
if (scale) {
scalingMethod = KisFileLayer::ToImagePPI;
}
else {
scalingMethod = KisFileLayer::None;
}
}
QString documentPath;
if (m_d->document) {
documentPath = m_d->document->url().toLocalFile();
}
QFileInfo info(documentPath);
QString basePath = info.absolutePath();
QString fullPath = QDir(basePath).filePath(QDir::cleanPath(filename));
if (!QFileInfo(fullPath).exists()) {
qApp->setOverrideCursor(Qt::ArrowCursor);
QString msg = i18nc(
"@info",
"The file associated to a file layer with the name \"%1\" is not found.\n\n"
"Expected path:\n"
"%2\n\n"
"Do you want to locate it manually?", name, fullPath);
int result = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
if (result == QMessageBox::Yes) {
KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument");
dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import));
dialog.setDefaultDir(basePath);
QString url = dialog.filename();
if (!QFileInfo(basePath).exists()) {
filename = url;
} else {
QDir d(basePath);
filename = d.relativeFilePath(url);
}
}
qApp->restoreOverrideCursor();
}
KisLayer *layer = new KisFileLayer(image, basePath, filename, (KisFileLayer::ScalingMethod)scalingMethod, name, opacity);
Q_CHECK_PTR(layer);
return layer;
}
KisNodeSP KisKraLoader::loadGroupLayer(const KoXmlElement& element, KisImageSP image,
const QString& name, const KoColorSpace* cs, quint32 opacity)
{
Q_UNUSED(element);
Q_UNUSED(cs);
QString attr;
KisGroupLayer* layer;
layer = new KisGroupLayer(image, name, opacity);
Q_CHECK_PTR(layer);
return layer;
}
KisNodeSP KisKraLoader::loadAdjustmentLayer(const KoXmlElement& element, KisImageSP image,
const QString& name, const KoColorSpace* cs, quint32 opacity)
{
// XXX: do something with filterversion?
Q_UNUSED(cs);
QString attr;
KisAdjustmentLayer* layer;
QString filtername;
QString legacy = filtername;
if ((filtername = element.attribute(FILTER_NAME)).isNull()) {
// XXX: Invalid adjustmentlayer! We should warn about it!
warnFile << "No filter in adjustment layer";
return 0;
}
//get deprecated filters.
if (filtername=="brightnesscontrast") {
legacy = filtername;
filtername = "perchannel";
}
if (filtername=="left edge detections"
|| filtername=="right edge detections"
|| filtername=="top edge detections"
|| filtername=="bottom edge detections") {
legacy = filtername;
filtername = "edge detection";
}
KisFilterSP f = KisFilterRegistry::instance()->value(filtername);
if (!f) {
warnFile << "No filter for filtername" << filtername << "";
return 0; // XXX: We don't have this filter. We should warn about it!
}
KisFilterConfigurationSP kfc = f->defaultConfiguration();
kfc->setProperty("legacy", legacy);
if (legacy=="brightnesscontrast") {
kfc->setProperty("colorModel", cs->colorModelId().id());
}
// We'll load the configuration and the selection later.
layer = new KisAdjustmentLayer(image, name, kfc, 0);
Q_CHECK_PTR(layer);
layer->setOpacity(opacity);
return layer;
}
KisNodeSP KisKraLoader::loadShapeLayer(const KoXmlElement& element, KisImageSP image,
const QString& name, const KoColorSpace* cs, quint32 opacity)
{
Q_UNUSED(element);
Q_UNUSED(cs);
QString attr;
KoShapeControllerBase * shapeController = 0;
if (m_d->document) {
shapeController = m_d->document->shapeController();
}
KisShapeLayer* layer = new KisShapeLayer(shapeController, image, name, opacity);
Q_CHECK_PTR(layer);
return layer;
}
KisNodeSP KisKraLoader::loadGeneratorLayer(const KoXmlElement& element, KisImageSP image,
const QString& name, const KoColorSpace* cs, quint32 opacity)
{
Q_UNUSED(cs);
// XXX: do something with generator version?
KisGeneratorLayer* layer;
QString generatorname = element.attribute(GENERATOR_NAME);
if (generatorname.isNull()) {
// XXX: Invalid generator layer! We should warn about it!
warnFile << "No generator in generator layer";
return 0;
}
KisGeneratorSP generator = KisGeneratorRegistry::instance()->value(generatorname);
if (!generator) {
warnFile << "No generator for generatorname" << generatorname << "";
return 0; // XXX: We don't have this generator. We should warn about it!
}
KisFilterConfigurationSP kgc = generator->defaultConfiguration();
// We'll load the configuration and the selection later.
layer = new KisGeneratorLayer(image, name, kgc, 0);
Q_CHECK_PTR(layer);
layer->setOpacity(opacity);
return layer;
}
KisNodeSP KisKraLoader::loadCloneLayer(const KoXmlElement& element, KisImageSP image,
const QString& name, const KoColorSpace* cs, quint32 opacity)
{
Q_UNUSED(cs);
KisCloneLayerSP layer = new KisCloneLayer(0, image, name, opacity);
KisNodeUuidInfo info;
if (! (element.attribute(CLONE_FROM_UUID)).isNull()) {
info = KisNodeUuidInfo(QUuid(element.attribute(CLONE_FROM_UUID)));
} else {
if ((element.attribute(CLONE_FROM)).isNull()) {
return 0;
} else {
info = KisNodeUuidInfo(element.attribute(CLONE_FROM));
}
}
layer->setCopyFromInfo(info);
if ((element.attribute(CLONE_TYPE)).isNull()) {
return 0;
} else {
layer->setCopyType((CopyLayerType) element.attribute(CLONE_TYPE).toInt());
}
return layer;
}
KisNodeSP KisKraLoader::loadFilterMask(const KoXmlElement& element)
{
QString attr;
KisFilterMask* mask;
QString filtername;
// XXX: should we check the version?
if ((filtername = element.attribute(FILTER_NAME)).isNull()) {
// XXX: Invalid filter layer! We should warn about it!
warnFile << "No filter in filter layer";
return 0;
}
KisFilterSP f = KisFilterRegistry::instance()->value(filtername);
if (!f) {
warnFile << "No filter for filtername" << filtername << "";
return 0; // XXX: We don't have this filter. We should warn about it!
}
KisFilterConfigurationSP kfc = f->defaultConfiguration();
// We'll load the configuration and the selection later.
mask = new KisFilterMask();
mask->setFilter(kfc);
Q_CHECK_PTR(mask);
return mask;
}
KisNodeSP KisKraLoader::loadTransformMask(const KoXmlElement& element)
{
Q_UNUSED(element);
KisTransformMask* mask;
/**
* We'll load the transform configuration later on a stage
* of binary data loading
*/
mask = new KisTransformMask();
Q_CHECK_PTR(mask);
return mask;
}
KisNodeSP KisKraLoader::loadTransparencyMask(const KoXmlElement& element)
{
Q_UNUSED(element);
KisTransparencyMask* mask = new KisTransparencyMask();
Q_CHECK_PTR(mask);
return mask;
}
KisNodeSP KisKraLoader::loadSelectionMask(KisImageSP image, const KoXmlElement& element)
{
KisSelectionMaskSP mask = new KisSelectionMask(image);
bool active = element.attribute(ACTIVE, "1") == "0" ? false : true;
mask->setActive(active);
Q_CHECK_PTR(mask);
return mask;
}
KisNodeSP KisKraLoader::loadColorizeMask(KisImageSP image, const KoXmlElement& element, const KoColorSpace *colorSpace)
{
KisColorizeMaskSP mask = new KisColorizeMask();
const bool editKeystrokes = element.attribute(COLORIZE_EDIT_KEYSTROKES, "1") == "0" ? false : true;
const bool showColoring = element.attribute(COLORIZE_SHOW_COLORING, "1") == "0" ? false : true;
KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, editKeystrokes, image);
KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, showColoring, image);
const bool useEdgeDetection = KisDomUtils::toInt(element.attribute(COLORIZE_USE_EDGE_DETECTION, "0"));
const qreal edgeDetectionSize = KisDomUtils::toDouble(element.attribute(COLORIZE_EDGE_DETECTION_SIZE, "4"));
const qreal radius = KisDomUtils::toDouble(element.attribute(COLORIZE_FUZZY_RADIUS, "0"));
const int cleanUp = KisDomUtils::toInt(element.attribute(COLORIZE_CLEANUP, "0"));
const bool limitToDevice = KisDomUtils::toInt(element.attribute(COLORIZE_LIMIT_TO_DEVICE, "0"));
mask->setUseEdgeDetection(useEdgeDetection);
mask->setEdgeDetectionSize(edgeDetectionSize);
mask->setFuzzyRadius(radius);
mask->setCleanUpAmount(qreal(cleanUp) / 100.0);
mask->setLimitToDeviceBounds(limitToDevice);
delete mask->setColorSpace(colorSpace);
mask->setImage(image);
return mask;
}
void KisKraLoader::loadCompositions(const KoXmlElement& elem, KisImageSP image)
{
KoXmlNode child;
for (child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) {
KoXmlElement e = child.toElement();
QString name = e.attribute("name");
bool exportEnabled = e.attribute("exportEnabled", "1") == "0" ? false : true;
KisLayerCompositionSP composition(new KisLayerComposition(image, name));
composition->setExportEnabled(exportEnabled);
KoXmlNode value;
for (value = child.lastChild(); !value.isNull(); value = value.previousSibling()) {
KoXmlElement e = value.toElement();
QUuid uuid(e.attribute("uuid"));
bool visible = e.attribute("visible", "1") == "0" ? false : true;
composition->setVisible(uuid, visible);
bool collapsed = e.attribute("collapsed", "1") == "0" ? false : true;
composition->setCollapsed(uuid, collapsed);
}
image->addComposition(composition);
}
}
void KisKraLoader::loadAssistantsList(const KoXmlElement &elem)
{
KoXmlNode child;
int count = 0;
for (child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) {
KoXmlElement e = child.toElement();
QString type = e.attribute("type");
QString file_name = e.attribute("filename");
m_d->assistantsFilenames.insert(file_name,type);
count++;
}
}
void KisKraLoader::loadGrid(const KoXmlElement& elem)
{
QDomDocument dom;
KoXml::asQDomElement(dom, elem);
QDomElement domElement = dom.firstChildElement();
KisGridConfig config;
config.loadDynamicDataFromXml(domElement);
config.loadStaticData();
m_d->document->setGridConfig(config);
}
void KisKraLoader::loadGuides(const KoXmlElement& elem)
{
QDomDocument dom;
KoXml::asQDomElement(dom, elem);
QDomElement domElement = dom.firstChildElement();
KisGuidesConfig guides;
guides.loadFromXml(domElement);
m_d->document->setGuidesConfig(guides);
}
+void KisKraLoader::loadMirrorAxis(const KoXmlElement &elem)
+{
+ QDomDocument dom;
+ KoXml::asQDomElement(dom, elem);
+ QDomElement domElement = dom.firstChildElement();
+
+ KisMirrorAxisConfig mirrorAxis;
+ mirrorAxis.loadFromXml(domElement);
+ m_d->document->setMirrorAxisConfig(mirrorAxis);
+}
+
void KisKraLoader::loadAudio(const KoXmlElement& elem, KisImageSP image)
{
QDomDocument dom;
KoXml::asQDomElement(dom, elem);
QDomElement qElement = dom.firstChildElement();
QString fileName;
if (KisDomUtils::loadValue(qElement, "masterChannelPath", &fileName)) {
fileName = QDir::toNativeSeparators(fileName);
QDir baseDirectory = QFileInfo(m_d->document->localFilePath()).absoluteDir();
fileName = baseDirectory.absoluteFilePath(fileName);
QFileInfo info(fileName);
if (!info.exists()) {
qApp->setOverrideCursor(Qt::ArrowCursor);
QString msg = i18nc(
"@info",
"Audio channel file \"%1\" doesn't exist!\n\n"
"Expected path:\n"
"%2\n\n"
"Do you want to locate it manually?", info.fileName(), info.absoluteFilePath());
int result = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
if (result == QMessageBox::Yes) {
info.setFile(KisImportExportManager::askForAudioFileName(info.absolutePath(), 0));
}
qApp->restoreOverrideCursor();
}
if (info.exists()) {
image->animationInterface()->setAudioChannelFileName(info.absoluteFilePath());
}
}
bool audioMuted = false;
if (KisDomUtils::loadValue(qElement, "audioMuted", &audioMuted)) {
image->animationInterface()->setAudioMuted(audioMuted);
}
qreal audioVolume = 0.5;
if (KisDomUtils::loadValue(qElement, "audioVolume", &audioVolume)) {
image->animationInterface()->setAudioVolume(audioVolume);
}
}
KisNodeSP KisKraLoader::loadReferenceImagesLayer(const KoXmlElement &elem, KisImageSP image)
{
KisSharedPtr layer =
new KisReferenceImagesLayer(m_d->document->shapeController(), image);
m_d->document->setReferenceImagesLayer(layer, false);
for (QDomElement child = elem.firstChildElement(); !child.isNull(); child = child.nextSiblingElement()) {
if (child.nodeName().toLower() == "referenceimage") {
auto* reference = KisReferenceImage::fromXml(child);
layer->addShape(reference);
}
}
return layer;
}
diff --git a/plugins/impex/libkra/kis_kra_loader.h b/plugins/impex/libkra/kis_kra_loader.h
index 237ce4ccd5..a4a65f3b73 100644
--- a/plugins/impex/libkra/kis_kra_loader.h
+++ b/plugins/impex/libkra/kis_kra_loader.h
@@ -1,121 +1,122 @@
/* This file is part of the KDE project
* Copyright (C) Boudewijn Rempt , (C) 2007
*
* 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 KIS_KRA_LOADER_H
#define KIS_KRA_LOADER_H
class QString;
class QStringList;
#include "KoXmlReaderForward.h"
class KoStore;
class KisDocument;
class KoColorSpace;
class KisPaintingAssistant;
#include
#include "kritalibkra_export.h"
/**
* Load old-style 1.x .kra files. Updated for 2.0, let's try to stay
* compatible. But 2.0 won't be able to save 1.x .kra files unless we
* implement an export filter.
*/
class KRITALIBKRA_EXPORT KisKraLoader
{
public:
KisKraLoader(KisDocument * document, int syntaxVersion);
~KisKraLoader();
/**
* Loading is done in two steps: first all xml is loaded, then, in finishLoading,
* the actual layer data is loaded.
*/
KisImageSP loadXML(const KoXmlElement& elem);
void loadBinaryData(KoStore* store, KisImageSP image, const QString & uri, bool external);
void loadPalettes(KoStore *store, KisDocument *doc);
vKisNodeSP selectedNodes() const;
// it's neater to follow the same design as with selectedNodes, so let's have a getter here
QList assistants() const;
/// if empty, loading didn't fail...
QStringList errorMessages() const;
/// if not empty, loading didn't fail, but there are problems
QStringList warningMessages() const;
private:
// this needs to be private, for neatness sake
void loadAssistants(KoStore* store, const QString & uri, bool external);
void loadAnimationMetadata(const KoXmlElement& element, KisImageSP image);
KisNodeSP loadNodes(const KoXmlElement& element, KisImageSP image, KisNodeSP parent);
KisNodeSP loadNode(const KoXmlElement& elem, KisImageSP image);
KisNodeSP loadPaintLayer(const KoXmlElement& elem, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity);
KisNodeSP loadGroupLayer(const KoXmlElement& elem, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity);
KisNodeSP loadAdjustmentLayer(const KoXmlElement& elem, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity);
KisNodeSP loadShapeLayer(const KoXmlElement& elem, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity);
KisNodeSP loadGeneratorLayer(const KoXmlElement& elem, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity);
KisNodeSP loadCloneLayer(const KoXmlElement& elem, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity);
KisNodeSP loadFilterMask(const KoXmlElement& elem);
KisNodeSP loadTransformMask(const KoXmlElement& elem);
KisNodeSP loadTransparencyMask(const KoXmlElement& elem);
KisNodeSP loadSelectionMask(KisImageSP image, const KoXmlElement& elem);
KisNodeSP loadColorizeMask(KisImageSP image, const KoXmlElement& elem, const KoColorSpace *colorSpace);
KisNodeSP loadFileLayer(const KoXmlElement& elem, KisImageSP image, const QString& name, quint32 opacity);
KisNodeSP loadReferenceImagesLayer(const KoXmlElement& elem, KisImageSP image);
void loadNodeKeyframes(KoStore *store, const QString &location, KisNodeSP node);
void loadCompositions(const KoXmlElement& elem, KisImageSP image);
void loadAssistantsList(const KoXmlElement& elem);
void loadGrid(const KoXmlElement& elem);
void loadGuides(const KoXmlElement& elem);
+ void loadMirrorAxis(const KoXmlElement& elem);
void loadAudio(const KoXmlElement& elem, KisImageSP image);
private:
struct Private;
Private * const m_d;
};
#endif
diff --git a/plugins/impex/libkra/kis_kra_saver.cpp b/plugins/impex/libkra/kis_kra_saver.cpp
index cebf9b4038..70c0382816 100644
--- a/plugins/impex/libkra/kis_kra_saver.cpp
+++ b/plugins/impex/libkra/kis_kra_saver.cpp
@@ -1,512 +1,527 @@
/* This file is part of the KDE project
* Copyright 2008 (C) Boudewijn Rempt
*
* 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 "kis_kra_saver.h"
#include "kis_kra_tags.h"
#include "kis_kra_save_visitor.h"
#include "kis_kra_savexml_visitor.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include