diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt index 20434b6899..be370eb2ce 100644 --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -1,658 +1,660 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ${OCIO_INCLUDE_DIR} ) if (ANDROID) add_definitions(-DQT_OPENGL_ES_3) add_definitions(-DHAS_ONLY_OPENGL_ES) include_directories (${Qt5AndroidExtras_INCLUDE_DIRS}) endif() 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_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 canvas/KisSnapPixelStrategy.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_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 dialogs/KisDlgChangeCloneSource.cpp dialogs/KisRecoverNamedAutosaveDialog.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 KisOcioConfiguration.cpp kis_control_frame.cpp kis_composite_ops_model.cc kis_paint_ops_model.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 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 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 KisChangeCloneLayersCommand.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 opengl/KisOpenGLModeProber.cpp opengl/KisScreenInformationAdapter.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/KisAsyncronousStrokeUpdateHelper.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/strokes/KisNodeSelectionRecipe.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_transport_controls.cpp widgets/kis_utility_title_bar.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/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_color_label_selector_widget.cpp + widgets/kis_color_label_button.cpp + widgets/kis_layer_filter_widget.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 widgets/KisDitherWidget.cpp KisPaletteEditor.cpp dialogs/KisDlgPaletteEditor.cpp widgets/KisNewsWidget.cpp widgets/KisGamutMaskToolbar.cpp utils/kis_document_aware_spin_box_unit_manager.cpp utils/KisSpinBoxSplineUnitConverter.cpp utils/KisClipboardUtil.cpp utils/KisDitherUtil.cpp utils/KisFileIconCreator.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 input/kis_zoom_and_rotate_action.cpp operations/kis_operation.cpp operations/kis_operation_configuration.cpp operations/kis_operation_registry.cpp operations/kis_operation_ui_factory.cpp operations/kis_operation_ui_widget.cpp operations/kis_filter_selection_operation.cpp actions/kis_selection_action_factories.cpp actions/KisPasteActionFactories.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 canvas/kis_mirror_axis.cpp kis_abstract_perspective_grid.cpp KisApplication.cpp KisAutoSaveRecoveryDialog.cpp KisDetailsPane.cpp KisDocument.cpp KisCloneDocumentStroke.cpp kis_node_view_color_scheme.cpp KisImportExportFilter.cpp KisImportExportManager.cpp KisImportExportUtils.cpp kis_async_action_feedback.cpp KisMainWindow.cpp KisOpenPane.cpp KisPart.cpp KisTemplate.cpp KisTemplateCreateDia.cpp KisTemplateGroup.cpp KisTemplates.cpp KisTemplatesPane.cpp KisTemplateTree.cpp KisUndoActionsUpdateManager.cpp KisView.cpp KisCanvasWindow.cpp KisImportExportErrorCode.cpp KisImportExportAdditionalChecks.cpp thememanager.cpp kis_mainwindow_observer.cpp KisViewManager.cpp kis_mirror_manager.cpp qtlockedfile/qtlockedfile.cpp qtsingleapplication/qtlocalpeer.cpp qtsingleapplication/qtsingleapplication.cpp KisApplicationArguments.cpp KisNetworkAccessManager.cpp KisRssReader.cpp KisMultiFeedRSSModel.cpp KisRemoteFileFetcher.cpp KisSaveGroupVisitor.cpp KisWindowLayoutResource.cpp KisWindowLayoutManager.cpp KisSessionResource.cpp KisReferenceImagesDecoration.cpp KisReferenceImage.cpp flake/KisReferenceImagesLayer.cpp flake/KisReferenceImagesLayer.h KisMouseClickEater.cpp KisDecorationsWrapperLayer.cpp KoDocumentInfoDlg.cpp KoDocumentInfo.cpp ) if(WIN32) # Private headers are needed for: # * KisDlgCustomTabletResolution # * KisScreenInformationAdapter include_directories(SYSTEM ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} qtlockedfile/qtlockedfile_win.cpp ) if (NOT USE_QT_TABLET_WINDOWS) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/wintab/kis_tablet_support_win.cpp input/wintab/kis_screen_size_choice_dialog.cpp input/wintab/kis_tablet_support_win8.cpp ) else() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} dialogs/KisDlgCustomTabletResolution.cpp ) endif() 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 ) endif() if (ENABLE_UPDATERS) if (UNIX) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} utils/KisAppimageUpdater.cpp ) endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} utils/KisUpdaterBase.cpp utils/KisManualUpdater.cpp utils/KisUpdaterStatus.cpp ) endif() if(APPLE) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/kis_extended_modifiers_mapper_osx.mm osx.mm ) endif() if (ANDROID) set (kritaui_LIB_SRCS ${kritaui_LIB_SRCS} KisAndroidFileManager.cpp) endif() ki18n_wrap_ui(kritaui_LIB_SRCS widgets/KoFillConfigWidget.ui widgets/KoStrokeConfigWidget.ui widgets/KisDitherWidget.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/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 forms/wdgchangeclonesource.ui forms/koDocumentInfoAboutWidget.ui forms/koDocumentInfoAuthorWidget.ui brushhud/kis_dlg_brush_hud_config.ui dialogs/kis_delayed_save_dialog.ui dialogs/KisRecoverNamedAutosaveDialog.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 ) if(WIN32) if(USE_QT_TABLET_WINDOWS) ki18n_wrap_ui(kritaui_LIB_SRCS dialogs/KisDlgCustomTabletResolution.ui ) else() ki18n_wrap_ui(kritaui_LIB_SRCS input/wintab/kis_screen_size_choice_dialog.ui ) endif() endif() 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 kritaversion kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils kritaresources ${PNG_LIBRARIES} LibExiv2::LibExiv2 ) if (ANDROID) target_link_libraries(kritaui GLESv3) target_link_libraries(kritaui Qt5::Gui) target_link_libraries(kritaui Qt5::AndroidExtras) endif() if (HAVE_QT_MULTIMEDIA) target_link_libraries(kritaui Qt5::Multimedia) endif() if (NOT WIN32 AND NOT APPLE AND NOT ANDROID) target_link_libraries(kritaui ${X11_X11_LIB} ${X11_Xinput_LIB}) 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 AND NOT ANDROID) 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 () if (UNIX AND BUILD_TESTING AND ENABLE_UPDATERS) install(FILES tests/data/AppImageUpdateDummy PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) endif () diff --git a/libs/ui/kis_node_filter_proxy_model.cpp b/libs/ui/kis_node_filter_proxy_model.cpp index e426ab1b7a..20dbc9bcf3 100644 --- a/libs/ui/kis_node_filter_proxy_model.cpp +++ b/libs/ui/kis_node_filter_proxy_model.cpp @@ -1,171 +1,183 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_node_filter_proxy_model.h" #include +#include + #include "kis_node.h" #include "kis_node_model.h" #include "kis_node_manager.h" #include "kis_signal_compressor.h" #include "kis_image.h" struct KisNodeFilterProxyModel::Private { Private() : nodeModel(0), activeNodeCompressor(1000, KisSignalCompressor::FIRST_INACTIVE) {} KisNodeModel *nodeModel; KisNodeSP pendingActiveNode; KisNodeSP activeNode; - QSet acceptedLabels; + QSet acceptedColorLabels; + boost::optional activeTextFilter; KisSignalCompressor activeNodeCompressor; bool isUpdatingFilter = false; bool checkIndexAllowedRecursively(QModelIndex srcIndex); }; KisNodeFilterProxyModel::KisNodeFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent), m_d(new Private) { connect(&m_d->activeNodeCompressor, SIGNAL(timeout()), SLOT(slotUpdateCurrentNodeFilter()), Qt::QueuedConnection); } KisNodeFilterProxyModel::~KisNodeFilterProxyModel() { } void KisNodeFilterProxyModel::setNodeModel(KisNodeModel *model) { m_d->nodeModel = model; setSourceModel(model); } bool KisNodeFilterProxyModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (m_d->isUpdatingFilter && role == KisNodeModel::ActiveRole) { return false; } return QSortFilterProxyModel::setData(index, value, role); } bool KisNodeFilterProxyModel::Private::checkIndexAllowedRecursively(QModelIndex srcIndex) { if (!srcIndex.isValid()) return false; KisNodeSP node = nodeModel->nodeFromIndex(srcIndex); - if (node == activeNode || - acceptedLabels.contains(node->colorLabelIndex())) { + const bool nodeTextFilterMatch = (!activeTextFilter || node->name().contains(activeTextFilter.get(), Qt::CaseInsensitive)); + + // directParentTextFilterMatch -- There's an argument to be made that searching for a parent name should show + // all of the direct children of said text-search. For now, it will remain unused. + const bool directParentTextFilterMatch = (!activeTextFilter || (node->parent() && node->parent()->name().contains(activeTextFilter.get(), Qt::CaseInsensitive))); + Q_UNUSED(directParentTextFilterMatch); + const bool nodeColorMatch = (acceptedColorLabels.count() == 0 || acceptedColorLabels.contains(node->colorLabelIndex())); + if ( node == activeNode || + ( nodeColorMatch && nodeTextFilterMatch )) { return true; } bool result = false; const int numChildren = srcIndex.model()->rowCount(srcIndex); for (int i = 0; i < numChildren; i++) { QModelIndex child = nodeModel->index(i, 0, srcIndex); if (checkIndexAllowedRecursively(child)) { result = true; break; } } return result; } bool KisNodeFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { KIS_ASSERT_RECOVER(m_d->nodeModel) { return true; } const QModelIndex index = sourceModel()->index(source_row, 0, source_parent); if (!index.isValid()) return false; KisNodeSP node = m_d->nodeModel->nodeFromIndex(index); return !node || - m_d->acceptedLabels.isEmpty() || + (m_d->acceptedColorLabels.isEmpty() && !m_d->activeTextFilter) || m_d->checkIndexAllowedRecursively(index); } KisNodeSP KisNodeFilterProxyModel::nodeFromIndex(const QModelIndex &index) const { KIS_ASSERT_RECOVER(m_d->nodeModel) { return 0; } QModelIndex srcIndex = mapToSource(index); return m_d->nodeModel->nodeFromIndex(srcIndex); } QModelIndex KisNodeFilterProxyModel::indexFromNode(KisNodeSP node) const { KIS_ASSERT_RECOVER(m_d->nodeModel) { return QModelIndex(); } QModelIndex srcIndex = m_d->nodeModel->indexFromNode(node); return mapFromSource(srcIndex); } -void KisNodeFilterProxyModel::setAcceptedLabels(const QList &value) +void KisNodeFilterProxyModel::setAcceptedLabels(const QSet &value) +{ + m_d->acceptedColorLabels = value; + invalidateFilter(); +} + +void KisNodeFilterProxyModel::setTextFilter(const QString &text) { -#if QT_VERSION >= QT_VERSION_CHECK(5,14,0) - m_d->acceptedLabels = QSet(value.begin(), value.end()); -#else - m_d->acceptedLabels = QSet::fromList(value); -#endif + m_d->activeTextFilter = !text.isEmpty() ? boost::make_optional(text) : boost::none; invalidateFilter(); } void KisNodeFilterProxyModel::setActiveNode(KisNodeSP node) { // NOTE: the current node might change due to beginRemoveRows, in such case // we must ensure we don't trigger recursive model invalidation. // the new node may temporary become null when the last layer // of the document in removed m_d->pendingActiveNode = node; m_d->activeNodeCompressor.start(); } void KisNodeFilterProxyModel::slotUpdateCurrentNodeFilter() { m_d->activeNode = m_d->pendingActiveNode; /** * During the filter update the model might emit "current changed" signals, * which (in their turn) will issue setData(..., KisNodeModel::ActiveRole) * call, leading to a double recursion. Which, obviously, crashes Krita. * * Right now, just blocking the KisNodeModel::ActiveRole call is the * most obvious solution for the problem. */ m_d->isUpdatingFilter = true; invalidateFilter(); m_d->isUpdatingFilter = false; } void KisNodeFilterProxyModel::unsetDummiesFacade() { m_d->nodeModel->setDummiesFacade(0, 0, 0, 0, 0); m_d->pendingActiveNode = 0; m_d->activeNode = 0; } diff --git a/libs/ui/kis_node_filter_proxy_model.h b/libs/ui/kis_node_filter_proxy_model.h index f7a56af662..d72359aa77 100644 --- a/libs/ui/kis_node_filter_proxy_model.h +++ b/libs/ui/kis_node_filter_proxy_model.h @@ -1,62 +1,63 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_NODE_FILTER_PROXY_MODEL_H #define __KIS_NODE_FILTER_PROXY_MODEL_H #include #include #include "kis_types.h" #include "kritaui_export.h" class KisNodeModel; class KisNodeDummy; class KisNodeManager; class KRITAUI_EXPORT KisNodeFilterProxyModel : public QSortFilterProxyModel { Q_OBJECT public: KisNodeFilterProxyModel(QObject *parent); ~KisNodeFilterProxyModel() override; void setNodeModel(KisNodeModel *model); bool setData(const QModelIndex &index, const QVariant &value, int role) override; bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; - void setAcceptedLabels(const QList &value); + void setAcceptedLabels(const QSet &value); + void setTextFilter(const QString &text); KisNodeSP nodeFromIndex(const QModelIndex &index) const; QModelIndex indexFromNode(KisNodeSP node) const; void unsetDummiesFacade(); public Q_SLOTS: void setActiveNode(KisNodeSP node); private Q_SLOTS: void slotUpdateCurrentNodeFilter(); private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_NODE_FILTER_PROXY_MODEL_H */ diff --git a/libs/ui/widgets/kis_color_filter_combo.cpp b/libs/ui/widgets/kis_color_filter_combo.cpp index c9d3432145..4c493df896 100644 --- a/libs/ui/widgets/kis_color_filter_combo.cpp +++ b/libs/ui/widgets/kis_color_filter_combo.cpp @@ -1,427 +1,449 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_color_filter_combo.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_node_view_color_scheme.h" #include "kis_debug.h" #include "kis_icon_utils.h" #include "krita_utils.h" #include "kis_node.h" enum AdditionalRoles { OriginalLabelIndex = Qt::UserRole + 1000 }; struct LabelFilteringModel : public QSortFilterProxyModel { LabelFilteringModel(QObject *parent) : QSortFilterProxyModel(parent) {} bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override { const QModelIndex index = sourceModel()->index(source_row, 0, source_parent); const int labelIndex = index.data(OriginalLabelIndex).toInt(); return labelIndex < 0 || m_acceptedLabels.contains(labelIndex); } void setAcceptedLabels(const QSet &value) { m_acceptedLabels = value; invalidateFilter(); } private: QSet m_acceptedLabels; }; class ComboEventFilter : public QObject { public: ComboEventFilter(KisColorFilterCombo *parent) : m_parent(parent), m_buttonPressed(false) {} protected: bool eventFilter(QObject *obj, QEvent *event) override { if (event->type() == QEvent::Leave) { m_buttonPressed = false; } else if (event->type() == QEvent::MouseButtonPress) { QMouseEvent *mevent = static_cast(event); m_buttonPressed = mevent->button() == Qt::LeftButton; } else if (event->type() == QEvent::MouseButtonRelease) { QMouseEvent *mevent = static_cast(event); QModelIndex index = m_parent->view()->indexAt(mevent->pos()); if (!index.isValid()) return false; /** * We should eat the first event that arrives exactly when * the drop down appears on screen. */ if (!m_buttonPressed) return true; const bool toUncheckedState = index.data(Qt::CheckStateRole) == Qt::Checked; if (toUncheckedState) { m_parent->model()->setData(index, Qt::Unchecked, Qt::CheckStateRole); } else { m_parent->model()->setData(index, Qt::Checked, Qt::CheckStateRole); } if (index.data(OriginalLabelIndex).toInt() == -1) { for (int i = 0; i < m_parent->model()->rowCount(); i++) { const QModelIndex &other = m_parent->model()->index(i, 0); if (other.data(OriginalLabelIndex) != -1) { m_parent->model()->setData(other, toUncheckedState ? Qt::Unchecked : Qt::Checked, Qt::CheckStateRole); } } } else { bool prevChecked = false; bool checkedVaries = false; QModelIndex allLabelsIndex; for (int i = 0; i < m_parent->model()->rowCount(); i++) { const QModelIndex &other = m_parent->model()->index(i, 0); if (other.data(OriginalLabelIndex) != -1) { const bool currentChecked = other.data(Qt::CheckStateRole) == Qt::Checked; if (i == 0) { prevChecked = currentChecked; } else { if (prevChecked != currentChecked) { checkedVaries = true; break; } } } else { allLabelsIndex = other; } } const bool allLabelsIndexShouldBeChecked = prevChecked && !checkedVaries; if (allLabelsIndexShouldBeChecked != (allLabelsIndex.data(Qt::CheckStateRole) == Qt::Checked)) { m_parent->model()->setData(allLabelsIndex, allLabelsIndexShouldBeChecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); } } emit m_parent->selectedColorsChanged(); m_buttonPressed = false; return true; } return QObject::eventFilter(obj, event); } private: KisColorFilterCombo *m_parent; bool m_buttonPressed; }; class FullSizedListView : public QListView { public: QSize sizeHint() const override { return contentsSize(); } }; class PopupComboBoxStyle : public QProxyStyle { public: PopupComboBoxStyle(QStyle *baseStyle = nullptr) : QProxyStyle(baseStyle) {} int styleHint(QStyle::StyleHint hint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const override { // This flag makes ComboBox popup float ontop of its parent ComboBox, like in Fusion style. // Only when this hint is set will Qt respect combobox popup size hints, otherwise the popup // can never exceed the width of its parent ComboBox, like in Breeze style. if (hint == QStyle::SH_ComboBox_Popup) { return true; } return QProxyStyle::styleHint(hint, option, widget, returnData); } }; struct KisColorFilterCombo::Private { LabelFilteringModel *filteringModel; /** * if the combobox is in the filter mode * (when no colors are selected) * it will show the filter icon ("view-filter") * otherwise it will show tag icon ("tag") */ bool filterMode {true}; /** * If the combobox is in the circle mode, * it will show the selected colors as circle * otherwise it will show it in a rectangle */ bool circleMode {true}; }; KisColorFilterCombo::KisColorFilterCombo(QWidget *parent, bool filterMode, bool circleMode) : QComboBox(parent), m_d(new Private) { m_d->filterMode = filterMode; m_d->circleMode = circleMode; QStandardItemModel *newModel = new QStandardItemModel(this); setModel(newModel); QStyle* newStyle = QStyleFactory::create(style()->objectName()); // proxy style steals the ownership of the style and deletes it later PopupComboBoxStyle *proxyStyle = new PopupComboBoxStyle(newStyle); proxyStyle->setParent(this); setStyle(proxyStyle); setView(new FullSizedListView); m_eventFilters.append(new ComboEventFilter(this)); m_eventFilters.append(new ComboEventFilter(this)); view()->installEventFilter(m_eventFilters[0]); view()->viewport()->installEventFilter(m_eventFilters[1]); KisNodeViewColorScheme scm; QStandardItem* item = new QStandardItem(i18nc("combo box: show all layers", "All")); item->setCheckable(true); item->setCheckState(Qt::Unchecked); item->setData(QColor(Qt::transparent), Qt::BackgroundColorRole); item->setData(int(-1), OriginalLabelIndex); item->setData(QSize(30, scm.rowHeight()), Qt::SizeHintRole); newModel->appendRow(item); int labelIndex = 0; foreach (const QColor &color, scm.allColorLabels()) { const QString title = color.alpha() > 0 ? "" : i18nc("combo box: select all layers without a label", "No Label"); QStandardItem* item = new QStandardItem(title); item->setCheckable(true); item->setCheckState(Qt::Unchecked); item->setData(color, Qt::BackgroundColorRole); item->setData(labelIndex, OriginalLabelIndex); item->setData(QSize(30, scm.rowHeight()), Qt::SizeHintRole); newModel->appendRow(item); labelIndex++; } m_d->filteringModel = new LabelFilteringModel(this); QAbstractItemModel *originalModel = model(); originalModel->setParent(m_d->filteringModel); m_d->filteringModel->setSourceModel(originalModel); setModel(m_d->filteringModel); } KisColorFilterCombo::~KisColorFilterCombo() { qDeleteAll(m_eventFilters); } void collectAvailableLabels(KisNodeSP root, QSet *labels) { labels->insert(root->colorLabelIndex()); KisNodeSP node = root->firstChild(); while (node) { collectAvailableLabels(node, labels); node = node->nextSibling(); } } void KisColorFilterCombo::updateAvailableLabels(KisNodeSP rootNode) { QSet labels; if (!rootNode.isNull()) { collectAvailableLabels(rootNode, &labels); } updateAvailableLabels(labels); } void KisColorFilterCombo::updateAvailableLabels(const QSet &labels) { m_d->filteringModel->setAcceptedLabels(labels); } void KisColorFilterCombo::setModes(bool filterMode, bool circleMode) { m_d->filterMode = filterMode; m_d->circleMode = circleMode; } QList KisColorFilterCombo::selectedColors() const { QList colors; for (int i = 0; i < model()->rowCount(); i++) { const QModelIndex &other = model()->index(i, 0); const int label = other.data(OriginalLabelIndex).toInt(); if (label != -1 && other.data(Qt::CheckStateRole) == Qt::Checked) { colors << label; } } return colors; } +void KisColorFilterCombo::paintColorPie(QStylePainter &painter, const QPalette& palette, const QList &selectedColors, const QRect &rect, const int &baseSize) +{ + KisNodeViewColorScheme scm; + const QPen oldPen = painter.pen(); + const QBrush oldBrush = painter.brush(); + const int border = 0; + QColor shadowColor = palette.shadow().color(); + shadowColor.setAlpha(64); + + QRect pieRect(0, 0, baseSize - 2 * border, baseSize - 2 * border); + pieRect.moveCenter(rect.center()); + + if (selectedColors.size() == 1) { + const int currentLabel = selectedColors.first(); + const QColor currentColor = scm.colorLabel(currentLabel); + const QBrush brush = QBrush(currentColor); + painter.setBrush(brush); + painter.setPen(QPen(shadowColor, 1)); + + if (currentColor.alpha() > 0) { + painter.drawEllipse(rect); + } else if (currentLabel == 0) { + QColor white = Qt::white; + QColor grey = QColor(220,220,220); + painter.setBrush(QBrush(shadowColor)); + painter.setRenderHint(QPainter::Antialiasing); + painter.drawEllipse(rect); + const int step = 16 * 360 / 4; + const int checkerSteps = 4; + + for (int i = 0; i < checkerSteps; i++) { + QBrush checkerBrush = QBrush((i % 2) ? grey : white); + painter.setPen(Qt::NoPen); + painter.setBrush(checkerBrush); + painter.drawPie(pieRect, step * i, step); + } + + } + } else { + const int numColors = selectedColors.size(); + const int step = 16 * 360 / numColors; + + painter.setPen(QPen(shadowColor, 1)); + painter.setBrush(QColor(0,0,0,0)); + painter.setRenderHint(QPainter::Antialiasing); + painter.drawEllipse(rect); + for (int i = 0; i < numColors; i++) { + QColor color = scm.colorLabel(selectedColors[i]); + QBrush brush = color.alpha() > 0 ? QBrush(color) : QBrush(Qt::black, Qt::Dense4Pattern); + painter.setPen(Qt::NoPen); + painter.setBrush(brush); + + painter.drawPie(pieRect, step * i, step); + } + } + + painter.setPen(oldPen); + painter.setBrush(oldBrush); +} + + void KisColorFilterCombo::paintEvent(QPaintEvent *event) { Q_UNUSED(event); QStylePainter painter(this); painter.setPen(palette().color(QPalette::Text)); // draw the combobox frame, focusrect and selected etc. QStyleOptionComboBox opt; initStyleOption(&opt); painter.drawComplexControl(QStyle::CC_ComboBox, opt); + { - KisNodeViewColorScheme scm; const QRect editRect = style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField, this); const int size = qMin(editRect.width(), editRect.height()); const QList selectedColors = this->selectedColors(); if (selectedColors.size() == 0 || selectedColors.size() == model()->rowCount() - 1) { QIcon icon = KisIconUtils::loadIcon(m_d->filterMode ? "view-filter" : "tag"); QPixmap pixmap = icon.pixmap(QSize(size, size), !isEnabled() ? QIcon::Disabled : QIcon::Normal); painter.drawPixmap(editRect.right() - size, editRect.top(), pixmap); - } else if (selectedColors.size() == 1) { - const int currentLabel = selectedColors.first(); - QColor currentColor = scm.colorLabel(currentLabel); - - if (currentColor.alpha() > 0) { - painter.fillRect(editRect, currentColor); - } else if (currentLabel == 0) { - QPen oldPen = painter.pen(); - - const int border = 4; - QRect crossRect(0, 0, size - 2 * border, size - 2 * border); - crossRect.moveCenter(editRect.center()); - - QColor shade = opt.palette.dark().color(); - painter.setPen(QPen(shade, 2)); - painter.drawLine(crossRect.topLeft(), crossRect.bottomRight()); - painter.drawLine(crossRect.bottomLeft(), crossRect.topRight()); - } } else { const int numColors = selectedColors.size(); if (m_d->circleMode) { - // show all colors in a circle - - const int border = 0; - QRect pieRect(0, 0, size - 2 * border, size - 2 * border); - pieRect.moveCenter(editRect.center()); - - const int step = 16 * 360 / numColors; - - int currentAngle = 0; - - //painter.save(); // optimize out! - painter.setRenderHint(QPainter::Antialiasing); - - for (int i = 0; i < numColors; i++) { - QColor color = scm.colorLabel(selectedColors[i]); - QBrush brush = color.alpha() > 0 ? QBrush(color) : QBrush(Qt::black, Qt::Dense4Pattern); - painter.setPen(color); - painter.setBrush(brush); - - painter.drawPie(pieRect, currentAngle, step); - currentAngle += step; - } + KisColorFilterCombo::paintColorPie(painter, opt.palette, selectedColors, editRect, size ); } else { // show all colors in a rectangle + KisNodeViewColorScheme scm; int oneColorWidth = editRect.width()/numColors; int currentWidth = 0; for (int i = 0; i < numColors; i++) { QColor color = scm.colorLabel(selectedColors[i]); QBrush brush = color.alpha() > 0 ? QBrush(color) : QBrush(Qt::black, Qt::Dense4Pattern); painter.setPen(color); painter.setBrush(brush); if (i == numColors - 1) { // last color; let's fill up painter.fillRect(currentWidth, editRect.top(), editRect.width() - currentWidth, editRect.height(), brush); } else { painter.fillRect(currentWidth, editRect.top(), oneColorWidth, editRect.height(), brush); } currentWidth += oneColorWidth; } } - //painter.restore(); // optimize out! } } // draw the icon and text //painter.drawControl(QStyle::CE_ComboBoxLabel, opt); } QSize KisColorFilterCombo::minimumSizeHint() const { return sizeHint(); } QSize KisColorFilterCombo::sizeHint() const { QStyleOptionComboBox opt; initStyleOption(&opt); const QStyleOption *baseOption = qstyleoption_cast(&opt); const int arrowSize = style()->pixelMetric(QStyle::PM_ScrollBarExtent, baseOption, this); const QSize originalHint = QComboBox::sizeHint(); QSize sh(3 * arrowSize, originalHint.height()); return sh; } diff --git a/libs/ui/widgets/kis_color_filter_combo.h b/libs/ui/widgets/kis_color_filter_combo.h index c145a498ca..8503ec8f0a 100644 --- a/libs/ui/widgets/kis_color_filter_combo.h +++ b/libs/ui/widgets/kis_color_filter_combo.h @@ -1,57 +1,61 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_COLOR_FILTER_COMBO_H #define __KIS_COLOR_FILTER_COMBO_H #include #include +#include #include "kritaui_export.h" #include "kis_types.h" class ComboEventFilter; class KRITAUI_EXPORT KisColorFilterCombo : public QComboBox { Q_OBJECT public: KisColorFilterCombo(QWidget *parent, bool filterMode = true, bool circleMode = true); ~KisColorFilterCombo() override; void updateAvailableLabels(KisNodeSP rootNode); void updateAvailableLabels(const QSet &labels); void setModes(bool filterMode, bool circleMode); QSize minimumSizeHint() const override; QSize sizeHint() const override; QList selectedColors() const; Q_SIGNALS: void selectedColorsChanged(); +public: + static void paintColorPie(QStylePainter &painter, const QPalette& palette, const QList &selectedColors, const QRect &rect, const int &baseSize); + private: void paintEvent(QPaintEvent *event) override; private: struct Private; const QScopedPointer m_d; QList m_eventFilters; }; #endif /* __KIS_COLOR_FILTER_COMBO_H */ diff --git a/libs/ui/widgets/kis_color_label_button.cpp b/libs/ui/widgets/kis_color_label_button.cpp new file mode 100644 index 0000000000..f0d8268ad1 --- /dev/null +++ b/libs/ui/widgets/kis_color_label_button.cpp @@ -0,0 +1,426 @@ +/* + * Copyright (c) 2020 Eoin O'Neill + * Copyright (c) 2020 Emmet O'Neill + * + * 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_color_label_button.h" + +#include +#include +#include +#include + + +#include "kis_global.h" +#include "kis_debug.h" +#include "krita_container_utils.h" + +struct KisColorLabelButton::Private +{ + const QColor m_color; + const uint m_sizeSquared; + KisColorLabelButton::SelectionIndicationType selectionVis; + + Private(QColor color, uint sizeSquared) + : m_color(color) + , m_sizeSquared(sizeSquared) + , selectionVis(KisColorLabelButton::FillIn) + { + + } + + Private(const Private& rhs) + : m_color(rhs.m_color) + , m_sizeSquared(rhs.m_sizeSquared) + { + + } +}; + +KisColorLabelButton::KisColorLabelButton(QColor color, uint sizeSquared, QWidget *parent) : QAbstractButton(parent), m_d(new Private(color, sizeSquared)) +{ + setCheckable(true); + setChecked(true); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); +} + +KisColorLabelButton::~KisColorLabelButton() {} + +void KisColorLabelButton::paintEvent(QPaintEvent *event) +{ + QWidget::paintEvent(event); + + QStylePainter painter(this); + QStyleOptionButton styleOption; + styleOption.initFrom(this); + + const bool darkTheme = styleOption.palette.window().color().value() < 128; + + if (isDown() || isChecked()){ + styleOption.state |= QStyle::State_On; + } + + QRect fillRect = kisGrowRect(rect(), -2); + QRect outlineRect = kisGrowRect(fillRect, -1); + + const QColor shadowColor = styleOption.palette.window().color().darker(darkTheme ? 128 : 200); + const QBrush shadowBrush = QBrush(shadowColor); + const QBrush bgBrush = QBrush(styleOption.palette.window().color()); + + if (!isEnabled()) { + fillRect -= QMargins(sizeHint().width() / 4, sizeHint().height() / 4, sizeHint().width() / 4, sizeHint().height() / 4); + } else { + fillRect = kisGrowRect(fillRect, -3); + } + + if (m_d->m_color.alpha() > 0) { + QColor fillColor = m_d->m_color; + + if ((!isChecked() || !isEnabled()) && (m_d->selectionVis == FillIn)) { + fillColor.setAlpha(32); + } else if ((!isChecked() || !isEnabled()) && (m_d->selectionVis == Outline)) { + if ((styleOption.state & QStyle::State_MouseOver) == 0) { + fillColor.setAlpha(192); + } + } + + if ((isEnabled() && isChecked() && m_d->selectionVis == FillIn) || + m_d->selectionVis == Outline) { + painter.fillRect(kisGrowRect(fillRect, 1), shadowBrush); + painter.fillRect(fillRect, bgBrush); + } + + QBrush brush = QBrush(fillColor); + painter.fillRect(fillRect, brush); + + if ((isEnabled() && (m_d->selectionVis == FillIn)) || + (isChecked() && (m_d->selectionVis == Outline))) { + const QRect& shadowRect = outlineRect; + painter.setPen(QPen(shadowColor, 4)); + painter.drawRect(shadowRect); + + painter.setPen(QPen(bgBrush.color(), 2)); + painter.drawRect(outlineRect); + + painter.setPen(QPen(m_d->m_color, 2)); + painter.drawRect(outlineRect); + } + + } else { + QColor white = QColor(255,255,255); + QColor grey = QColor(200,200,200); + QColor xOverlayColor = QColor(100,100,100); + QColor outlineColor = grey; + + if ((!isChecked() || !isEnabled()) && (m_d->selectionVis == FillIn)) { + white.setAlpha(32); + grey.setAlpha(32); + + if (darkTheme) { + xOverlayColor = styleOption.palette.window().color().lighter(130); + } else { + xOverlayColor = xOverlayColor.lighter(190); + } + + } else if ((!isChecked() || !isEnabled()) && (m_d->selectionVis == Outline)) { + if ((styleOption.state & QStyle::State_MouseOver) == 0) { + white.setAlpha(192); + xOverlayColor.setAlpha(192); + if (darkTheme) { + grey = grey.darker(110); + } else { + grey = grey.lighter(110); + } + grey.setAlpha(192); + } + } + + QBrush whiteBrush = QBrush(white); + QBrush greyBrush = QBrush(grey); + + QRect upperLeftGrey = fillRect - QMargins(0, 0, fillRect.size().width() / 2, fillRect.size().height() /2); + QRect lowerRightGrey = fillRect - QMargins(fillRect.size().width() / 2, fillRect.size().height() / 2, 0, 0); + QRect xOverlay = kisGrowRect(fillRect, (m_d->m_sizeSquared / 8) * -1); + + if (isEnabled() && ((isChecked() && m_d->selectionVis == FillIn) || + m_d->selectionVis == Outline)) { + painter.fillRect(kisGrowRect(fillRect, 1), shadowBrush); + painter.fillRect(fillRect, bgBrush); + } + + painter.fillRect(fillRect, whiteBrush); + + painter.fillRect(upperLeftGrey, greyBrush); + painter.fillRect(lowerRightGrey, greyBrush); + + painter.setPen(QPen(xOverlayColor, 2)); + painter.drawLine(xOverlay.topLeft(), xOverlay.bottomRight()); + painter.drawLine(xOverlay.bottomLeft(), xOverlay.topRight()); + + if ((isEnabled() && (m_d->selectionVis == FillIn)) || + (isChecked() && (m_d->selectionVis == Outline))) { + const QRect& shadowRect = outlineRect; + painter.setPen(QPen(shadowColor, 4)); + painter.drawRect(shadowRect); + + painter.setPen(QPen(bgBrush.color(), 2)); + painter.drawRect(outlineRect); + + painter.setPen(QPen(outlineColor, 2)); + painter.drawRect(outlineRect); + } + + } +} + +void KisColorLabelButton::enterEvent(QEvent *event) { + Q_UNUSED(event); + update(); +} + +void KisColorLabelButton::leaveEvent(QEvent *event) { + Q_UNUSED(event); + update(); +} + +QSize KisColorLabelButton::sizeHint() const +{ + return QSize(m_d->m_sizeSquared,m_d->m_sizeSquared); +} + +void KisColorLabelButton::setSelectionVisType(KisColorLabelButton::SelectionIndicationType type) +{ + m_d->selectionVis = type; +} + +void KisColorLabelButton::nextCheckState() +{ + KisColorLabelFilterGroup* colorLabelFilterGroup = dynamic_cast(group()); + + if (!colorLabelFilterGroup || (colorLabelFilterGroup->countCheckedViableButtons() > colorLabelFilterGroup->minimumRequiredChecked() || !isChecked())) { + setChecked(!isChecked()); + } else { + setChecked(isChecked()); + } +} + +KisColorLabelFilterGroup::KisColorLabelFilterGroup(QObject *parent) + : QButtonGroup(parent) + , minimumCheckedButtons(1) +{ +} + +KisColorLabelFilterGroup::~KisColorLabelFilterGroup() +{ +} + +QList KisColorLabelFilterGroup::viableButtons() const { + QList viableButtons; + + Q_FOREACH( int index, viableColorLabels ) { + viableButtons.append(button(index)); + } + + return viableButtons; +} + +void KisColorLabelFilterGroup::setViableLabels(const QSet &labels) { + setAllVisibility(false); + disableAll(); + QSet removed = viableColorLabels.subtract(labels); + + viableColorLabels = labels; + + if (viableColorLabels.count() > 1) { + setAllVisibility(true); + Q_FOREACH( int index, viableColorLabels) { + if (button(index)) { + button(index)->setEnabled(true); + } + } + } + + Q_FOREACH( int index, removed ) { + button(index)->setChecked(true); + } +} + +void KisColorLabelFilterGroup::setViableLabels(const QList &viableLabels) { + setViableLabels(QSet::fromList(viableLabels)); +} + +QSet KisColorLabelFilterGroup::getActiveLabels() const { + QSet checkedLabels = QSet(); + + Q_FOREACH( int index, viableColorLabels ) { + if (button(index)->isChecked()) { + checkedLabels.insert(index); + } + } + + return checkedLabels.count() == viableColorLabels.count() ? QSet() : checkedLabels; +} + +QList KisColorLabelFilterGroup::checkedViableButtons() const { + QList checkedButtons = viableButtons(); + + KritaUtils::filterContainer(checkedButtons, [](QAbstractButton* btn){ + return (btn->isChecked()); + }); + + return checkedButtons; +} + +int KisColorLabelFilterGroup::countCheckedViableButtons() const { + return checkedViableButtons().count(); +} + +int KisColorLabelFilterGroup::countViableButtons() const { + return viableColorLabels.count(); +} + +void KisColorLabelFilterGroup::setMinimumRequiredChecked(int checkedBtns) +{ + minimumCheckedButtons = checkedBtns; +} + +int KisColorLabelFilterGroup::minimumRequiredChecked() +{ + return minimumCheckedButtons; +} + +void KisColorLabelFilterGroup::reset() { + Q_FOREACH( QAbstractButton* btn, viableButtons() ) { + btn->setChecked(true); + } +} + +void KisColorLabelFilterGroup::disableAll() { + Q_FOREACH( QAbstractButton* btn, buttons() ) { + btn->setDisabled(true); + } +} + +void KisColorLabelFilterGroup::setAllVisibility(const bool vis) +{ + Q_FOREACH( QAbstractButton* btn, buttons() ) { + btn->setVisible(vis); + } +} + +KisColorLabelMouseDragFilter::KisColorLabelMouseDragFilter(QObject* parent) : QObject(parent) +{ + lastKnownMousePosition = QPoint(0,0); + currentState = Idle; +} + +bool KisColorLabelMouseDragFilter::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick) { + QMouseEvent* mouseEvent = static_cast(event); + + currentState = WaitingForDragLeave; + lastKnownMousePosition = mouseEvent->globalPos(); + + return true; + + } else if (event->type() == QEvent::MouseButtonRelease) { + QMouseEvent* mouseEvent = static_cast(event); + QAbstractButton* startingButton = static_cast(obj); + + //If we never left, toggle the original button. + if( currentState == WaitingForDragLeave ) { + if ( startingButton->group() && (mouseEvent->modifiers() & Qt::SHIFT)) { + KisColorLabelFilterGroup* const group = static_cast(startingButton->group()); + const QList viableCheckedButtons = group->checkedViableButtons(); + + const int buttonsEnabled = viableCheckedButtons.count(); + const bool shouldChangeIsolation = (buttonsEnabled == 1) && (viableCheckedButtons.first() == startingButton); + const bool shouldIsolate = (buttonsEnabled != 1) || !shouldChangeIsolation; + + Q_FOREACH(QAbstractButton* otherBtn, group->viableButtons()) { + if (otherBtn == startingButton){ + startingButton->setChecked(true); + } else { + otherBtn->setChecked(!shouldIsolate); + } + } + + } else { + startingButton->click(); + } + } + + currentState = Idle; + lastKnownMousePosition = mouseEvent->globalPos(); + + return true; + + } else if (event->type() == QEvent::MouseMove) { + + if (currentState == WaitingForDragLeave) { + QMouseEvent* mouseEvent = static_cast(event); + QWidget* firstClicked = static_cast(obj); + const QPointF localPosition = mouseEvent->localPos(); + + if (!firstClicked->rect().contains(localPosition.x(), localPosition.y())) { + QAbstractButton* btn = static_cast(obj); + btn->click(); + + checkSlideOverNeighborButtons(mouseEvent, btn); + + currentState = WaitingForDragEnter; + } + + lastKnownMousePosition = mouseEvent->globalPos(); + + return true; + + } else if (currentState == WaitingForDragEnter) { + QMouseEvent* mouseEvent = static_cast(event); + QAbstractButton* startingButton = static_cast(obj); + const QPoint currentPosition = mouseEvent->globalPos(); + + checkSlideOverNeighborButtons(mouseEvent, startingButton); + + lastKnownMousePosition = currentPosition; + + return true; + } + + } + + return false; +} + +void KisColorLabelMouseDragFilter::checkSlideOverNeighborButtons(QMouseEvent* mouseEvent, QAbstractButton* startingButton) +{ + const QPoint currentPosition = mouseEvent->globalPos(); + + if (startingButton->group()) { + QList allButtons = startingButton->group()->buttons(); + + Q_FOREACH(QAbstractButton* button, allButtons) { + const QRect bounds = QRect(button->mapToGlobal(QPoint(0,0)), button->size()); + const QPoint upperLeft = QPoint(qMin(lastKnownMousePosition.x(), currentPosition.x()), qMin(lastKnownMousePosition.y(), currentPosition.y())); + const QPoint lowerRight = QPoint(qMax(lastKnownMousePosition.x(), currentPosition.x()), qMax(lastKnownMousePosition.y(), currentPosition.y())); + const QRect mouseMovement = QRect(upperLeft, lowerRight); + if( bounds.intersects(mouseMovement) && !bounds.contains(lastKnownMousePosition)) { + button->click(); + } + } + } +} diff --git a/libs/ui/widgets/kis_color_label_button.h b/libs/ui/widgets/kis_color_label_button.h new file mode 100644 index 0000000000..b588587f8a --- /dev/null +++ b/libs/ui/widgets/kis_color_label_button.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2020 Eoin O'Neill + * Copyright (c) 2020 Emmet O'Neill + * + * 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 KISCOLORLABELBUTTON_H +#define KISCOLORLABELBUTTON_H + +#include +#include +#include + +#include "kritaui_export.h" + +class KRITAUI_EXPORT KisColorLabelButton : public QAbstractButton +{ + Q_OBJECT +public: + enum SelectionIndicationType { + FillIn, + Outline + }; + + KisColorLabelButton(QColor color, uint sizeSquared = 32, QWidget *parent = nullptr); + ~KisColorLabelButton(); + + void paintEvent(QPaintEvent* event) override; + void enterEvent(QEvent *event) override; + void leaveEvent(QEvent *event) override; + QSize sizeHint() const override; + void setSelectionVisType( SelectionIndicationType type ); + + virtual void nextCheckState() override; + +private: + struct Private; + const QScopedPointer m_d; +}; + + +class KRITAUI_EXPORT KisColorLabelFilterGroup : public QButtonGroup { + Q_OBJECT +public: + KisColorLabelFilterGroup(QObject* parent); + ~KisColorLabelFilterGroup(); + + QList viableButtons() const; + void setViableLabels(const QSet &buttons); + void setViableLabels(const QList &viableLabels); + QSet getActiveLabels() const; + + QList checkedViableButtons() const; + int countCheckedViableButtons() const; + int countViableButtons() const; + + void setMinimumRequiredChecked( int checkedBtns ); + int minimumRequiredChecked(); + +public Q_SLOTS: + void reset(); + void setAllVisibility(const bool vis); + +private: + void disableAll(); + QSet viableColorLabels; + int minimumCheckedButtons; + +}; + +class KRITAUI_EXPORT KisColorLabelMouseDragFilter : public QObject { + enum State{ + Idle, + WaitingForDragLeave, //Waiting for mouse to exit first clicked while the mouse button is down. + WaitingForDragEnter //Waiting for mouse to slide across buttons within the same button group. + }; + + State currentState; + QPoint lastKnownMousePosition; + +public: + KisColorLabelMouseDragFilter(QObject *parent = nullptr); + +protected: + bool eventFilter(QObject *obj, QEvent *event); + void checkSlideOverNeighborButtons(QMouseEvent* mouseEvent, class QAbstractButton* startingButton); +}; + + +#endif // KISCOLORLABELBUTTON_H diff --git a/libs/ui/widgets/kis_color_label_selector_widget.cpp b/libs/ui/widgets/kis_color_label_selector_widget.cpp index c27f60783d..ee2225ad70 100644 --- a/libs/ui/widgets/kis_color_label_selector_widget.cpp +++ b/libs/ui/widgets/kis_color_label_selector_widget.cpp @@ -1,340 +1,169 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_color_label_selector_widget.h" #include "kis_debug.h" #include "kis_global.h" #include #include #include +#include -#include #include #include -#include -#include - -#include -#include +#include +#include +#include "kis_color_label_button.h" #include "kis_node_view_color_scheme.h" -struct KisColorLabelSelectorWidget::Private +struct Private { Private(KisColorLabelSelectorWidget *_q) - : q(_q), - xMenuOffset(0), - yCenteringOffset(0), - realItemSize(0), - realItemSpacing(0), - hoveringItem(-1), - selectedItem(0) + : q(_q) + , buttonSize(26) { } KisColorLabelSelectorWidget *q; QVector colors; - - - const int minHeight = 12 + 4; - const int minSpacing = 1; - const int maxSpacing = 3; - const int border = 2; - - int xMenuOffset; - int yCenteringOffset; - int realItemSize; - int realItemSpacing; - - int hoveringItem; - int selectedItem; - - QRect itemRect(int index) const; - int indexFromPos(const QPoint &pos); - void updateItem(int index); - - int widthForHeight(int height, int spacing) const; - int heightForWidth(int width, int spacing) const; - void updateItemSizes(const QSize &widgetSize); + QButtonGroup* colorButtonGroup; + QSpacerItem* menuAlignmentOffset; + const int buttonSize; }; KisColorLabelSelectorWidget::KisColorLabelSelectorWidget(QWidget *parent) - : QWidget(parent), - m_d(new Private(this)) + : QWidget(parent) + , m_d(new Private(this)) { KisNodeViewColorScheme scm; m_d->colors = scm.allColorLabels(); - setMouseTracking(true); + + QHBoxLayout *layout = new QHBoxLayout(this); + + this->setLayout(layout); + layout->setContentsMargins(0,0,0,0); + layout->setSpacing(0); + layout->setAlignment(Qt::AlignLeft); + m_d->menuAlignmentOffset = new QSpacerItem(0,0); + layout->addItem(m_d->menuAlignmentOffset); + + { + m_d->colorButtonGroup = new QButtonGroup(this); + m_d->colorButtonGroup->setExclusive(true); + + for (int id = 0; id < m_d->colors.count(); id++) { + KisColorLabelButton* btn = new KisColorLabelButton(m_d->colors[id], m_d->buttonSize, this); + btn->setChecked(false); + btn->setSelectionVisType(KisColorLabelButton::Outline); + m_d->colorButtonGroup->addButton(btn, id); + layout->addWidget(btn); + } + + connect(m_d->colorButtonGroup, SIGNAL(buttonToggled(int,bool)), this, SLOT(groupButtonChecked(int,bool))); + } } -KisColorLabelSelectorWidget::~KisColorLabelSelectorWidget() -{ +KisColorLabelSelectorWidget::~KisColorLabelSelectorWidget(){ } int KisColorLabelSelectorWidget::currentIndex() const { - return m_d->selectedItem; + return m_d->colorButtonGroup->checkedId(); } -void KisColorLabelSelectorWidget::setCurrentIndex(int index) +QSize KisColorLabelSelectorWidget::sizeHint() const { - if (index == m_d->selectedItem) return; + return QSize(calculateMenuOffset() + m_d->buttonSize * m_d->colors.count(), m_d->buttonSize); +} - const int oldItem = m_d->selectedItem; - m_d->selectedItem = index; - m_d->updateItem(oldItem); - m_d->updateItem(m_d->selectedItem); - m_d->hoveringItem = index; +void KisColorLabelSelectorWidget::resizeEvent(QResizeEvent *e) { + int menuOffset = calculateMenuOffset(); - emit currentIndexChanged(m_d->selectedItem); -} + m_d->menuAlignmentOffset->changeSize(menuOffset, height()); + layout()->invalidate(); -QSize KisColorLabelSelectorWidget::minimumSizeHint() const -{ - return QSize(m_d->widthForHeight(m_d->minHeight, m_d->minSpacing), m_d->minHeight); -} + QMenu *menu = qobject_cast(parent()); -QSize KisColorLabelSelectorWidget::sizeHint() const -{ - const int preferredHeight = 22 + 2 * m_d->border; - return QSize(m_d->widthForHeight(preferredHeight, m_d->maxSpacing), preferredHeight); + if(menu) { + menu->resize(menu->width() + menuOffset, menu->height()); + } + + QWidget::resizeEvent(e); } -void KisColorLabelSelectorWidget::resizeEvent(QResizeEvent *e) +int KisColorLabelSelectorWidget::calculateMenuOffset() const { - m_d->xMenuOffset = 0; - bool hasWideItems = false; QMenu *menu = qobject_cast(parent()); + bool hasCheckable = false; + bool hasIcon = false; + + int menuOffset = 0; + if (menu) { Q_FOREACH(QAction *action, menu->actions()) { - if (action->isCheckable() || - !action->icon().isNull()) { + hasCheckable |= action->isCheckable(); + hasIcon |= action->icon().isNull(); + hasWideItems |= (hasCheckable || hasIcon); - hasWideItems = true; + if (hasWideItems) { break; } } } if (hasWideItems) { QStyleOption opt; opt.init(this); // some copy-pasted code from QFusionStyle style - const int hmargin = style()->pixelMetric(QStyle::PM_MenuHMargin, &opt, this); - const int icone = style()->pixelMetric(QStyle::PM_SmallIconSize, &opt, this); - m_d->xMenuOffset = hmargin + icone + 6; - } - - m_d->updateItemSizes(e->size()); - QWidget::resizeEvent(e); -} - -int KisColorLabelSelectorWidget::Private::widthForHeight(int height, int spacing) const -{ - return height * colors.size() + spacing * (colors.size() - 1) + 2 * border + xMenuOffset; -} - -int KisColorLabelSelectorWidget::Private::heightForWidth(int width, int spacing) const -{ - const int numItems = colors.size(); - return qRound(qreal(width - spacing * (numItems - 1) - 2 * border - xMenuOffset) / numItems); -} - -void KisColorLabelSelectorWidget::Private::updateItemSizes(const QSize &widgetSize) -{ - const int height = qBound(minHeight, - heightForWidth(widgetSize.width(), minSpacing), - widgetSize.height()); - - const int size = height - 2 * border; - const int numItems = colors.size(); - - const int rest = widgetSize.width() - size * numItems - 2 * border - xMenuOffset; - const int spacing = qBound(minSpacing, - rest / (numItems - 1), - maxSpacing); - - realItemSize = size; - realItemSpacing = spacing; - yCenteringOffset = qMax(0, (widgetSize.height() - height) / 2); -} - -QRect KisColorLabelSelectorWidget::Private::itemRect(int index) const -{ - const int x = xMenuOffset + border + index * realItemSize + index * realItemSpacing; - const int y = border + yCenteringOffset; - - return QRect(x, y, realItemSize, realItemSize); -} - -int KisColorLabelSelectorWidget::Private::indexFromPos(const QPoint &pos) -{ - const int x = pos.x() - border - xMenuOffset; - const int y = pos.y() - border - yCenteringOffset; - if (y < 0 || y >= realItemSize || x < 0) return -1; - int idx = (x + realItemSpacing) / (realItemSize + realItemSpacing); - - if (idx < 0 || idx >= colors.size()) { - idx = -1; + const int hMargin = style()->pixelMetric(QStyle::PM_MenuHMargin, &opt, this); + const int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, &opt, this); + menuOffset = (hMargin + iconSize + 6); } - return idx; -} - -void KisColorLabelSelectorWidget::Private::updateItem(int index) -{ - if (index >= 0 && index < colors.size()) { - q->update(kisGrowRect(itemRect(index), border)); - } + return menuOffset; } -enum State { - NORMAL = 0, - HOVER, - CHECKED, - DISABLED -}; - -void drawToolButton(QWidget *widget, const QRect &rc, State state, const QColor &color, int border) +void KisColorLabelSelectorWidget::groupButtonChecked(int index, bool state) { - QStylePainter p(widget); - QStyleOption opt; - opt.initFrom(widget); - opt.rect = kisGrowRect(rc, border); - - switch (state) { - case DISABLED: - case NORMAL: - opt.state &= ~QStyle::State_Raised; - break; - case HOVER: - opt.state |= QStyle::State_Raised; - break; - case CHECKED: - opt.state |= QStyle::State_On; - break; - }; - - - if (opt.state & (QStyle::State_Sunken | QStyle::State_On | QStyle::State_Raised)) { - - const QRect borderRect = kisGrowRect(rc, 1); - p.setPen(QPen(opt.palette.text().color(), 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); - p.drawRect(borderRect); - } - - const int offset = qMax(1, rc.height() / 10); - const QRect colorBlobRect = kisGrowRect(rc, -offset); - if (color.alpha() > 0) { - QColor fillColor = color; - - if (state == DISABLED) { - fillColor.setHsl(0, 0, color.lightness()); - } - - p.fillRect(colorBlobRect, fillColor); - } else { - - // draw an X for no color for the first item - QRect crossRect = kisGrowRect(colorBlobRect, -offset); - - QColor shade = opt.palette.text().color(); - p.setPen(QPen(shade, 2)); - p.drawLine(crossRect.topLeft(), crossRect.bottomRight()); - p.drawLine(crossRect.bottomLeft(), crossRect.topRight()); + if (state == true) { + emit currentIndexChanged(index); } } -void KisColorLabelSelectorWidget::paintEvent(QPaintEvent *e) +void KisColorLabelSelectorWidget::setCurrentIndex(int index) { - QWidget::paintEvent(e); - if (isEnabled()) { - for (int i = 0; i < m_d->colors.size(); i++) { - if (i == m_d->selectedItem || i == m_d->hoveringItem) { - continue; - } - drawToolButton(this, m_d->itemRect(i), NORMAL, m_d->colors[i], m_d->border); + if (index == -1) { + QAbstractButton* btn = m_d->colorButtonGroup->checkedButton(); + if (btn) { + btn->group()->setExclusive(false); + btn->setChecked(false); + btn->group()->setExclusive(true); } - - if (m_d->selectedItem >= 0) { - drawToolButton(this, m_d->itemRect(m_d->selectedItem), CHECKED, m_d->colors[m_d->selectedItem], m_d->border); - } - - if (m_d->hoveringItem >= 0 && m_d->hoveringItem != m_d->selectedItem) { - drawToolButton(this, m_d->itemRect(m_d->hoveringItem), HOVER, m_d->colors[m_d->hoveringItem], m_d->border); + } else if (index != m_d->colorButtonGroup->checkedId()) { + QAbstractButton* btn = m_d->colorButtonGroup->button(index); + if (btn) { + btn->setChecked(true); } - } else { - for (int i = 0; i < m_d->colors.size(); i++) { - drawToolButton(this, m_d->itemRect(i), DISABLED, m_d->colors[i], m_d->border); - } - } -} - -void KisColorLabelSelectorWidget::keyPressEvent(QKeyEvent *e) -{ - if (e->key() == Qt::Key_Right || e->key() == Qt::Key_Up) { - int newItem = (m_d->selectedItem + 1) % m_d->colors.size(); - setCurrentIndex(newItem); - } else if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Down) { - int newItem = m_d->selectedItem < 0 ? m_d->colors.size() - 1 : - (m_d->selectedItem + m_d->colors.size() - 1) % m_d->colors.size(); - setCurrentIndex(newItem); - } - - QWidget::keyPressEvent(e); -} - -void KisColorLabelSelectorWidget::mousePressEvent(QMouseEvent *e) -{ - QWidget::mousePressEvent(e); -} - -void KisColorLabelSelectorWidget::mouseReleaseEvent(QMouseEvent *e) -{ - const int newItem = m_d->indexFromPos(e->pos()); - - if (newItem >= 0 && - (e->button() == Qt::LeftButton || - e->button() == Qt::RightButton)) { - - setCurrentIndex(newItem); } - QWidget::mouseReleaseEvent(e); -} - -void KisColorLabelSelectorWidget::mouseMoveEvent(QMouseEvent *e) -{ - const int oldItem = m_d->hoveringItem; - m_d->hoveringItem = m_d->indexFromPos(e->pos()); - m_d->updateItem(oldItem); - m_d->updateItem(m_d->hoveringItem); - update(); - QWidget::mouseMoveEvent(e); -} - -void KisColorLabelSelectorWidget::leaveEvent(QEvent *e) -{ - const int oldItem = m_d->hoveringItem; - m_d->hoveringItem = -1; - m_d->updateItem(oldItem); - QWidget::leaveEvent(e); + emit currentIndexChanged(index); } diff --git a/libs/ui/widgets/kis_color_label_selector_widget.h b/libs/ui/widgets/kis_color_label_selector_widget.h index 4acbb55234..a16c381345 100644 --- a/libs/ui/widgets/kis_color_label_selector_widget.h +++ b/libs/ui/widgets/kis_color_label_selector_widget.h @@ -1,62 +1,54 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_COLOR_LABEL_SELECTOR_WIDGET_H #define __KIS_COLOR_LABEL_SELECTOR_WIDGET_H #include #include #include "kritaui_export.h" class KRITAUI_EXPORT KisColorLabelSelectorWidget : public QWidget { Q_OBJECT public: KisColorLabelSelectorWidget(QWidget *parent); ~KisColorLabelSelectorWidget() override; - QSize minimumSizeHint() const override; - QSize sizeHint() const override; - int currentIndex() const; + QSize sizeHint() const; + void resizeEvent(QResizeEvent* e) override; + + int calculateMenuOffset() const; + public Q_SLOTS: + void groupButtonChecked(int index, bool state); void setCurrentIndex(int index); Q_SIGNALS: void currentIndexChanged(int index); -protected: - - void resizeEvent(QResizeEvent *e) override; - void paintEvent(QPaintEvent *e) override; - void keyPressEvent(QKeyEvent *e) override; - void mousePressEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void leaveEvent(QEvent *e) override; - private: - struct Private; - const QScopedPointer m_d; + class Private* m_d; }; #endif /* __KIS_COLOR_LABEL_SELECTOR_WIDGET_H */ diff --git a/libs/ui/widgets/kis_layer_filter_widget.cpp b/libs/ui/widgets/kis_layer_filter_widget.cpp new file mode 100644 index 0000000000..954deb7bab --- /dev/null +++ b/libs/ui/widgets/kis_layer_filter_widget.cpp @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2020 Eoin O'Neill + * Copyright (c) 2020 Emmet O'Neill + * + * 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_layer_filter_widget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kis_debug.h" +#include "kis_node.h" +#include "kis_global.h" +#include "kis_icon_utils.h" + +#include "kis_color_filter_combo.h" +#include "kis_color_label_button.h" +#include "kis_color_label_selector_widget.h" +#include "kis_node_view_color_scheme.h" + +#include "KisMouseClickEater.h" + +KisLayerFilterWidget::KisLayerFilterWidget(QWidget *parent) : QWidget(parent) +{ + QVBoxLayout *layout = new QVBoxLayout(this); + setLayout(layout); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + textFilter = new QLineEdit(this); + textFilter->setPlaceholderText(i18n("Filter by name...")); + textFilter->setMinimumWidth(255); + textFilter->setMinimumHeight(28); + textFilter->setClearButtonEnabled(true); + + connect(textFilter, SIGNAL(textChanged(QString)), this, SIGNAL(filteringOptionsChanged())); + connect(textFilter, &QLineEdit::returnPressed, [this]() { + QMenu* menu = dynamic_cast(parentWidget()); + if (menu) { + menu->close(); + } + }); + + KisNodeViewColorScheme colorScheme; + + QWidget* buttonContainer = new QWidget(this); + MouseClickIgnore* mouseEater = new MouseClickIgnore(this); + buttonContainer->setToolTip(i18n("Filter by color label...")); + buttonContainer->installEventFilter(mouseEater); + buttonEventFilter = new KisColorLabelMouseDragFilter(buttonContainer); + { + QHBoxLayout *subLayout = new QHBoxLayout(buttonContainer); + buttonContainer->setLayout(subLayout); + subLayout->setContentsMargins(0,0,0,0); + subLayout->setSpacing(0); + subLayout->setAlignment(Qt::AlignLeft); + buttonGroup = new KisColorLabelFilterGroup(buttonContainer); + buttonGroup->setExclusive(false); + QVector colors = colorScheme.allColorLabels(); + + for (int id = 0; id < colors.count(); id++) { + KisColorLabelButton* btn = new KisColorLabelButton(colors[id], 28, buttonContainer); + buttonGroup->addButton(btn, id); + btn->installEventFilter(buttonEventFilter); + subLayout->addWidget(btn); + } + + connect(buttonGroup, SIGNAL(buttonToggled(int,bool)), this, SIGNAL(filteringOptionsChanged())); + } + + resetButton = new QPushButton(i18n("Reset Filters"), this); + resetButton->setMinimumHeight(28); + connect(resetButton, &QPushButton::clicked, [this](){ + this->reset(); + }); + + + layout->addWidget(textFilter); + layout->addWidget(buttonContainer); + layout->addWidget(resetButton); +} + +void KisLayerFilterWidget::scanUsedColorLabels(KisNodeSP node, QSet &colorLabels) +{ + if (node->parent()) { + colorLabels.insert(node->colorLabelIndex()); + } + + KisNodeSP child = node->firstChild(); + while(child) { + scanUsedColorLabels(child, colorLabels); + child = child->nextSibling(); + } +} + +void KisLayerFilterWidget::updateColorLabels(KisNodeSP root) +{ + QSet colorLabels; + + scanUsedColorLabels(root, colorLabels); + buttonGroup->setViableLabels(colorLabels); +} + +bool KisLayerFilterWidget::isCurrentlyFiltering() const +{ + const bool isFilteringText = hasTextFilter(); + const bool isFilteringColors = buttonGroup->getActiveLabels().count() > 0; + + return isFilteringText || isFilteringColors; +} + +bool KisLayerFilterWidget::hasTextFilter() const +{ + return !textFilter->text().isEmpty(); +} + +QSet KisLayerFilterWidget::getActiveColors() const +{ + QSet activeColors = buttonGroup->getActiveLabels(); + + return activeColors; +} + +QString KisLayerFilterWidget::getTextFilter() const +{ + return textFilter->text(); +} + +int KisLayerFilterWidget::getDesiredMinimumWidth() const { + return qMax(textFilter->minimumWidth(), buttonGroup->countViableButtons() * 32); +} + +int KisLayerFilterWidget::getDesiredMinimumHeight() const { + QList viableButtons = buttonGroup->viableButtons(); + if (viableButtons.count() > 1) { + return viableButtons[0]->sizeHint().height() + textFilter->minimumHeight() + resetButton->minimumHeight(); + } else { + return textFilter->minimumHeight() + resetButton->minimumHeight(); + } +} + +void KisLayerFilterWidget::reset() +{ + textFilter->clear(); + buttonGroup->reset(); + filteringOptionsChanged(); +} + +QSize KisLayerFilterWidget::sizeHint() const +{ + return QSize(getDesiredMinimumWidth(), getDesiredMinimumHeight()); +} + +void KisLayerFilterWidget::showEvent(QShowEvent *show) +{ + QMenu *parentMenu = dynamic_cast(parentWidget()); + + if (parentMenu) { + const int widthBefore = parentMenu->width(); + const int rightEdgeThreshold = 5; + + //Fake resize event needs to be made to register change in widget menu size. + //Not doing this will cause QMenu to not resize properly! + resize(sizeHint()); + + adjustSize(); + QResizeEvent event = QResizeEvent(sizeHint(), parentMenu->size()); + + parentMenu->resize(sizeHint()); + parentMenu->adjustSize(); + qApp->sendEvent(parentMenu, &event); + + QScreen *screen = QGuiApplication::screenAt(parentMenu->mapToGlobal(parentMenu->pos())); + QRect screenGeometry = screen ? screen->geometry() : parentMenu->parentWidget()->window()->geometry(); + + const bool onRightEdge = (parentMenu->pos().x() + widthBefore + rightEdgeThreshold) > screenGeometry.width(); + const int widthAfter = parentMenu->width(); + + + if (onRightEdge) { + if (widthAfter > widthBefore) { + const QRect newGeo = kisEnsureInRect( parentMenu->geometry(), screenGeometry ); + const int xShift = newGeo.x() - parentMenu->pos().x(); + parentMenu->move(parentMenu->pos().x() + xShift, parentMenu->pos().y() + 0); + } else { + const int xShift = widthBefore - widthAfter; + parentMenu->move(parentMenu->pos().x() + xShift, parentMenu->pos().y() + 0); + } + } + } + QWidget::showEvent(show); +} + +KisLayerFilterWidgetToolButton::KisLayerFilterWidgetToolButton(QWidget *parent) + : QToolButton(parent) +{ + m_textFilter = false; + m_selectedColors = QList(); +} + +KisLayerFilterWidgetToolButton::KisLayerFilterWidgetToolButton(const KisLayerFilterWidgetToolButton &rhs) + : QToolButton(rhs.parentWidget()) + , m_textFilter(rhs.m_textFilter) + , m_selectedColors(rhs.m_selectedColors) +{ + +} + +void KisLayerFilterWidgetToolButton::setSelectedColors(QList colors) +{ + m_selectedColors = colors; +} + +void KisLayerFilterWidgetToolButton::setTextFilter(bool isTextFiltering) +{ + m_textFilter = isTextFiltering; +} + +void KisLayerFilterWidgetToolButton::paintEvent(QPaintEvent *paintEvent) +{ + KisNodeViewColorScheme colorScheme; + const bool validColorFilter = !(m_selectedColors.count() == 0 || m_selectedColors.count() == colorScheme.allColorLabels().count()); + + if (m_textFilter == false && !validColorFilter) + { + QToolButton::paintEvent(paintEvent); + } + else + { + QStylePainter paint(this); + QStyleOptionToolButton opt; + initStyleOption(&opt); + opt.icon = m_textFilter ? KisIconUtils::loadIcon("format-text-bold") : icon(); + paint.drawComplexControl(QStyle::CC_ToolButton, opt); + const QSize halfIconSize = this->iconSize() / 2; + const QSize halfButtonSize = this->size() / 2; + const QRect editRect = kisGrowRect(QRect(QPoint(halfButtonSize.width() - halfIconSize.width(), halfButtonSize.height() - halfIconSize.height()),this->iconSize()), -1); + const int size = qMin(editRect.width(), editRect.height()); + + if( validColorFilter ) + { + KisColorFilterCombo::paintColorPie(paint, opt.palette, m_selectedColors, editRect, size ); + if (m_textFilter) { + if (!opt.icon.isNull()) { + QRadialGradient radGradient = QRadialGradient(editRect.center(), size); + QColor shadowTransparent = palette().shadow().color(); + shadowTransparent.setAlpha(96); + radGradient.setColorAt(0.0f, shadowTransparent); + shadowTransparent.setAlpha(0); + radGradient.setColorAt(1.0f, shadowTransparent); + paint.setBrush(radGradient); + paint.setPen(Qt::NoPen); + paint.drawEllipse(editRect.center(), size, size); + opt.icon.paint(&paint, editRect); + } + } + } + } +} + +MouseClickIgnore::MouseClickIgnore(QObject *parent) + : QObject(parent) +{ +} + +bool MouseClickIgnore::eventFilter(QObject *obj, QEvent *event) +{ + if (obj && + (event->type() == QEvent::MouseButtonPress || + event->type() == QEvent::MouseButtonDblClick || + event->type() == QEvent::MouseButtonRelease)) { + event->setAccepted(true); + return true; + } else { + return false; + } +} diff --git a/libs/ui/widgets/kis_layer_filter_widget.h b/libs/ui/widgets/kis_layer_filter_widget.h new file mode 100644 index 0000000000..7dbe94e317 --- /dev/null +++ b/libs/ui/widgets/kis_layer_filter_widget.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2020 Eoin O'Neill + * Copyright (c) 2020 Emmet O'Neill + * + * 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 KISLAYERFILTERWIDGET_H +#define KISLAYERFILTERWIDGET_H + +#include +#include +#include "kis_types.h" + +#include "kritaui_export.h" + + +class KRITAUI_EXPORT KisLayerFilterWidget : public QWidget +{ + Q_OBJECT +private: + class KisColorLabelMouseDragFilter *buttonEventFilter; + class QLineEdit *textFilter; + class KisColorLabelFilterGroup *buttonGroup; + class QPushButton *resetButton; + +public: + KisLayerFilterWidget(QWidget *parent = nullptr); + + static void scanUsedColorLabels(KisNodeSP node, QSet &colorLabels); + void updateColorLabels(KisNodeSP root); + + bool isCurrentlyFiltering() const; + bool hasTextFilter() const; + QSet getActiveColors() const; + QString getTextFilter() const; + + int getDesiredMinimumWidth() const; + int getDesiredMinimumHeight() const; + + void reset(); + + QSize sizeHint() const override; + + /* Show Event has to be overridden to + * correct for parent QMenu to properly + * resize. */ + void showEvent(QShowEvent *show) override; + +Q_SIGNALS: + void filteringOptionsChanged(); + +}; + +class KRITAUI_EXPORT KisLayerFilterWidgetToolButton : public QToolButton +{ + Q_OBJECT +public: + explicit KisLayerFilterWidgetToolButton(QWidget *parent = nullptr); + KisLayerFilterWidgetToolButton(const KisLayerFilterWidgetToolButton& rhs); + ~KisLayerFilterWidgetToolButton(){} + + void setSelectedColors(QList colors); + void setTextFilter(bool isTextFiltering); + + +private: + void paintEvent(QPaintEvent *paintEvent) override; + +private: + bool m_textFilter; + QList m_selectedColors; +}; + +class KRITAUI_EXPORT MouseClickIgnore : public QObject { + Q_OBJECT +public: + MouseClickIgnore(QObject *parent = nullptr); + bool eventFilter(QObject *obj, QEvent *event); + +}; + +#endif // KISLAYERFILTERWIDGET_H diff --git a/plugins/dockers/animation/onion_skins_docker.cpp b/plugins/dockers/animation/onion_skins_docker.cpp index 309c68c67b..2d58854ed9 100644 --- a/plugins/dockers/animation/onion_skins_docker.cpp +++ b/plugins/dockers/animation/onion_skins_docker.cpp @@ -1,271 +1,237 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * 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 "onion_skins_docker.h" #include "ui_onion_skins_docker.h" #include #include #include +#include #include "kis_icon_utils.h" #include "kis_image_config.h" #include "kis_onion_skin_compositor.h" #include "kis_signals_blocker.h" #include "kis_node_view_color_scheme.h" #include "KisViewManager.h" #include "kis_action_manager.h" #include "kis_action.h" #include #include "kis_equalizer_widget.h" +#include "kis_color_label_button.h" OnionSkinsDocker::OnionSkinsDocker(QWidget *parent) : QDockWidget(i18n("Onion Skins"), parent), ui(new Ui::OnionSkinsDocker), m_updatesCompressor(300, KisSignalCompressor::FIRST_ACTIVE), m_toggleOnionSkinsAction(0) { QWidget* mainWidget = new QWidget(this); setWidget(mainWidget); KisImageConfig config(true); ui->setupUi(mainWidget); mainWidget->setContentsMargins(10, 10, 10, 10); ui->doubleTintFactor->setMinimum(0); ui->doubleTintFactor->setMaximum(100); ui->doubleTintFactor->setPrefix(i18n("Tint: ")); ui->doubleTintFactor->setSuffix(i18n("%")); ui->btnBackwardColor->setToolTip(i18n("Tint color for past frames")); ui->btnForwardColor->setToolTip(i18n("Tint color for future frames")); QVBoxLayout *layout = ui->slidersLayout; m_equalizerWidget = new KisEqualizerWidget(10, this); connect(m_equalizerWidget, SIGNAL(sigConfigChanged()), &m_updatesCompressor, SLOT(start())); layout->addWidget(m_equalizerWidget, 1); connect(ui->btnBackwardColor, SIGNAL(changed(KoColor)), &m_updatesCompressor, SLOT(start())); connect(ui->btnForwardColor, SIGNAL(changed(KoColor)), &m_updatesCompressor, SLOT(start())); connect(ui->doubleTintFactor, SIGNAL(valueChanged(qreal)), &m_updatesCompressor, SLOT(start())); connect(&m_updatesCompressor, SIGNAL(timeout()), SLOT(changed())); { const bool isShown = config.showAdditionalOnionSkinsSettings(); ui->btnShowHide->setChecked(isShown); connect(ui->btnShowHide, SIGNAL(toggled(bool)), SLOT(slotShowAdditionalSettings(bool))); slotShowAdditionalSettings(isShown); } - // create colored checkboxes for onion skin filtering - KisNodeViewColorScheme scm; - QPalette filterColorPalette; - QPixmap iconPixmap(10, 10); - - //iconPixmap.fill(scm.colorLabel(0)); - //ui->colorFilter0_checkbox->setIcon(iconPixmap); // default(no) color - - iconPixmap.fill(scm.colorLabel(1)); - ui->colorFilter1_checkbox->setIcon(QIcon(iconPixmap)); - - iconPixmap.fill(scm.colorLabel(2)); - ui->colorFilter2_checkbox->setIcon(QIcon(iconPixmap)); - - iconPixmap.fill(scm.colorLabel(3)); - ui->colorFilter3_checkbox->setIcon(QIcon(iconPixmap)); - - iconPixmap.fill(scm.colorLabel(4)); - ui->colorFilter4_checkbox->setIcon(QIcon(iconPixmap)); - - iconPixmap.fill(scm.colorLabel(5)); - ui->colorFilter5_checkbox->setIcon(QIcon(iconPixmap)); - - iconPixmap.fill(scm.colorLabel(6)); - ui->colorFilter6_checkbox->setIcon(QIcon(iconPixmap)); - - iconPixmap.fill(scm.colorLabel(7)); - ui->colorFilter7_checkbox->setIcon(QIcon(iconPixmap)); - - iconPixmap.fill(scm.colorLabel(8)); - ui->colorFilter8_checkbox->setIcon(QIcon(iconPixmap)); - - - // assign click events to color filters and group checkbox - connect(ui->colorFilter0_checkbox, SIGNAL(toggled(bool)), this, SLOT(slotFilteredColorsChanged())); - connect(ui->colorFilter1_checkbox, SIGNAL(toggled(bool)), this, SLOT(slotFilteredColorsChanged())); - connect(ui->colorFilter2_checkbox, SIGNAL(toggled(bool)), this, SLOT(slotFilteredColorsChanged())); - connect(ui->colorFilter3_checkbox, SIGNAL(toggled(bool)), this, SLOT(slotFilteredColorsChanged())); - connect(ui->colorFilter4_checkbox, SIGNAL(toggled(bool)), this, SLOT(slotFilteredColorsChanged())); - connect(ui->colorFilter5_checkbox, SIGNAL(toggled(bool)), this, SLOT(slotFilteredColorsChanged())); - connect(ui->colorFilter6_checkbox, SIGNAL(toggled(bool)), this, SLOT(slotFilteredColorsChanged())); - connect(ui->colorFilter7_checkbox, SIGNAL(toggled(bool)), this, SLOT(slotFilteredColorsChanged())); - connect(ui->colorFilter8_checkbox, SIGNAL(toggled(bool)), this, SLOT(slotFilteredColorsChanged())); - connect(ui->colorFilterGroupbox, SIGNAL(toggled(bool)), this, SLOT(slotFilteredColorsChanged())); + { + KisNodeViewColorScheme scm; + m_filterButtonGroup = new KisColorLabelFilterGroup(this); + m_dragFilter = new KisColorLabelMouseDragFilter(this); + m_filterButtonGroup->setExclusive(false); + m_filterButtonGroup->setMinimumRequiredChecked(0); + QWidget* filterButtonContainer = ui->colorFilterGroupbox; + QLayout* filterButtonLayout = ui->filterButtons; + filterButtonLayout->setSpacing(0); + QVector availableColors = scm.allColorLabels(); + QSet viableColors; + for (int i = 0; i < availableColors.count(); i++) { + KisColorLabelButton* colorLabelButton = new KisColorLabelButton(availableColors[i], 24, filterButtonContainer); + filterButtonLayout->addWidget(colorLabelButton); + m_filterButtonGroup->addButton(colorLabelButton, i); + colorLabelButton->installEventFilter(m_dragFilter); + viableColors << i; + } + + m_filterButtonGroup->setViableLabels(viableColors); + + connect(m_filterButtonGroup, SIGNAL(buttonToggled(int,bool)), this, SLOT(slotFilteredColorsChanged())); + connect(ui->colorFilterGroupbox, SIGNAL(toggled(bool)), this, SLOT(slotFilteredColorsChanged())); + connect(ui->resetFilter, SIGNAL(pressed()), m_filterButtonGroup, SLOT(reset()) ); + } loadSettings(); KisOnionSkinCompositor::instance()->configChanged(); // this mostly hides the checkboxes since no filtering is done by default slotFilteredColorsChanged(); resize(sizeHint()); } OnionSkinsDocker::~OnionSkinsDocker() { delete ui; } void OnionSkinsDocker::setCanvas(KoCanvasBase *canvas) { Q_UNUSED(canvas); } void OnionSkinsDocker::unsetCanvas() { } void OnionSkinsDocker::setViewManager(KisViewManager *view) { KisActionManager *actionManager = view->actionManager(); m_toggleOnionSkinsAction = actionManager->createAction("toggle_onion_skin"); connect(m_toggleOnionSkinsAction, SIGNAL(triggered()), SLOT(slotToggleOnionSkins())); slotUpdateIcons(); connect(view->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateIcons())); } void OnionSkinsDocker::slotToggleOnionSkins() { m_equalizerWidget->toggleMasterSwitch(); } void OnionSkinsDocker::slotFilteredColorsChanged() { // what colors are selected to filter?? - QList selectedFilterColors; - - if (ui->colorFilter0_checkbox->isChecked()) selectedFilterColors << 0; - if (ui->colorFilter1_checkbox->isChecked()) selectedFilterColors << 1; - if (ui->colorFilter2_checkbox->isChecked()) selectedFilterColors << 2; - if (ui->colorFilter3_checkbox->isChecked()) selectedFilterColors << 3; - if (ui->colorFilter4_checkbox->isChecked()) selectedFilterColors << 4; - if (ui->colorFilter5_checkbox->isChecked()) selectedFilterColors << 5; - if (ui->colorFilter6_checkbox->isChecked()) selectedFilterColors << 6; - if (ui->colorFilter7_checkbox->isChecked()) selectedFilterColors << 7; - if (ui->colorFilter8_checkbox->isChecked()) selectedFilterColors << 8; + QSet selectedFilterColors = m_filterButtonGroup->getActiveLabels(); // show all colors if the filter is off and ignore the checkboxes if(ui->colorFilterGroupbox->isChecked() == false) { selectedFilterColors.clear(); selectedFilterColors << 0 << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8; // show everything } - ui->colorFilter0_checkbox->setVisible(ui->colorFilterGroupbox->isChecked()); - ui->colorFilter1_checkbox->setVisible(ui->colorFilterGroupbox->isChecked()); - ui->colorFilter2_checkbox->setVisible(ui->colorFilterGroupbox->isChecked()); - ui->colorFilter3_checkbox->setVisible(ui->colorFilterGroupbox->isChecked()); - ui->colorFilter4_checkbox->setVisible(ui->colorFilterGroupbox->isChecked()); - ui->colorFilter5_checkbox->setVisible(ui->colorFilterGroupbox->isChecked()); - ui->colorFilter6_checkbox->setVisible(ui->colorFilterGroupbox->isChecked()); - ui->colorFilter7_checkbox->setVisible(ui->colorFilterGroupbox->isChecked()); - ui->colorFilter8_checkbox->setVisible(ui->colorFilterGroupbox->isChecked()); + m_filterButtonGroup->setAllVisibility(ui->colorFilterGroupbox->isChecked()); + ui->resetFilter->setVisible(ui->colorFilterGroupbox->isChecked()); // existing code - KisOnionSkinCompositor::instance()->setColorLabelFilter(selectedFilterColors); + KisOnionSkinCompositor::instance()->setColorLabelFilter(QList::fromSet(selectedFilterColors)); KisOnionSkinCompositor::instance()->configChanged(); } void OnionSkinsDocker::slotUpdateIcons() { if (m_toggleOnionSkinsAction) { m_toggleOnionSkinsAction->setIcon(KisIconUtils::loadIcon("onion_skin_options")); } } void OnionSkinsDocker::slotShowAdditionalSettings(bool value) { ui->lblPrevColor->setVisible(value); ui->lblNextColor->setVisible(value); ui->btnBackwardColor->setVisible(value); ui->btnForwardColor->setVisible(value); ui->doubleTintFactor->setVisible(value); QIcon icon = KisIconUtils::loadIcon(value ? "arrow-down" : "arrow-up"); ui->btnShowHide->setIcon(icon); KisImageConfig(false).setShowAdditionalOnionSkinsSettings(value); } void OnionSkinsDocker::changed() { KisImageConfig config(false); KisEqualizerWidget::EqualizerValues v = m_equalizerWidget->getValues(); config.setNumberOfOnionSkins(v.maxDistance); for (int i = -v.maxDistance; i <= v.maxDistance; i++) { config.setOnionSkinOpacity(i, v.value[i] * 255.0 / 100.0); config.setOnionSkinState(i, v.state[i]); } config.setOnionSkinTintFactor(ui->doubleTintFactor->value() * 255.0 / 100.0); config.setOnionSkinTintColorBackward(ui->btnBackwardColor->color().toQColor()); config.setOnionSkinTintColorForward(ui->btnForwardColor->color().toQColor()); KisOnionSkinCompositor::instance()->configChanged(); } void OnionSkinsDocker::loadSettings() { KisImageConfig config(true); KisSignalsBlocker b(ui->doubleTintFactor, ui->btnBackwardColor, ui->btnForwardColor, m_equalizerWidget); ui->doubleTintFactor->setValue(qRound(config.onionSkinTintFactor() * 100.0 / 255)); KoColor bcol(KoColorSpaceRegistry::instance()->rgb8()); bcol.fromQColor(config.onionSkinTintColorBackward()); ui->btnBackwardColor->setColor(bcol); bcol.fromQColor(config.onionSkinTintColorForward()); ui->btnForwardColor->setColor(bcol); KisEqualizerWidget::EqualizerValues v; v.maxDistance = 10; for (int i = -v.maxDistance; i <= v.maxDistance; i++) { v.value.insert(i, qRound(config.onionSkinOpacity(i) * 100.0 / 255.0)); v.state.insert(i, config.onionSkinState(i)); } m_equalizerWidget->setValues(v); } diff --git a/plugins/dockers/animation/onion_skins_docker.h b/plugins/dockers/animation/onion_skins_docker.h index 7d7785abc3..198d016ac1 100644 --- a/plugins/dockers/animation/onion_skins_docker.h +++ b/plugins/dockers/animation/onion_skins_docker.h @@ -1,66 +1,69 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * 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 ONION_SKINS_DOCKER_H #define ONION_SKINS_DOCKER_H #include #include #include "kis_signal_compressor.h" class KisAction; namespace Ui { class OnionSkinsDocker; } class KisEqualizerWidget; class OnionSkinsDocker : public QDockWidget, public KisMainwindowObserver { Q_OBJECT public: explicit OnionSkinsDocker(QWidget *parent = 0); ~OnionSkinsDocker() override; QString observerName() override { return "OnionSkinsDocker"; } void setCanvas(KoCanvasBase *canvas) override; void unsetCanvas() override; void setViewManager(KisViewManager *kisview) override; private: Ui::OnionSkinsDocker *ui; KisSignalCompressor m_updatesCompressor; KisEqualizerWidget *m_equalizerWidget; KisAction *m_toggleOnionSkinsAction; + class KisColorLabelFilterGroup *m_filterButtonGroup; + class KisColorLabelMouseDragFilter *m_dragFilter; + private: void loadSettings(); private Q_SLOTS: void changed(); void slotShowAdditionalSettings(bool value); void slotUpdateIcons(); void slotToggleOnionSkins(); void slotFilteredColorsChanged(); }; #endif // ONION_SKINS_DOCKER_H diff --git a/plugins/dockers/animation/onion_skins_docker.ui b/plugins/dockers/animation/onion_skins_docker.ui index e16b4003d5..2566179e03 100644 --- a/plugins/dockers/animation/onion_skins_docker.ui +++ b/plugins/dockers/animation/onion_skins_docker.ui @@ -1,258 +1,217 @@ OnionSkinsDocker 0 0 336 282 false Onion skin options 12 12 12 12 0 0 0 - Filter Frames by Color + Filter Onion Skins by Frame Color true false 4 0 0 0 0 - + + + + 0 + 0 + + - 0 + 65 0 - - None - - + - 16 - 16 + 16777215 + 16777215 - - - - - - - - - - - - - - - - - - - - - - - - - - + Reset - - - - - - - - - - - - - - - - - - - - + + + 0 + 0 + - - - - - + 6 Qt::Horizontal + true false true 50 0 Previous frames Qt::Horizontal 13 13 Next frames KisDoubleSliderSpinBox QWidget
kis_slider_spin_box.h
1
KisColorButton QPushButton
kis_color_button.h
diff --git a/plugins/dockers/animation/timeline_frames_view.cpp b/plugins/dockers/animation/timeline_frames_view.cpp index b692d64c89..c4ddef0f65 100644 --- a/plugins/dockers/animation/timeline_frames_view.cpp +++ b/plugins/dockers/animation/timeline_frames_view.cpp @@ -1,1552 +1,1556 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timeline_frames_view.h" #include "timeline_frames_model.h" #include "timeline_ruler_header.h" #include "timeline_layers_header.h" #include "timeline_insert_keyframe_dialog.h" #include "timeline_frames_item_delegate.h" #include #include #include #include #include #include #include #include #include #include #include "config-qtmultimedia.h" #include "KSharedConfig" #include "KisKineticScroller.h" #include "kis_zoom_button.h" #include "kis_icon_utils.h" #include "kis_animation_utils.h" #include "kis_custom_modifiers_catcher.h" #include "kis_action.h" #include "kis_signal_compressor.h" #include "kis_time_range.h" #include "kis_color_label_selector_widget.h" +#include "kis_layer_filter_widget.h" #include "kis_keyframe_channel.h" #include "kis_slider_spin_box.h" #include #include #include #include #include typedef QPair QItemViewPaintPair; typedef QList QItemViewPaintPairs; struct TimelineFramesView::Private { Private(TimelineFramesView *_q) : q(_q), fps(1), zoomStillPointIndex(-1), zoomStillPointOriginalOffset(0), dragInProgress(false), dragWasSuccessful(false), modifiersCatcher(0), selectionChangedCompressor(300, KisSignalCompressor::FIRST_INACTIVE) {} TimelineFramesView *q; TimelineFramesModel *model; TimelineRulerHeader *horizontalRuler; TimelineLayersHeader *layersHeader; int fps; int zoomStillPointIndex; int zoomStillPointOriginalOffset; QPoint initialDragPanValue; QPoint initialDragPanPos; QToolButton *addLayersButton; KisAction *pinLayerToTimelineAction; QToolButton *audioOptionsButton; KisColorLabelSelectorWidget *colorSelector; QWidgetAction *colorSelectorAction; KisColorLabelSelectorWidget *multiframeColorSelector; QWidgetAction *multiframeColorSelectorAction; QMenu *audioOptionsMenu; QAction *openAudioAction; QAction *audioMuteAction; KisSliderSpinBox *volumeSlider; QMenu *layerEditingMenu; QMenu *existingLayersMenu; TimelineInsertKeyframeDialog *insertKeyframeDialog; KisZoomButton *zoomDragButton; bool dragInProgress; bool dragWasSuccessful; KisCustomModifiersCatcher *modifiersCatcher; QPoint lastPressedPosition; Qt::KeyboardModifiers lastPressedModifier; KisSignalCompressor selectionChangedCompressor; QStyleOptionViewItem viewOptionsV4() const; QItemViewPaintPairs draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const; QPixmap renderToPixmap(const QModelIndexList &indexes, QRect *r) const; KisIconToolTip tip; KisActionManager *actionMan = 0; }; TimelineFramesView::TimelineFramesView(QWidget *parent) : QTableView(parent), m_d(new Private(this)) { m_d->modifiersCatcher = new KisCustomModifiersCatcher(this); m_d->modifiersCatcher->addModifier("pan-zoom", Qt::Key_Space); m_d->modifiersCatcher->addModifier("offset-frame", Qt::Key_Alt); setCornerButtonEnabled(false); setSelectionBehavior(QAbstractItemView::SelectItems); setSelectionMode(QAbstractItemView::ExtendedSelection); setItemDelegate(new TimelineFramesItemDelegate(this)); setDragEnabled(true); setDragDropMode(QAbstractItemView::DragDrop); setAcceptDrops(true); setDropIndicatorShown(true); setDefaultDropAction(Qt::MoveAction); m_d->horizontalRuler = new TimelineRulerHeader(this); this->setHorizontalHeader(m_d->horizontalRuler); connect(m_d->horizontalRuler, SIGNAL(sigInsertColumnLeft()), SLOT(slotInsertKeyframeColumnLeft())); connect(m_d->horizontalRuler, SIGNAL(sigInsertColumnRight()), SLOT(slotInsertKeyframeColumnRight())); connect(m_d->horizontalRuler, SIGNAL(sigInsertMultipleColumns()), SLOT(slotInsertMultipleKeyframeColumns())); connect(m_d->horizontalRuler, SIGNAL(sigRemoveColumns()), SLOT(slotRemoveSelectedColumns())); connect(m_d->horizontalRuler, SIGNAL(sigRemoveColumnsAndShift()), SLOT(slotRemoveSelectedColumnsAndShift())); connect(m_d->horizontalRuler, SIGNAL(sigInsertHoldColumns()), SLOT(slotInsertHoldFrameColumn())); connect(m_d->horizontalRuler, SIGNAL(sigRemoveHoldColumns()), SLOT(slotRemoveHoldFrameColumn())); connect(m_d->horizontalRuler, SIGNAL(sigInsertHoldColumnsCustom()), SLOT(slotInsertMultipleHoldFrameColumns())); connect(m_d->horizontalRuler, SIGNAL(sigRemoveHoldColumnsCustom()), SLOT(slotRemoveMultipleHoldFrameColumns())); connect(m_d->horizontalRuler, SIGNAL(sigMirrorColumns()), SLOT(slotMirrorColumns())); connect(m_d->horizontalRuler, SIGNAL(sigCopyColumns()), SLOT(slotCopyColumns())); connect(m_d->horizontalRuler, SIGNAL(sigCutColumns()), SLOT(slotCutColumns())); connect(m_d->horizontalRuler, SIGNAL(sigPasteColumns()), SLOT(slotPasteColumns())); m_d->layersHeader = new TimelineLayersHeader(this); m_d->layersHeader->setSectionResizeMode(QHeaderView::Fixed); m_d->layersHeader->setDefaultSectionSize(24); m_d->layersHeader->setMinimumWidth(60); m_d->layersHeader->setHighlightSections(true); this->setVerticalHeader(m_d->layersHeader); connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), SLOT(slotUpdateInfiniteFramesCount())); connect(horizontalScrollBar(), SIGNAL(sliderReleased()), SLOT(slotUpdateInfiniteFramesCount())); /********** Layer Menu ***********************************************************/ m_d->layerEditingMenu = new QMenu(this); m_d->layerEditingMenu->addSection(i18n("Edit Layers:")); m_d->layerEditingMenu->addSeparator(); m_d->layerEditingMenu->addAction(KisAnimationUtils::newLayerActionName, this, SLOT(slotAddNewLayer())); m_d->layerEditingMenu->addAction(KisAnimationUtils::removeLayerActionName, this, SLOT(slotRemoveLayer())); m_d->layerEditingMenu->addSeparator(); m_d->existingLayersMenu = m_d->layerEditingMenu->addMenu(KisAnimationUtils::pinExistingLayerActionName); connect(m_d->existingLayersMenu, SIGNAL(aboutToShow()), SLOT(slotUpdateLayersMenu())); connect(m_d->existingLayersMenu, SIGNAL(triggered(QAction*)), SLOT(slotAddExistingLayer(QAction*))); connect(m_d->layersHeader, SIGNAL(sigRequestContextMenu(QPoint)), SLOT(slotLayerContextMenuRequested(QPoint))); m_d->addLayersButton = new QToolButton(this); m_d->addLayersButton->setAutoRaise(true); m_d->addLayersButton->setIcon(KisIconUtils::loadIcon("addlayer")); m_d->addLayersButton->setIconSize(QSize(20, 20)); m_d->addLayersButton->setPopupMode(QToolButton::InstantPopup); m_d->addLayersButton->setMenu(m_d->layerEditingMenu); /********** Audio Channel Menu *******************************************************/ m_d->audioOptionsButton = new QToolButton(this); m_d->audioOptionsButton->setAutoRaise(true); m_d->audioOptionsButton->setIcon(KisIconUtils::loadIcon("audio-none")); m_d->audioOptionsButton->setIconSize(QSize(20, 20)); // very small on windows if not explicitly set m_d->audioOptionsButton->setPopupMode(QToolButton::InstantPopup); m_d->audioOptionsMenu = new QMenu(this); m_d->audioOptionsMenu->addSection(i18n("Edit Audio:")); m_d->audioOptionsMenu->addSeparator(); #ifndef HAVE_QT_MULTIMEDIA m_d->audioOptionsMenu->addSection(i18nc("@item:inmenu", "Audio playback is not supported in this build!")); #endif m_d->openAudioAction= new QAction("XXX", this); connect(m_d->openAudioAction, SIGNAL(triggered()), this, SLOT(slotSelectAudioChannelFile())); m_d->audioOptionsMenu->addAction(m_d->openAudioAction); m_d->audioMuteAction = new QAction(i18nc("@item:inmenu", "Mute"), this); m_d->audioMuteAction->setCheckable(true); connect(m_d->audioMuteAction, SIGNAL(triggered(bool)), SLOT(slotAudioChannelMute(bool))); m_d->audioOptionsMenu->addAction(m_d->audioMuteAction); m_d->audioOptionsMenu->addAction(i18nc("@item:inmenu", "Remove audio"), this, SLOT(slotAudioChannelRemove())); m_d->audioOptionsMenu->addSeparator(); m_d->volumeSlider = new KisSliderSpinBox(this); m_d->volumeSlider->setRange(0, 100); m_d->volumeSlider->setSuffix(i18n("%")); m_d->volumeSlider->setPrefix(i18nc("@item:inmenu, slider", "Volume:")); m_d->volumeSlider->setSingleStep(1); m_d->volumeSlider->setPageStep(10); m_d->volumeSlider->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); connect(m_d->volumeSlider, SIGNAL(valueChanged(int)), SLOT(slotAudioVolumeChanged(int))); QWidgetAction *volumeAction = new QWidgetAction(m_d->audioOptionsMenu); volumeAction->setDefaultWidget(m_d->volumeSlider); m_d->audioOptionsMenu->addAction(volumeAction); m_d->audioOptionsButton->setMenu(m_d->audioOptionsMenu); /********** Frame Editing Context Menu ***********************************************/ m_d->colorSelector = new KisColorLabelSelectorWidget(this); + MouseClickIgnore* clickIgnore = new MouseClickIgnore(this); + m_d->colorSelector->installEventFilter(clickIgnore); m_d->colorSelectorAction = new QWidgetAction(this); m_d->colorSelectorAction->setDefaultWidget(m_d->colorSelector); connect(m_d->colorSelector, &KisColorLabelSelectorWidget::currentIndexChanged, this, &TimelineFramesView::slotColorLabelChanged); m_d->multiframeColorSelector = new KisColorLabelSelectorWidget(this); + m_d->multiframeColorSelector->installEventFilter(clickIgnore); m_d->multiframeColorSelectorAction = new QWidgetAction(this); m_d->multiframeColorSelectorAction->setDefaultWidget(m_d->multiframeColorSelector); connect(m_d->multiframeColorSelector, &KisColorLabelSelectorWidget::currentIndexChanged, this, &TimelineFramesView::slotColorLabelChanged); /********** Insert Keyframes Dialog **************************************************/ m_d->insertKeyframeDialog = new TimelineInsertKeyframeDialog(this); /********** Zoom Button **************************************************************/ m_d->zoomDragButton = new KisZoomButton(this); m_d->zoomDragButton->setAutoRaise(true); m_d->zoomDragButton->setIcon(KisIconUtils::loadIcon("zoom-horizontal")); m_d->zoomDragButton->setIconSize(QSize(20, 20)); // this icon is very small on windows if no explicitly set m_d->zoomDragButton->setToolTip(i18nc("@info:tooltip", "Zoom Timeline. Hold down and drag left or right.")); m_d->zoomDragButton->setPopupMode(QToolButton::InstantPopup); connect(m_d->zoomDragButton, SIGNAL(zoomLevelChanged(qreal)), SLOT(slotZoomButtonChanged(qreal))); connect(m_d->zoomDragButton, SIGNAL(zoomStarted(qreal)), SLOT(slotZoomButtonPressed(qreal))); setFramesPerSecond(12); setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); { QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(this); if( scroller ) { connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State))); } } connect(&m_d->selectionChangedCompressor, SIGNAL(timeout()), SLOT(slotSelectionChanged())); connect(&m_d->selectionChangedCompressor, SIGNAL(timeout()), SLOT(slotUpdateFrameActions())); { QClipboard *cb = QApplication::clipboard(); connect(cb, SIGNAL(dataChanged()), SLOT(slotUpdateFrameActions())); } } TimelineFramesView::~TimelineFramesView() { } void TimelineFramesView::setActionManager(KisActionManager *actionManager) { m_d->actionMan = actionManager; m_d->horizontalRuler->setActionManager(actionManager); if (actionManager) { KisAction *action = 0; action = m_d->actionMan->createAction("add_blank_frame"); connect(action, SIGNAL(triggered()), SLOT(slotAddBlankFrame())); action = m_d->actionMan->createAction("add_duplicate_frame"); connect(action, SIGNAL(triggered()), SLOT(slotAddDuplicateFrame())); action = m_d->actionMan->createAction("insert_keyframe_left"); connect(action, SIGNAL(triggered()), SLOT(slotInsertKeyframeLeft())); action = m_d->actionMan->createAction("insert_keyframe_right"); connect(action, SIGNAL(triggered()), SLOT(slotInsertKeyframeRight())); action = m_d->actionMan->createAction("insert_multiple_keyframes"); connect(action, SIGNAL(triggered()), SLOT(slotInsertMultipleKeyframes())); action = m_d->actionMan->createAction("remove_frames_and_pull"); connect(action, SIGNAL(triggered()), SLOT(slotRemoveSelectedFramesAndShift())); action = m_d->actionMan->createAction("remove_frames"); connect(action, SIGNAL(triggered()), SLOT(slotRemoveSelectedFrames())); action = m_d->actionMan->createAction("insert_hold_frame"); connect(action, SIGNAL(triggered()), SLOT(slotInsertHoldFrame())); action = m_d->actionMan->createAction("insert_multiple_hold_frames"); connect(action, SIGNAL(triggered()), SLOT(slotInsertMultipleHoldFrames())); action = m_d->actionMan->createAction("remove_hold_frame"); connect(action, SIGNAL(triggered()), SLOT(slotRemoveHoldFrame())); action = m_d->actionMan->createAction("remove_multiple_hold_frames"); connect(action, SIGNAL(triggered()), SLOT(slotRemoveMultipleHoldFrames())); action = m_d->actionMan->createAction("mirror_frames"); connect(action, SIGNAL(triggered()), SLOT(slotMirrorFrames())); action = m_d->actionMan->createAction("copy_frames_to_clipboard"); connect(action, SIGNAL(triggered()), SLOT(slotCopyFrames())); action = m_d->actionMan->createAction("cut_frames_to_clipboard"); connect(action, SIGNAL(triggered()), SLOT(slotCutFrames())); action = m_d->actionMan->createAction("paste_frames_from_clipboard"); connect(action, SIGNAL(triggered()), SLOT(slotPasteFrames())); action = m_d->actionMan->createAction("set_start_time"); connect(action, SIGNAL(triggered()), SLOT(slotSetStartTimeToCurrentPosition())); action = m_d->actionMan->createAction("set_end_time"); connect(action, SIGNAL(triggered()), SLOT(slotSetEndTimeToCurrentPosition())); action = m_d->actionMan->createAction("update_playback_range"); connect(action, SIGNAL(triggered()), SLOT(slotUpdatePlackbackRange())); action = m_d->actionMan->actionByName("pin_to_timeline"); m_d->pinLayerToTimelineAction = action; m_d->layerEditingMenu->addAction(action); } } void resizeToMinimalSize(QAbstractButton *w, int minimalSize) { QSize buttonSize = w->sizeHint(); if (buttonSize.height() > minimalSize) { buttonSize = QSize(minimalSize, minimalSize); } w->resize(buttonSize); } void TimelineFramesView::updateGeometries() { QTableView::updateGeometries(); const int availableHeight = m_d->horizontalRuler->height(); const int margin = 2; const int minimalSize = availableHeight - 2 * margin; resizeToMinimalSize(m_d->addLayersButton, minimalSize); resizeToMinimalSize(m_d->audioOptionsButton, minimalSize); resizeToMinimalSize(m_d->zoomDragButton, minimalSize); int x = 2 * margin; int y = (availableHeight - minimalSize) / 2; m_d->addLayersButton->move(x, 2 * y); m_d->audioOptionsButton->move(x + minimalSize + 2 * margin, 2 * y); const int availableWidth = m_d->layersHeader->width(); x = availableWidth - margin - minimalSize; m_d->zoomDragButton->move(x, 2 * y); } void TimelineFramesView::setModel(QAbstractItemModel *model) { TimelineFramesModel *framesModel = qobject_cast(model); m_d->model = framesModel; QTableView::setModel(model); connect(m_d->model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), this, SLOT(slotHeaderDataChanged(Qt::Orientation,int,int))); connect(m_d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(slotDataChanged(QModelIndex,QModelIndex))); connect(m_d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(slotReselectCurrentIndex())); connect(m_d->model, SIGNAL(sigInfiniteTimelineUpdateNeeded()), this, SLOT(slotUpdateInfiniteFramesCount())); connect(m_d->model, SIGNAL(sigAudioChannelChanged()), this, SLOT(slotUpdateAudioActions())); connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), &m_d->selectionChangedCompressor, SLOT(start())); connect(m_d->model, SIGNAL(sigEnsureRowVisible(int)), SLOT(slotEnsureRowVisible(int))); slotUpdateAudioActions(); } void TimelineFramesView::setFramesPerSecond(int fps) { m_d->fps = fps; m_d->horizontalRuler->setFramePerSecond(fps); // For some reason simple update sometimes doesn't work here, so // reset the whole header // // m_d->horizontalRuler->reset(); } void TimelineFramesView::slotZoomButtonPressed(qreal staticPoint) { m_d->zoomStillPointIndex = qIsNaN(staticPoint) ? currentIndex().column() : staticPoint; const int w = m_d->horizontalRuler->defaultSectionSize(); m_d->zoomStillPointOriginalOffset = w * m_d->zoomStillPointIndex - horizontalScrollBar()->value(); } void TimelineFramesView::slotZoomButtonChanged(qreal zoomLevel) { if (m_d->horizontalRuler->setZoom(zoomLevel)) { slotUpdateInfiniteFramesCount(); const int w = m_d->horizontalRuler->defaultSectionSize(); horizontalScrollBar()->setValue(w * m_d->zoomStillPointIndex - m_d->zoomStillPointOriginalOffset); viewport()->update(); } } void TimelineFramesView::slotColorLabelChanged(int label) { Q_FOREACH(QModelIndex index, selectedIndexes()) { m_d->model->setData(index, label, TimelineFramesModel::FrameColorLabelIndexRole); } KisImageConfig(false).setDefaultFrameColorLabel(label); } void TimelineFramesView::slotSelectAudioChannelFile() { if (!m_d->model) return; QString defaultDir = QStandardPaths::writableLocation(QStandardPaths::MusicLocation); const QString currentFile = m_d->model->audioChannelFileName(); QDir baseDir = QFileInfo(currentFile).absoluteDir(); if (baseDir.exists()) { defaultDir = baseDir.absolutePath(); } const QString result = KisImportExportManager::askForAudioFileName(defaultDir, this); const QFileInfo info(result); if (info.exists()) { m_d->model->setAudioChannelFileName(info.absoluteFilePath()); } } void TimelineFramesView::slotAudioChannelMute(bool value) { if (!m_d->model) return; if (value != m_d->model->isAudioMuted()) { m_d->model->setAudioMuted(value); } } void TimelineFramesView::slotUpdateIcons() { m_d->addLayersButton->setIcon(KisIconUtils::loadIcon("addlayer")); m_d->audioOptionsButton->setIcon(KisIconUtils::loadIcon("audio-none")); m_d->zoomDragButton->setIcon(KisIconUtils::loadIcon("zoom-horizontal")); } void TimelineFramesView::slotAudioChannelRemove() { if (!m_d->model) return; m_d->model->setAudioChannelFileName(QString()); } void TimelineFramesView::slotUpdateAudioActions() { if (!m_d->model) return; const QString currentFile = m_d->model->audioChannelFileName(); if (currentFile.isEmpty()) { m_d->openAudioAction->setText(i18nc("@item:inmenu", "Open audio...")); } else { QFileInfo info(currentFile); m_d->openAudioAction->setText(i18nc("@item:inmenu", "Change audio (%1)...", info.fileName())); } m_d->audioMuteAction->setChecked(m_d->model->isAudioMuted()); QIcon audioIcon; if (currentFile.isEmpty()) { audioIcon = KisIconUtils::loadIcon("audio-none"); } else { if (m_d->model->isAudioMuted()) { audioIcon = KisIconUtils::loadIcon("audio-volume-mute"); } else { audioIcon = KisIconUtils::loadIcon("audio-volume-high"); } } m_d->audioOptionsButton->setIcon(audioIcon); m_d->volumeSlider->setEnabled(!m_d->model->isAudioMuted()); KisSignalsBlocker b(m_d->volumeSlider); m_d->volumeSlider->setValue(qRound(m_d->model->audioVolume() * 100.0)); } void TimelineFramesView::slotAudioVolumeChanged(int value) { m_d->model->setAudioVolume(qreal(value) / 100.0); } void TimelineFramesView::slotUpdateInfiniteFramesCount() { if (horizontalScrollBar()->isSliderDown()) return; const int sectionWidth = m_d->horizontalRuler->defaultSectionSize(); const int calculatedIndex = (horizontalScrollBar()->value() + m_d->horizontalRuler->width() - 1) / sectionWidth; m_d->model->setLastVisibleFrame(calculatedIndex); } void TimelineFramesView::slotScrollerStateChanged( QScroller::State state ) { KisKineticScroller::updateCursor(this, state); } void TimelineFramesView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { QTableView::currentChanged(current, previous); if (previous.column() != current.column()) { m_d->model->setData(previous, false, TimelineFramesModel::ActiveFrameRole); m_d->model->setData(current, true, TimelineFramesModel::ActiveFrameRole); } } QItemSelectionModel::SelectionFlags TimelineFramesView::selectionCommand(const QModelIndex &index, const QEvent *event) const { // WARNING: Copy-pasted from KisNodeView! Please keep in sync! /** * Qt has a bug: when we Ctrl+click on an item, the item's * selections gets toggled on mouse *press*, whereas usually it is * done on mouse *release*. Therefore the user cannot do a * Ctrl+D&D with the default configuration. This code fixes the * problem by manually returning QItemSelectionModel::NoUpdate * flag when the user clicks on an item and returning * QItemSelectionModel::Toggle on release. */ if (event && (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) && index.isValid()) { const QMouseEvent *mevent = static_cast(event); if (mevent->button() == Qt::RightButton && selectionModel()->selectedIndexes().contains(index)) { // Allow calling context menu for multiple layers return QItemSelectionModel::NoUpdate; } if (event->type() == QEvent::MouseButtonPress && (mevent->modifiers() & Qt::ControlModifier)) { return QItemSelectionModel::NoUpdate; } if (event->type() == QEvent::MouseButtonRelease && (mevent->modifiers() & Qt::ControlModifier)) { return QItemSelectionModel::Toggle; } } return QAbstractItemView::selectionCommand(index, event); } void TimelineFramesView::slotSelectionChanged() { int minColumn = std::numeric_limits::max(); int maxColumn = std::numeric_limits::min(); foreach (const QModelIndex &idx, selectedIndexes()) { if (idx.column() > maxColumn) { maxColumn = idx.column(); } if (idx.column() < minColumn) { minColumn = idx.column(); } } KisTimeRange range; if (maxColumn > minColumn) { range = KisTimeRange(minColumn, maxColumn - minColumn + 1); } if (m_d->model->isPlaybackPaused()) { m_d->model->stopPlayback(); } m_d->model->setPlaybackRange(range); } void TimelineFramesView::slotReselectCurrentIndex() { QModelIndex index = currentIndex(); currentChanged(index, index); } void TimelineFramesView::slotEnsureRowVisible(int row) { QModelIndex index = currentIndex(); if (!index.isValid() || row < 0) return; index = m_d->model->index(row, index.column()); scrollTo(index); } void TimelineFramesView::slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { if (m_d->model->isPlaybackActive()) return; int selectedColumn = -1; for (int j = topLeft.column(); j <= bottomRight.column(); j++) { QVariant value = m_d->model->data( m_d->model->index(topLeft.row(), j), TimelineFramesModel::ActiveFrameRole); if (value.isValid() && value.toBool()) { selectedColumn = j; break; } } QModelIndex index = currentIndex(); if (!index.isValid() && selectedColumn < 0) { return; } if (selectedColumn == -1) { selectedColumn = index.column(); } if (selectedColumn != index.column() && !m_d->dragInProgress) { int row= index.isValid() ? index.row() : 0; selectionModel()->setCurrentIndex(m_d->model->index(row, selectedColumn), QItemSelectionModel::ClearAndSelect); } } void TimelineFramesView::slotHeaderDataChanged(Qt::Orientation orientation, int first, int last) { Q_UNUSED(first); Q_UNUSED(last); if (orientation == Qt::Horizontal) { const int newFps = m_d->model->headerData(0, Qt::Horizontal, TimelineFramesModel::FramesPerSecondRole).toInt(); if (newFps != m_d->fps) { setFramesPerSecond(newFps); } } } void TimelineFramesView::rowsInserted(const QModelIndex& parent, int start, int end) { QTableView::rowsInserted(parent, start, end); } inline bool isIndexDragEnabled(QAbstractItemModel *model, const QModelIndex &index) { return (model->flags(index) & Qt::ItemIsDragEnabled); } QStyleOptionViewItem TimelineFramesView::Private::viewOptionsV4() const { QStyleOptionViewItem option = q->viewOptions(); option.locale = q->locale(); option.locale.setNumberOptions(QLocale::OmitGroupSeparator); option.widget = q; return option; } QItemViewPaintPairs TimelineFramesView::Private::draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const { Q_ASSERT(r); QRect &rect = *r; const QRect viewportRect = q->viewport()->rect(); QItemViewPaintPairs ret; for (int i = 0; i < indexes.count(); ++i) { const QModelIndex &index = indexes.at(i); const QRect current = q->visualRect(index); if (current.intersects(viewportRect)) { ret += qMakePair(current, index); rect |= current; } } rect &= viewportRect; return ret; } QPixmap TimelineFramesView::Private::renderToPixmap(const QModelIndexList &indexes, QRect *r) const { Q_ASSERT(r); QItemViewPaintPairs paintPairs = draggablePaintPairs(indexes, r); if (paintPairs.isEmpty()) return QPixmap(); QPixmap pixmap(r->size()); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); QStyleOptionViewItem option = viewOptionsV4(); option.state |= QStyle::State_Selected; for (int j = 0; j < paintPairs.count(); ++j) { option.rect = paintPairs.at(j).first.translated(-r->topLeft()); const QModelIndex ¤t = paintPairs.at(j).second; //adjustViewOptionsForIndex(&option, current); q->itemDelegate(current)->paint(&painter, option, current); } return pixmap; } void TimelineFramesView::startDrag(Qt::DropActions supportedActions) { QModelIndexList indexes = selectionModel()->selectedIndexes(); if (!indexes.isEmpty() && m_d->modifiersCatcher->modifierPressed("offset-frame")) { QVector rows; int leftmostColumn = std::numeric_limits::max(); Q_FOREACH (const QModelIndex &index, indexes) { leftmostColumn = qMin(leftmostColumn, index.column()); if (!rows.contains(index.row())) { rows.append(index.row()); } } const int lastColumn = m_d->model->columnCount() - 1; selectionModel()->clear(); Q_FOREACH (const int row, rows) { QItemSelection sel(m_d->model->index(row, leftmostColumn), m_d->model->index(row, lastColumn)); selectionModel()->select(sel, QItemSelectionModel::Select); } supportedActions = Qt::MoveAction; { QModelIndexList indexes = selectedIndexes(); for(int i = indexes.count() - 1 ; i >= 0; --i) { if (!isIndexDragEnabled(m_d->model, indexes.at(i))) indexes.removeAt(i); } selectionModel()->clear(); if (indexes.count() > 0) { QMimeData *data = m_d->model->mimeData(indexes); if (!data) return; QRect rect; QPixmap pixmap = m_d->renderToPixmap(indexes, &rect); rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); QDrag *drag = new QDrag(this); drag->setPixmap(pixmap); drag->setMimeData(data); drag->setHotSpot(m_d->lastPressedPosition - rect.topLeft()); drag->exec(supportedActions, Qt::MoveAction); setCurrentIndex(currentIndex()); } } } else { /** * Workaround for Qt5's bug: if we start a dragging action right during * Shift-selection, Qt will get crazy. We cannot workaround it easily, * because we would need to fork mouseMoveEvent() for that (where the * decision about drag state is done). So we just abort dragging in that * case. * * BUG:373067 */ if (m_d->lastPressedModifier & Qt::ShiftModifier) { return; } /** * Workaround for Qt5's bugs: * * 1) Qt doesn't treat selection the selection on D&D * correctly, so we save it in advance and restore * afterwards. * * 2) There is a private variable in QAbstractItemView: * QAbstractItemView::Private::currentSelectionStartIndex. * It is initialized *only* when the setCurrentIndex() is called * explicitly on the view object, not on the selection model. * Therefore we should explicitly call setCurrentIndex() after * D&D, even if it already has *correct* value! * * 2) We should also call selectionModel()->select() * explicitly. There are two reasons for it: 1) Qt doesn't * maintain selection over D&D; 2) when reselecting single * element after D&D, Qt goes crazy, because it tries to * read *global* keyboard modifiers. Therefore if we are * dragging with Shift or Ctrl pressed it'll get crazy. So * just reset it explicitly. */ QModelIndexList selectionBefore = selectionModel()->selectedIndexes(); QModelIndex currentBefore = selectionModel()->currentIndex(); // initialize a global status variable m_d->dragWasSuccessful = false; QAbstractItemView::startDrag(supportedActions); QModelIndex newCurrent; QPoint selectionOffset; if (m_d->dragWasSuccessful) { newCurrent = currentIndex(); selectionOffset = QPoint(newCurrent.column() - currentBefore.column(), newCurrent.row() - currentBefore.row()); } else { newCurrent = currentBefore; selectionOffset = QPoint(); } setCurrentIndex(newCurrent); selectionModel()->clearSelection(); Q_FOREACH (const QModelIndex &idx, selectionBefore) { QModelIndex newIndex = model()->index(idx.row() + selectionOffset.y(), idx.column() + selectionOffset.x()); selectionModel()->select(newIndex, QItemSelectionModel::Select); } } } void TimelineFramesView::dragEnterEvent(QDragEnterEvent *event) { m_d->dragInProgress = true; m_d->model->setScrubState(true); QTableView::dragEnterEvent(event); } void TimelineFramesView::dragMoveEvent(QDragMoveEvent *event) { m_d->dragInProgress = true; m_d->model->setScrubState(true); QTableView::dragMoveEvent(event); if (event->isAccepted()) { QModelIndex index = indexAt(event->pos()); if (!m_d->model->canDropFrameData(event->mimeData(), index)) { event->ignore(); } else { selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); } } } void TimelineFramesView::dropEvent(QDropEvent *event) { m_d->dragInProgress = false; m_d->model->setScrubState(false); if (event->keyboardModifiers() & Qt::ControlModifier) { event->setDropAction(Qt::CopyAction); } QAbstractItemView::dropEvent(event); m_d->dragWasSuccessful = event->isAccepted(); } void TimelineFramesView::dragLeaveEvent(QDragLeaveEvent *event) { m_d->dragInProgress = false; m_d->model->setScrubState(false); QAbstractItemView::dragLeaveEvent(event); } void TimelineFramesView::createFrameEditingMenuActions(QMenu *menu, bool addFrameCreationActions) { slotUpdateFrameActions(); // calculate if selection range is set. This will determine if the update playback range is available QSet rows; int minColumn = 0; int maxColumn = 0; calculateSelectionMetrics(minColumn, maxColumn, rows); bool selectionExists = minColumn != maxColumn; menu->addSection(i18n("Edit Frames:")); menu->addSeparator(); if (selectionExists) { KisActionManager::safePopulateMenu(menu, "update_playback_range", m_d->actionMan); } else { KisActionManager::safePopulateMenu(menu, "set_start_time", m_d->actionMan); KisActionManager::safePopulateMenu(menu, "set_end_time", m_d->actionMan); } menu->addSeparator(); KisActionManager::safePopulateMenu(menu, "cut_frames_to_clipboard", m_d->actionMan); KisActionManager::safePopulateMenu(menu, "copy_frames_to_clipboard", m_d->actionMan); KisActionManager::safePopulateMenu(menu, "paste_frames_from_clipboard", m_d->actionMan); menu->addSeparator(); { //Frames submenu. QMenu *frames = menu->addMenu(i18nc("@item:inmenu", "Keyframes")); KisActionManager::safePopulateMenu(frames, "insert_keyframe_left", m_d->actionMan); KisActionManager::safePopulateMenu(frames, "insert_keyframe_right", m_d->actionMan); frames->addSeparator(); KisActionManager::safePopulateMenu(frames, "insert_multiple_keyframes", m_d->actionMan); } { //Holds submenu. QMenu *hold = menu->addMenu(i18nc("@item:inmenu", "Hold Frames")); KisActionManager::safePopulateMenu(hold, "insert_hold_frame", m_d->actionMan); KisActionManager::safePopulateMenu(hold, "remove_hold_frame", m_d->actionMan); hold->addSeparator(); KisActionManager::safePopulateMenu(hold, "insert_multiple_hold_frames", m_d->actionMan); KisActionManager::safePopulateMenu(hold, "remove_multiple_hold_frames", m_d->actionMan); } menu->addSeparator(); KisActionManager::safePopulateMenu(menu, "remove_frames", m_d->actionMan); KisActionManager::safePopulateMenu(menu, "remove_frames_and_pull", m_d->actionMan); menu->addSeparator(); if (addFrameCreationActions) { KisActionManager::safePopulateMenu(menu, "add_blank_frame", m_d->actionMan); KisActionManager::safePopulateMenu(menu, "add_duplicate_frame", m_d->actionMan); menu->addSeparator(); } } void TimelineFramesView::mousePressEvent(QMouseEvent *event) { QPersistentModelIndex index = indexAt(event->pos()); if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) { if (event->button() == Qt::RightButton) { // TODO: try calculate index under mouse cursor even when // it is outside any visible row qreal staticPoint = index.isValid() ? index.column() : currentIndex().column(); m_d->zoomDragButton->beginZoom(event->pos(), staticPoint); } else if (event->button() == Qt::LeftButton) { m_d->initialDragPanPos = event->pos(); m_d->initialDragPanValue = QPoint(horizontalScrollBar()->value(), verticalScrollBar()->value()); } event->accept(); } else if (event->button() == Qt::RightButton) { int numSelectedItems = selectionModel()->selectedIndexes().size(); if (index.isValid() && numSelectedItems <= 1 && m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { model()->setData(index, true, TimelineFramesModel::ActiveLayerRole); model()->setData(index, true, TimelineFramesModel::ActiveFrameRole); setCurrentIndex(index); if (model()->data(index, TimelineFramesModel::FrameExistsRole).toBool() || model()->data(index, TimelineFramesModel::SpecialKeyframeExists).toBool()) { { KisSignalsBlocker b(m_d->colorSelector); QVariant colorLabel = index.data(TimelineFramesModel::FrameColorLabelIndexRole); int labelIndex = colorLabel.isValid() ? colorLabel.toInt() : 0; m_d->colorSelector->setCurrentIndex(labelIndex); } QMenu menu; createFrameEditingMenuActions(&menu, false); menu.addSeparator(); menu.addAction(m_d->colorSelectorAction); menu.exec(event->globalPos()); } else { { KisSignalsBlocker b(m_d->colorSelector); const int labelIndex = KisImageConfig(true).defaultFrameColorLabel(); m_d->colorSelector->setCurrentIndex(labelIndex); } QMenu menu; createFrameEditingMenuActions(&menu, true); menu.addSeparator(); menu.addAction(m_d->colorSelectorAction); menu.exec(event->globalPos()); } } else if (numSelectedItems > 1) { int labelIndex = -1; bool haveFrames = false; Q_FOREACH(QModelIndex index, selectedIndexes()) { haveFrames |= index.data(TimelineFramesModel::FrameExistsRole).toBool(); QVariant colorLabel = index.data(TimelineFramesModel::FrameColorLabelIndexRole); if (colorLabel.isValid()) { if (labelIndex == -1) { // First label labelIndex = colorLabel.toInt(); } else if (labelIndex != colorLabel.toInt()) { // Mixed colors in selection labelIndex = -1; break; } } } if (haveFrames) { KisSignalsBlocker b(m_d->multiframeColorSelector); m_d->multiframeColorSelector->setCurrentIndex(labelIndex); } QMenu menu; createFrameEditingMenuActions(&menu, false); menu.addSeparator(); KisActionManager::safePopulateMenu(&menu, "mirror_frames", m_d->actionMan); menu.addSeparator(); menu.addAction(m_d->multiframeColorSelectorAction); menu.exec(event->globalPos()); } } else if (event->button() == Qt::MidButton) { QModelIndex index = model()->buddy(indexAt(event->pos())); if (index.isValid()) { QStyleOptionViewItem option = viewOptions(); option.rect = visualRect(index); // The offset of the headers is needed to get the correct position inside the view. m_d->tip.showTip(this, event->pos() + QPoint(verticalHeader()->width(), horizontalHeader()->height()), option, index); } event->accept(); } else { if (index.isValid()) { m_d->model->setLastClickedIndex(index); } m_d->lastPressedPosition = QPoint(horizontalOffset(), verticalOffset()) + event->pos(); m_d->lastPressedModifier = event->modifiers(); QAbstractItemView::mousePressEvent(event); } } void TimelineFramesView::mouseMoveEvent(QMouseEvent *e) { if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) { if (e->buttons() & Qt::RightButton) { m_d->zoomDragButton->continueZoom(e->pos()); } else if (e->buttons() & Qt::LeftButton) { QPoint diff = e->pos() - m_d->initialDragPanPos; QPoint offset = QPoint(m_d->initialDragPanValue.x() - diff.x(), m_d->initialDragPanValue.y() - diff.y()); const int height = m_d->layersHeader->defaultSectionSize(); horizontalScrollBar()->setValue(offset.x()); verticalScrollBar()->setValue(offset.y() / height); } e->accept(); } else if (e->buttons() == Qt::MidButton) { QModelIndex index = model()->buddy(indexAt(e->pos())); if (index.isValid()) { QStyleOptionViewItem option = viewOptions(); option.rect = visualRect(index); // The offset of the headers is needed to get the correct position inside the view. m_d->tip.showTip(this, e->pos() + QPoint(verticalHeader()->width(), horizontalHeader()->height()), option, index); } e->accept(); } else { m_d->model->setScrubState(true); QTableView::mouseMoveEvent(e); } } void TimelineFramesView::mouseReleaseEvent(QMouseEvent *e) { if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) { e->accept(); } else { m_d->model->setScrubState(false); QTableView::mouseReleaseEvent(e); } } void TimelineFramesView::wheelEvent(QWheelEvent *e) { QModelIndex index = currentIndex(); int column= -1; if (index.isValid()) { column= index.column() + ((e->delta() > 0) ? 1 : -1); } if (column >= 0 && !m_d->dragInProgress) { setCurrentIndex(m_d->model->index(index.row(), column)); } } void TimelineFramesView::slotUpdateLayersMenu() { QAction *action = 0; m_d->existingLayersMenu->clear(); QVariant value = model()->headerData(0, Qt::Vertical, TimelineFramesModel::OtherLayersRole); if (value.isValid()) { TimelineFramesModel::OtherLayersList list = value.value(); int i = 0; Q_FOREACH (const TimelineFramesModel::OtherLayer &l, list) { action = m_d->existingLayersMenu->addAction(l.name); action->setData(i++); } } } void TimelineFramesView::slotUpdateFrameActions() { if (!m_d->actionMan) return; const QModelIndexList editableIndexes = calculateSelectionSpan(false, true); const bool hasEditableFrames = !editableIndexes.isEmpty(); bool hasExistingFrames = false; Q_FOREACH (const QModelIndex &index, editableIndexes) { if (model()->data(index, TimelineFramesModel::FrameExistsRole).toBool()) { hasExistingFrames = true; break; } } auto enableAction = [this] (const QString &id, bool value) { KisAction *action = m_d->actionMan->actionByName(id); KIS_SAFE_ASSERT_RECOVER_RETURN(action); action->setEnabled(value); }; enableAction("add_blank_frame", hasEditableFrames); enableAction("add_duplicate_frame", hasEditableFrames); enableAction("insert_keyframe_left", hasEditableFrames); enableAction("insert_keyframe_right", hasEditableFrames); enableAction("insert_multiple_keyframes", hasEditableFrames); enableAction("remove_frames", hasEditableFrames && hasExistingFrames); enableAction("remove_frames_and_pull", hasEditableFrames); enableAction("insert_hold_frame", hasEditableFrames); enableAction("insert_multiple_hold_frames", hasEditableFrames); enableAction("remove_hold_frame", hasEditableFrames); enableAction("remove_multiple_hold_frames", hasEditableFrames); enableAction("mirror_frames", hasEditableFrames && editableIndexes.size() > 1); enableAction("copy_frames_to_clipboard", true); enableAction("cut_frames_to_clipboard", hasEditableFrames); } void TimelineFramesView::slotSetStartTimeToCurrentPosition() { m_d->model->setFullClipRangeStart(this->currentIndex().column()); } void TimelineFramesView::slotSetEndTimeToCurrentPosition() { m_d->model->setFullClipRangeEnd(this->currentIndex().column()); } void TimelineFramesView::slotUpdatePlackbackRange() { QSet rows; int minColumn = 0; int maxColumn = 0; calculateSelectionMetrics(minColumn, maxColumn, rows); m_d->model->setFullClipRangeStart(minColumn); m_d->model->setFullClipRangeEnd(maxColumn); } void TimelineFramesView::slotLayerContextMenuRequested(const QPoint &globalPos) { m_d->layerEditingMenu->exec(globalPos); } void TimelineFramesView::slotAddNewLayer() { QModelIndex index = currentIndex(); const int newRow = index.isValid() ? index.row() : 0; model()->insertRow(newRow); } void TimelineFramesView::slotAddExistingLayer(QAction *action) { QVariant value = action->data(); if (value.isValid()) { QModelIndex index = currentIndex(); const int newRow = index.isValid() ? index.row() + 1 : 0; m_d->model->insertOtherLayer(value.toInt(), newRow); } } void TimelineFramesView::slotRemoveLayer() { QModelIndex index = currentIndex(); if (!index.isValid()) return; model()->removeRow(index.row()); } void TimelineFramesView::slotAddBlankFrame() { QModelIndex index = currentIndex(); if (!index.isValid() || !m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { return; } m_d->model->createFrame(index); } void TimelineFramesView::slotAddDuplicateFrame() { QModelIndex index = currentIndex(); if (!index.isValid() || !m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { return; } m_d->model->copyFrame(index); } void TimelineFramesView::calculateSelectionMetrics(int &minColumn, int &maxColumn, QSet &rows) const { minColumn = std::numeric_limits::max(); maxColumn = std::numeric_limits::min(); Q_FOREACH (const QModelIndex &index, selectionModel()->selectedIndexes()) { if (!m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) continue; rows.insert(index.row()); minColumn = qMin(minColumn, index.column()); maxColumn = qMax(maxColumn, index.column()); } } void TimelineFramesView::insertKeyframes(int count, int timing, TimelineDirection direction, bool entireColumn) { QSet rows; int minColumn = 0, maxColumn = 0; calculateSelectionMetrics(minColumn, maxColumn, rows); if (count <= 0) { //Negative count? Use number of selected frames. count = qMax(1, maxColumn - minColumn + 1); } const int insertionColumn = direction == TimelineDirection::RIGHT ? maxColumn + 1 : minColumn; if (entireColumn) { rows.clear(); for (int i = 0; i < m_d->model->rowCount(); i++) { if (!m_d->model->data(m_d->model->index(i, insertionColumn), TimelineFramesModel::FrameEditableRole).toBool()) continue; rows.insert(i); } } if (!rows.isEmpty()) { #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) m_d->model->insertFrames(insertionColumn, QList(rows.begin(), rows.end()), count, timing); #else m_d->model->insertFrames(insertionColumn, QList::fromSet(rows), count, timing); #endif } } void TimelineFramesView::insertMultipleKeyframes(bool entireColumn) { int count, timing; TimelineDirection direction; if (m_d->insertKeyframeDialog->promptUserSettings(count, timing, direction)) { insertKeyframes(count, timing, direction, entireColumn); } } QModelIndexList TimelineFramesView::calculateSelectionSpan(bool entireColumn, bool editableOnly) const { QModelIndexList indexes; if (entireColumn) { QSet rows; int minColumn = 0; int maxColumn = 0; calculateSelectionMetrics(minColumn, maxColumn, rows); rows.clear(); for (int i = 0; i < m_d->model->rowCount(); i++) { if (editableOnly && !m_d->model->data(m_d->model->index(i, minColumn), TimelineFramesModel::FrameEditableRole).toBool()) continue; for (int column = minColumn; column <= maxColumn; column++) { indexes << m_d->model->index(i, column); } } } else { Q_FOREACH (const QModelIndex &index, selectionModel()->selectedIndexes()) { if (!editableOnly || m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { indexes << index; } } } return indexes; } void TimelineFramesView::slotRemoveSelectedFrames(bool entireColumn, bool pull) { const QModelIndexList selectedIndices = calculateSelectionSpan(entireColumn); if (!selectedIndices.isEmpty()) { if (pull) { m_d->model->removeFramesAndOffset(selectedIndices); } else { m_d->model->removeFrames(selectedIndices); } } } void TimelineFramesView::insertOrRemoveHoldFrames(int count, bool entireColumn) { QModelIndexList indexes; if (!entireColumn) { Q_FOREACH (const QModelIndex &index, selectionModel()->selectedIndexes()) { if (m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { indexes << index; } } } else { const int column = selectionModel()->currentIndex().column(); for (int i = 0; i < m_d->model->rowCount(); i++) { const QModelIndex index = m_d->model->index(i, column); if (m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { indexes << index; } } } if (!indexes.isEmpty()) { // add extra columns to the end of the timeline if we are adding hold frames // they will be truncated if we don't do this if (count > 0) { // Scan all the layers and find out what layer has the most keyframes // only keep a reference of layer that has the most keyframes int keyframesInLayerNode = 0; Q_FOREACH (const QModelIndex &index, indexes) { KisNodeSP layerNode = m_d->model->nodeAt(index); KisKeyframeChannel *channel = layerNode->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!channel) continue; if (keyframesInLayerNode < channel->allKeyframeIds().count()) { keyframesInLayerNode = channel->allKeyframeIds().count(); } } m_d->model->setLastVisibleFrame(m_d->model->columnCount() + count*keyframesInLayerNode); } m_d->model->insertHoldFrames(indexes, count); // bulk adding frames can add too many // trim timeline to clean up extra frames that might have been added slotUpdateInfiniteFramesCount(); } } void TimelineFramesView::insertOrRemoveMultipleHoldFrames(bool insertion, bool entireColumn) { bool ok = false; const int count = QInputDialog::getInt(this, i18nc("@title:window", "Insert or Remove Hold Frames"), i18nc("@label:spinbox", "Enter number of frames"), insertion ? m_d->insertKeyframeDialog->defaultTimingOfAddedFrames() : m_d->insertKeyframeDialog->defaultNumberOfHoldFramesToRemove(), 1, 10000, 1, &ok); if (ok) { if (insertion) { m_d->insertKeyframeDialog->setDefaultTimingOfAddedFrames(count); insertOrRemoveHoldFrames(count, entireColumn); } else { m_d->insertKeyframeDialog->setDefaultNumberOfHoldFramesToRemove(count); insertOrRemoveHoldFrames(-count, entireColumn); } } } void TimelineFramesView::slotMirrorFrames(bool entireColumn) { const QModelIndexList indexes = calculateSelectionSpan(entireColumn); if (!indexes.isEmpty()) { m_d->model->mirrorFrames(indexes); } } void TimelineFramesView::cutCopyImpl(bool entireColumn, bool copy) { const QModelIndexList selectedIndices = calculateSelectionSpan(entireColumn, !copy); if (selectedIndices.isEmpty()) return; int minColumn = std::numeric_limits::max(); int minRow = std::numeric_limits::max(); Q_FOREACH (const QModelIndex &index, selectedIndices) { minRow = qMin(minRow, index.row()); minColumn = qMin(minColumn, index.column()); } const QModelIndex baseIndex = m_d->model->index(minRow, minColumn); QMimeData *data = m_d->model->mimeDataExtended(selectedIndices, baseIndex, copy ? TimelineFramesModel::CopyFramesPolicy : TimelineFramesModel::MoveFramesPolicy); if (data) { QClipboard *cb = QApplication::clipboard(); cb->setMimeData(data); } } void TimelineFramesView::slotPasteFrames(bool entireColumn) { const QModelIndex currentIndex = !entireColumn ? this->currentIndex() : m_d->model->index(0, this->currentIndex().column()); if (!currentIndex.isValid()) return; QClipboard *cb = QApplication::clipboard(); const QMimeData *data = cb->mimeData(); if (data && data->hasFormat("application/x-krita-frame")) { bool dataMoved = false; bool result = m_d->model->dropMimeDataExtended(data, Qt::MoveAction, currentIndex, &dataMoved); if (result && dataMoved) { cb->clear(); } } } bool TimelineFramesView::viewportEvent(QEvent *event) { if (event->type() == QEvent::ToolTip && model()) { QHelpEvent *he = static_cast(event); QModelIndex index = model()->buddy(indexAt(he->pos())); if (index.isValid()) { QStyleOptionViewItem option = viewOptions(); option.rect = visualRect(index); // The offset of the headers is needed to get the correct position inside the view. m_d->tip.showTip(this, he->pos() + QPoint(verticalHeader()->width(), horizontalHeader()->height()), option, index); return true; } } return QTableView::viewportEvent(event); } diff --git a/plugins/dockers/layerdocker/LayerBox.cpp b/plugins/dockers/layerdocker/LayerBox.cpp index a3d7cb8d3d..e3ea871f63 100644 --- a/plugins/dockers/layerdocker/LayerBox.cpp +++ b/plugins/dockers/layerdocker/LayerBox.cpp @@ -1,1130 +1,1175 @@ /* * LayerBox.cc - part of Krita aka Krayon aka KimageShop * * Copyright (c) 2002 Patrick Julien * Copyright (C) 2006 Gábor Lehel * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007 Boudewijn Rempt * Copyright (c) 2011 José Luis Vergara * * 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 "LayerBox.h" +#include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_action_manager.h" #include "widgets/kis_cmb_composite.h" #include "kis_slider_spin_box.h" #include "KisViewManager.h" #include "kis_node_manager.h" #include "kis_node_model.h" #include "canvas/kis_canvas2.h" #include "kis_dummies_facade_base.h" #include "kis_shape_controller.h" #include "kis_selection_mask.h" #include "kis_config.h" #include "KisView.h" #include "krita_utils.h" #include "kis_color_label_selector_widget.h" #include "kis_signals_blocker.h" #include "kis_color_filter_combo.h" #include "kis_node_filter_proxy_model.h" #include "kis_selection.h" #include "kis_processing_applicator.h" #include "commands/kis_set_global_selection_command.h" #include "KisSelectionActionsAdapter.h" #include "kis_layer_utils.h" #include "ui_WdgLayerBox.h" #include "NodeView.h" #include "SyncButtonAndAction.h" class LayerBoxStyle : public QProxyStyle { public: LayerBoxStyle(QStyle *baseStyle = 0) : QProxyStyle(baseStyle) {} void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { if (element == QStyle::PE_IndicatorItemViewItemDrop) { QColor color(widget->palette().color(QPalette::Highlight).lighter()); if (option->rect.height() == 0) { QBrush brush(color); QRect r(option->rect); r.setTop(r.top() - 2); r.setBottom(r.bottom() + 2); painter->fillRect(r, brush); } else { color.setAlpha(200); QBrush brush(color); painter->fillRect(option->rect, brush); } } else { QProxyStyle::drawPrimitive(element, option, painter, widget); } } }; inline void LayerBox::connectActionToButton(KisViewManager* viewManager, QAbstractButton *button, const QString &id) { if (!viewManager || !button) return; KisAction *action = viewManager->actionManager()->actionByName(id); if (!action) return; connect(button, SIGNAL(clicked()), action, SLOT(trigger())); connect(action, SIGNAL(sigEnableSlaves(bool)), button, SLOT(setEnabled(bool))); connect(viewManager->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateIcons())); } inline void LayerBox::addActionToMenu(QMenu *menu, const QString &id) { if (m_canvas) { menu->addAction(m_canvas->viewManager()->actionManager()->actionByName(id)); } } LayerBox::LayerBox() : QDockWidget(i18n("Layers")) , m_canvas(0) , m_wdgLayerBox(new Ui_WdgLayerBox) , m_thumbnailCompressor(500, KisSignalCompressor::FIRST_INACTIVE) - , m_colorLabelCompressor(900, KisSignalCompressor::FIRST_INACTIVE) + , m_colorLabelCompressor(500, KisSignalCompressor::FIRST_INACTIVE) , m_thumbnailSizeCompressor(100, KisSignalCompressor::FIRST_INACTIVE) { KisConfig cfg(false); QWidget* mainWidget = new QWidget(this); setWidget(mainWidget); m_opacityDelayTimer.setSingleShot(true); m_wdgLayerBox->setupUi(mainWidget); m_wdgLayerBox->listLayers->setStyle(new LayerBoxStyle(m_wdgLayerBox->listLayers->style())); connect(m_wdgLayerBox->listLayers, SIGNAL(contextMenuRequested(QPoint,QModelIndex)), this, SLOT(slotContextMenuRequested(QPoint,QModelIndex))); connect(m_wdgLayerBox->listLayers, SIGNAL(collapsed(QModelIndex)), SLOT(slotCollapsed(QModelIndex))); connect(m_wdgLayerBox->listLayers, SIGNAL(expanded(QModelIndex)), SLOT(slotExpanded(QModelIndex))); connect(m_wdgLayerBox->listLayers, SIGNAL(selectionChanged(QModelIndexList)), SLOT(selectionChanged(QModelIndexList))); slotUpdateIcons(); m_wdgLayerBox->bnDelete->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnRaise->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnLower->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnProperties->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnDuplicate->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnLower->setEnabled(false); m_wdgLayerBox->bnRaise->setEnabled(false); if (cfg.sliderLabels()) { m_wdgLayerBox->opacityLabel->hide(); m_wdgLayerBox->doubleOpacity->setPrefix(QString("%1: ").arg(i18n("Opacity"))); } m_wdgLayerBox->doubleOpacity->setRange(0, 100, 0); m_wdgLayerBox->doubleOpacity->setSuffix(i18n("%")); connect(m_wdgLayerBox->doubleOpacity, SIGNAL(valueChanged(qreal)), SLOT(slotOpacitySliderMoved(qreal))); connect(&m_opacityDelayTimer, SIGNAL(timeout()), SLOT(slotOpacityChanged())); connect(m_wdgLayerBox->cmbComposite, SIGNAL(activated(int)), SLOT(slotCompositeOpChanged(int))); m_newLayerMenu = new QMenu(this); m_wdgLayerBox->bnAdd->setMenu(m_newLayerMenu); m_wdgLayerBox->bnAdd->setPopupMode(QToolButton::MenuButtonPopup); m_nodeModel = new KisNodeModel(this); m_filteringModel = new KisNodeFilterProxyModel(this); m_filteringModel->setNodeModel(m_nodeModel); /** * Connect model updateUI() to enable/disable controls. * Note: nodeActivated() is connected separately in setImage(), because * it needs particular order of calls: first the connection to the * node manager should be called, then updateUI() */ connect(m_nodeModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(modelReset()), SLOT(slotModelReset())); connect(m_nodeModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode())); connect(m_nodeModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode())); connect(m_nodeModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode())); connect(m_nodeModel, SIGNAL(modelReset()), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode())); KisAction *showGlobalSelectionMask = new KisAction(i18n("&Show Global Selection Mask"), this); showGlobalSelectionMask->setObjectName("show-global-selection-mask"); showGlobalSelectionMask->setActivationFlags(KisAction::ACTIVE_IMAGE); showGlobalSelectionMask->setToolTip(i18nc("@info:tooltip", "Shows global selection as a usual selection mask in Layers docker")); showGlobalSelectionMask->setCheckable(true); connect(showGlobalSelectionMask, SIGNAL(triggered(bool)), SLOT(slotEditGlobalSelection(bool))); m_actions.append(showGlobalSelectionMask); showGlobalSelectionMask->setChecked(cfg.showGlobalSelection()); m_colorSelector = new KisColorLabelSelectorWidget(this); + MouseClickIgnore* mouseEater = new MouseClickIgnore(this); + m_colorSelector->installEventFilter(mouseEater); connect(m_colorSelector, SIGNAL(currentIndexChanged(int)), SLOT(slotColorLabelChanged(int))); m_colorSelectorAction = new QWidgetAction(this); m_colorSelectorAction->setDefaultWidget(m_colorSelector); connect(m_nodeModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), &m_colorLabelCompressor, SLOT(start())); m_wdgLayerBox->listLayers->setModel(m_filteringModel); // this connection should be done *after* the setModel() call to // happen later than the internal selection model connect(m_filteringModel.data(), &KisNodeFilterProxyModel::rowsAboutToBeRemoved, this, &LayerBox::slotAboutToRemoveRows); - connect(m_wdgLayerBox->cmbFilter, SIGNAL(selectedColorsChanged()), SLOT(updateLayerFiltering())); + + //LayerFilter Menu + QMenu *layerFilterMenu = new QMenu(this); + m_wdgLayerBox->bnLayerFilters->setMenu(layerFilterMenu); + m_wdgLayerBox->bnLayerFilters->setPopupMode(QToolButton::InstantPopup); + + const QIcon filterIcon = KisIconUtils::loadIcon("view-filter"); + m_wdgLayerBox->bnLayerFilters->setIcon(filterIcon); + QPixmap filterEnabledPixmap = filterIcon.pixmap(64,64); + const QBitmap filterEnabledBitmask = filterEnabledPixmap.mask(); + filterEnabledPixmap.fill(palette().color(QPalette::Highlight)); + filterEnabledPixmap.setMask(filterEnabledBitmask); + const QIcon filterEnabledIcon = QIcon(filterEnabledPixmap); + + layerFilterWidget = new KisLayerFilterWidget(this); + connect(layerFilterWidget, SIGNAL(filteringOptionsChanged()), this, SLOT(updateLayerFiltering())); + connect(layerFilterWidget, &KisLayerFilterWidget::filteringOptionsChanged, [this, filterIcon, filterEnabledIcon](){ + if(layerFilterWidget->isCurrentlyFiltering()) { + m_wdgLayerBox->bnLayerFilters->setIcon(filterEnabledIcon); + } else { + m_wdgLayerBox->bnLayerFilters->setIcon(filterIcon); + } + + m_wdgLayerBox->bnLayerFilters->setSelectedColors(QList::fromSet(layerFilterWidget->getActiveColors())); + m_wdgLayerBox->bnLayerFilters->setTextFilter(layerFilterWidget->hasTextFilter()); + }); + + QWidgetAction *layerFilterMenuAction = new QWidgetAction(this); + layerFilterMenuAction->setDefaultWidget(layerFilterWidget); + layerFilterMenu->addAction(layerFilterMenuAction); setEnabled(false); connect(&m_thumbnailCompressor, SIGNAL(timeout()), SLOT(updateThumbnail())); connect(&m_colorLabelCompressor, SIGNAL(timeout()), SLOT(updateAvailableLabels())); - // set up the configure menu for changing thumbnail size QMenu* configureMenu = new QMenu(this); configureMenu->setStyleSheet("margin: 6px"); configureMenu->addSection(i18n("Thumbnail Size")); m_wdgLayerBox->configureLayerDockerToolbar->setMenu(configureMenu); m_wdgLayerBox->configureLayerDockerToolbar->setIcon(KisIconUtils::loadIcon("configure")); m_wdgLayerBox->configureLayerDockerToolbar->setPopupMode(QToolButton::InstantPopup); // add horizontal slider thumbnailSizeSlider = new QSlider(this); thumbnailSizeSlider->setOrientation(Qt::Horizontal); thumbnailSizeSlider->setRange(20, 80); thumbnailSizeSlider->setValue(cfg.layerThumbnailSize(false)); // grab this from the kritarc thumbnailSizeSlider->setMinimumHeight(20); thumbnailSizeSlider->setMinimumWidth(40); thumbnailSizeSlider->setTickInterval(5); QWidgetAction *sliderAction= new QWidgetAction(this); sliderAction->setDefaultWidget(thumbnailSizeSlider); configureMenu->addAction(sliderAction); connect(thumbnailSizeSlider, SIGNAL(sliderMoved(int)), &m_thumbnailSizeCompressor, SLOT(start())); connect(&m_thumbnailSizeCompressor, SIGNAL(timeout()), SLOT(slotUpdateThumbnailIconSize())); } LayerBox::~LayerBox() { delete m_wdgLayerBox; } void expandNodesRecursively(KisNodeSP root, QPointer filteringModel, NodeView *nodeView) { if (!root) return; if (filteringModel.isNull()) return; if (!nodeView) return; nodeView->blockSignals(true); KisNodeSP node = root->firstChild(); while (node) { QModelIndex idx = filteringModel->indexFromNode(node); if (idx.isValid()) { nodeView->setExpanded(idx, !node->collapsed()); } if (!node->collapsed() && node->childCount() > 0) { expandNodesRecursively(node, filteringModel, nodeView); } node = node->nextSibling(); } nodeView->blockSignals(false); } void LayerBox::slotAddLayerBnClicked() { if (m_canvas) { KisNodeList nodes = m_nodeManager->selectedNodes(); if (nodes.size() == 1) { KisAction *action = m_canvas->viewManager()->actionManager()->actionByName("add_new_paint_layer"); action->trigger(); } else { KisAction *action = m_canvas->viewManager()->actionManager()->actionByName("create_quick_group"); action->trigger(); } } } void LayerBox::setViewManager(KisViewManager* kisview) { m_nodeManager = kisview->nodeManager(); if (m_nodeManager) { connect(m_nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode())); } Q_FOREACH (KisAction *action, m_actions) { kisview->actionManager()-> addAction(action->objectName(), action); } connect(m_wdgLayerBox->bnAdd, SIGNAL(clicked()), this, SLOT(slotAddLayerBnClicked())); connectActionToButton(kisview, m_wdgLayerBox->bnDuplicate, "duplicatelayer"); KisActionManager *actionManager = kisview->actionManager(); KisAction *action = actionManager->createAction("RenameCurrentLayer"); Q_ASSERT(action); connect(action, SIGNAL(triggered()), this, SLOT(slotRenameCurrentNode())); m_propertiesAction = actionManager->createAction("layer_properties"); Q_ASSERT(m_propertiesAction); new SyncButtonAndAction(m_propertiesAction, m_wdgLayerBox->bnProperties, this); connect(m_propertiesAction, SIGNAL(triggered()), this, SLOT(slotPropertiesClicked())); m_removeAction = actionManager->createAction("remove_layer"); Q_ASSERT(m_removeAction); new SyncButtonAndAction(m_removeAction, m_wdgLayerBox->bnDelete, this); connect(m_removeAction, SIGNAL(triggered()), this, SLOT(slotRmClicked())); action = actionManager->createAction("move_layer_up"); Q_ASSERT(action); new SyncButtonAndAction(action, m_wdgLayerBox->bnRaise, this); connect(action, SIGNAL(triggered()), this, SLOT(slotRaiseClicked())); action = actionManager->createAction("move_layer_down"); Q_ASSERT(action); new SyncButtonAndAction(action, m_wdgLayerBox->bnLower, this); connect(action, SIGNAL(triggered()), this, SLOT(slotLowerClicked())); m_changeCloneSourceAction = actionManager->createAction("set-copy-from"); Q_ASSERT(m_changeCloneSourceAction); connect(m_changeCloneSourceAction, &KisAction::triggered, this, &LayerBox::slotChangeCloneSourceClicked); } void LayerBox::setCanvas(KoCanvasBase *canvas) { if (m_canvas == canvas) return; setEnabled(canvas != 0); if (m_canvas) { m_canvas->disconnectCanvasObserver(this); m_nodeModel->setDummiesFacade(0, 0, 0, 0, 0); m_selectionActionsAdapter.reset(); if (m_image) { KisImageAnimationInterface *animation = m_image->animationInterface(); animation->disconnect(this); } disconnect(m_image, 0, this, 0); disconnect(m_nodeManager, 0, this, 0); disconnect(m_nodeModel, 0, m_nodeManager, 0); m_nodeManager->slotSetSelectedNodes(KisNodeList()); } m_canvas = dynamic_cast(canvas); if (m_canvas) { m_image = m_canvas->image(); + emit imageChanged(); connect(m_image, SIGNAL(sigImageUpdated(QRect)), &m_thumbnailCompressor, SLOT(start())); KisDocument* doc = static_cast(m_canvas->imageView()->document()); KisShapeController *kritaShapeController = dynamic_cast(doc->shapeController()); KisDummiesFacadeBase *kritaDummiesFacade = static_cast(kritaShapeController); m_selectionActionsAdapter.reset(new KisSelectionActionsAdapter(m_canvas->viewManager()->selectionManager())); m_nodeModel->setDummiesFacade(kritaDummiesFacade, m_image, kritaShapeController, m_selectionActionsAdapter.data(), m_nodeManager); connect(m_image, SIGNAL(sigAboutToBeDeleted()), SLOT(notifyImageDeleted())); connect(m_image, SIGNAL(sigNodeCollapsedChanged()), SLOT(slotNodeCollapsedChanged())); // cold start if (m_nodeManager) { setCurrentNode(m_nodeManager->activeNode()); // Connection KisNodeManager -> LayerBox connect(m_nodeManager, SIGNAL(sigUiNeedChangeActiveNode(KisNodeSP)), this, SLOT(setCurrentNode(KisNodeSP))); connect(m_nodeManager, SIGNAL(sigUiNeedChangeSelectedNodes(QList)), SLOT(slotNodeManagerChangedSelection(QList))); } else { setCurrentNode(m_canvas->imageView()->currentNode()); } // Connection LayerBox -> KisNodeManager (isolate layer) connect(m_nodeModel, SIGNAL(toggleIsolateActiveNode()), m_nodeManager, SLOT(toggleIsolateActiveNode())); KisImageAnimationInterface *animation = m_image->animationInterface(); connect(animation, &KisImageAnimationInterface::sigUiTimeChanged, this, &LayerBox::slotImageTimeChanged); expandNodesRecursively(m_image->rootLayer(), m_filteringModel, m_wdgLayerBox->listLayers); m_wdgLayerBox->listLayers->scrollTo(m_wdgLayerBox->listLayers->currentIndex()); updateAvailableLabels(); addActionToMenu(m_newLayerMenu, "add_new_paint_layer"); addActionToMenu(m_newLayerMenu, "add_new_group_layer"); addActionToMenu(m_newLayerMenu, "add_new_clone_layer"); addActionToMenu(m_newLayerMenu, "add_new_shape_layer"); addActionToMenu(m_newLayerMenu, "add_new_adjustment_layer"); addActionToMenu(m_newLayerMenu, "add_new_fill_layer"); addActionToMenu(m_newLayerMenu, "add_new_file_layer"); m_newLayerMenu->addSeparator(); addActionToMenu(m_newLayerMenu, "add_new_transparency_mask"); addActionToMenu(m_newLayerMenu, "add_new_filter_mask"); addActionToMenu(m_newLayerMenu, "add_new_colorize_mask"); addActionToMenu(m_newLayerMenu, "add_new_transform_mask"); addActionToMenu(m_newLayerMenu, "add_new_selection_mask"); } } void LayerBox::unsetCanvas() { setEnabled(false); if (m_canvas) { m_newLayerMenu->clear(); } m_filteringModel->unsetDummiesFacade(); disconnect(m_image, 0, this, 0); disconnect(m_nodeManager, 0, this, 0); disconnect(m_nodeModel, 0, m_nodeManager, 0); m_nodeManager->slotSetSelectedNodes(KisNodeList()); m_canvas = 0; } void LayerBox::notifyImageDeleted() { setCanvas(0); } void LayerBox::updateUI() { if (!m_canvas) return; if (!m_nodeManager) return; KisNodeSP activeNode = m_nodeManager->activeNode(); if (activeNode != m_activeNode) { if( !m_activeNode.isNull() ) m_activeNode->disconnect(this); m_activeNode = activeNode; if (activeNode) { KisKeyframeChannel *opacityChannel = activeNode->getKeyframeChannel(KisKeyframeChannel::Opacity.id(), false); if (opacityChannel) { watchOpacityChannel(opacityChannel); } else { watchOpacityChannel(0); connect(activeNode.data(), &KisNode::keyframeChannelAdded, this, &LayerBox::slotKeyframeChannelAdded); } } } m_wdgLayerBox->bnRaise->setEnabled(activeNode && activeNode->isEditable(false) && (activeNode->nextSibling() || (activeNode->parent() && activeNode->parent() != m_image->root()))); m_wdgLayerBox->bnLower->setEnabled(activeNode && activeNode->isEditable(false) && (activeNode->prevSibling() || (activeNode->parent() && activeNode->parent() != m_image->root()))); m_wdgLayerBox->doubleOpacity->setEnabled(activeNode && activeNode->isEditable(false)); m_wdgLayerBox->cmbComposite->setEnabled(activeNode && activeNode->isEditable(false)); m_wdgLayerBox->cmbComposite->validate(m_image->colorSpace()); if (activeNode) { if (activeNode->inherits("KisColorizeMask") || activeNode->inherits("KisLayer")) { m_wdgLayerBox->doubleOpacity->setEnabled(true); if (!m_wdgLayerBox->doubleOpacity->isDragging()) { slotSetOpacity(activeNode->opacity() * 100.0 / 255); } const KoCompositeOp* compositeOp = activeNode->compositeOp(); if (compositeOp) { slotSetCompositeOp(compositeOp); } else { m_wdgLayerBox->cmbComposite->setEnabled(false); } const KisGroupLayer *group = qobject_cast(activeNode.data()); bool compositeSelectionActive = !(group && group->passThroughMode()); m_wdgLayerBox->cmbComposite->setEnabled(compositeSelectionActive); } else if (activeNode->inherits("KisMask")) { m_wdgLayerBox->cmbComposite->setEnabled(false); m_wdgLayerBox->doubleOpacity->setEnabled(false); } } } /** * This method is called *only* when non-GUI code requested the * change of the current node */ void LayerBox::setCurrentNode(KisNodeSP node) { m_filteringModel->setActiveNode(node); QModelIndex index = node ? m_filteringModel->indexFromNode(node) : QModelIndex(); m_filteringModel->setData(index, true, KisNodeModel::ActiveRole); updateUI(); } void LayerBox::slotModelReset() { if(m_nodeModel->hasDummiesFacade()) { QItemSelection selection; Q_FOREACH (const KisNodeSP node, m_nodeManager->selectedNodes()) { const QModelIndex &idx = m_filteringModel->indexFromNode(node); if(idx.isValid()){ QItemSelectionRange selectionRange(idx); selection << selectionRange; } } m_wdgLayerBox->listLayers->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); } updateUI(); } void LayerBox::slotSetCompositeOp(const KoCompositeOp* compositeOp) { KoID opId = KoCompositeOpRegistry::instance().getKoID(compositeOp->id()); m_wdgLayerBox->cmbComposite->blockSignals(true); m_wdgLayerBox->cmbComposite->selectCompositeOp(opId); m_wdgLayerBox->cmbComposite->blockSignals(false); } // range: 0-100 void LayerBox::slotSetOpacity(double opacity) { Q_ASSERT(opacity >= 0 && opacity <= 100); m_wdgLayerBox->doubleOpacity->blockSignals(true); m_wdgLayerBox->doubleOpacity->setValue(opacity); m_wdgLayerBox->doubleOpacity->blockSignals(false); } void LayerBox::slotContextMenuRequested(const QPoint &pos, const QModelIndex &index) { KisNodeList nodes = m_nodeManager->selectedNodes(); KisNodeSP activeNode = m_nodeManager->activeNode(); if (nodes.isEmpty() || !activeNode) return; if (m_canvas) { QMenu menu; const bool singleLayer = nodes.size() == 1; if (index.isValid()) { menu.addAction(m_propertiesAction); if (singleLayer) { addActionToMenu(&menu, "layer_style"); } Q_FOREACH(KisNodeSP node, nodes) { if (node && node->inherits("KisCloneLayer")) { menu.addAction(m_changeCloneSourceAction); break; } } { KisSignalsBlocker b(m_colorSelector); m_colorSelector->setCurrentIndex(singleLayer ? activeNode->colorLabelIndex() : -1); } + menu.addAction(m_colorSelectorAction); menu.addSeparator(); addActionToMenu(&menu, "cut_layer_clipboard"); addActionToMenu(&menu, "copy_layer_clipboard"); addActionToMenu(&menu, "paste_layer_from_clipboard"); menu.addAction(m_removeAction); addActionToMenu(&menu, "duplicatelayer"); addActionToMenu(&menu, "merge_layer"); addActionToMenu(&menu, "new_from_visible"); if (singleLayer) { addActionToMenu(&menu, "flatten_image"); addActionToMenu(&menu, "flatten_layer"); } menu.addSeparator(); QMenu *selectMenu = menu.addMenu(i18n("&Select")); addActionToMenu(selectMenu, "select_all_layers"); addActionToMenu(selectMenu, "select_visible_layers"); addActionToMenu(selectMenu, "select_invisible_layers"); addActionToMenu(selectMenu, "select_locked_layers"); addActionToMenu(selectMenu, "select_unlocked_layers"); QMenu *groupMenu = menu.addMenu(i18n("&Group")); addActionToMenu(groupMenu, "create_quick_group"); addActionToMenu(groupMenu, "create_quick_clipping_group"); addActionToMenu(groupMenu, "quick_ungroup"); QMenu *locksMenu = menu.addMenu(i18n("&Toggle Locks && Visibility")); addActionToMenu(locksMenu, "toggle_layer_visibility"); addActionToMenu(locksMenu, "toggle_layer_lock"); addActionToMenu(locksMenu, "toggle_layer_inherit_alpha"); addActionToMenu(locksMenu, "toggle_layer_alpha_lock"); if (singleLayer) { QMenu *addLayerMenu = menu.addMenu(i18n("&Add")); addActionToMenu(addLayerMenu, "add_new_transparency_mask"); addActionToMenu(addLayerMenu, "add_new_filter_mask"); addActionToMenu(addLayerMenu, "add_new_colorize_mask"); addActionToMenu(addLayerMenu, "add_new_transform_mask"); addActionToMenu(addLayerMenu, "add_new_selection_mask"); addLayerMenu->addSeparator(); addActionToMenu(addLayerMenu, "add_new_clone_layer"); QMenu *convertToMenu = menu.addMenu(i18n("&Convert")); addActionToMenu(convertToMenu, "convert_to_paint_layer"); addActionToMenu(convertToMenu, "convert_to_transparency_mask"); addActionToMenu(convertToMenu, "convert_to_filter_mask"); addActionToMenu(convertToMenu, "convert_to_selection_mask"); addActionToMenu(convertToMenu, "convert_to_file_layer"); QMenu *splitAlphaMenu = menu.addMenu(i18n("S&plit Alpha")); addActionToMenu(splitAlphaMenu, "split_alpha_into_mask"); addActionToMenu(splitAlphaMenu, "split_alpha_write"); addActionToMenu(splitAlphaMenu, "split_alpha_save_merged"); } else { QMenu *addLayerMenu = menu.addMenu(i18n("&Add")); addActionToMenu(addLayerMenu, "add_new_clone_layer"); } menu.addSeparator(); addActionToMenu(&menu, "pin_to_timeline"); if (singleLayer) { KisNodeSP node = m_filteringModel->nodeFromIndex(index); if (node && !node->inherits("KisTransformMask")) { addActionToMenu(&menu, "isolate_active_layer"); } addActionToMenu(&menu, "selectopaque"); } } menu.exec(pos); } } void LayerBox::slotMinimalView() { m_wdgLayerBox->listLayers->setDisplayMode(NodeView::MinimalMode); } void LayerBox::slotDetailedView() { m_wdgLayerBox->listLayers->setDisplayMode(NodeView::DetailedMode); } void LayerBox::slotThumbnailView() { m_wdgLayerBox->listLayers->setDisplayMode(NodeView::ThumbnailMode); } void LayerBox::slotRmClicked() { if (!m_canvas) return; m_nodeManager->removeNode(); } void LayerBox::slotRaiseClicked() { if (!m_canvas) return; m_nodeManager->raiseNode(); } void LayerBox::slotLowerClicked() { if (!m_canvas) return; m_nodeManager->lowerNode(); } void LayerBox::slotPropertiesClicked() { if (!m_canvas) return; if (KisNodeSP active = m_nodeManager->activeNode()) { m_nodeManager->nodeProperties(active); } } void LayerBox::slotChangeCloneSourceClicked() { if (!m_canvas) return; m_nodeManager->changeCloneSource(); } void LayerBox::slotCompositeOpChanged(int index) { Q_UNUSED(index); if (!m_canvas) return; QString compositeOp = m_wdgLayerBox->cmbComposite->selectedCompositeOp().id(); m_nodeManager->nodeCompositeOpChanged(m_nodeManager->activeColorSpace()->compositeOp(compositeOp)); } void LayerBox::slotOpacityChanged() { if (!m_canvas) return; m_blockOpacityUpdate = true; m_nodeManager->nodeOpacityChanged(m_newOpacity); m_blockOpacityUpdate = false; } void LayerBox::slotOpacitySliderMoved(qreal opacity) { m_newOpacity = opacity; m_opacityDelayTimer.start(200); } void LayerBox::slotCollapsed(const QModelIndex &index) { KisNodeSP node = m_filteringModel->nodeFromIndex(index); if (node) { node->setCollapsed(true); } } void LayerBox::slotExpanded(const QModelIndex &index) { KisNodeSP node = m_filteringModel->nodeFromIndex(index); if (node) { node->setCollapsed(false); } } void LayerBox::slotSelectOpaque() { if (!m_canvas) return; QAction *action = m_canvas->viewManager()->actionManager()->actionByName("selectopaque"); if (action) { action->trigger(); } } void LayerBox::slotNodeCollapsedChanged() { expandNodesRecursively(m_image->rootLayer(), m_filteringModel, m_wdgLayerBox->listLayers); } inline bool isSelectionMask(KisNodeSP node) { return dynamic_cast(node.data()); } KisNodeSP LayerBox::findNonHidableNode(KisNodeSP startNode) { if (KisNodeManager::isNodeHidden(startNode, true) && startNode->parent() && !startNode->parent()->parent()) { KisNodeSP node = startNode->prevSibling(); while (node && KisNodeManager::isNodeHidden(node, true)) { node = node->prevSibling(); } if (!node) { node = startNode->nextSibling(); while (node && KisNodeManager::isNodeHidden(node, true)) { node = node->nextSibling(); } } if (!node) { node = m_image->root()->lastChild(); while (node && KisNodeManager::isNodeHidden(node, true)) { node = node->prevSibling(); } } KIS_ASSERT_RECOVER_NOOP(node && "cannot activate any node!"); startNode = node; } return startNode; } void LayerBox::slotEditGlobalSelection(bool showSelections) { KisNodeSP lastActiveNode = m_nodeManager->activeNode(); KisNodeSP activateNode = lastActiveNode; KisSelectionMaskSP globalSelectionMask; if (!showSelections) { activateNode = m_savedNodeBeforeEditSelectionMode ? KisNodeSP(m_savedNodeBeforeEditSelectionMode) : findNonHidableNode(activateNode); } m_nodeModel->setShowGlobalSelection(showSelections); globalSelectionMask = m_image->rootLayer()->selectionMask(); // try to find deactivated, but visible masks if (!globalSelectionMask) { KoProperties properties; properties.setProperty("visible", true); QList masks = m_image->rootLayer()->childNodes(QStringList("KisSelectionMask"), properties); if (!masks.isEmpty()) { globalSelectionMask = dynamic_cast(masks.first().data()); } } // try to find at least any selection mask if (!globalSelectionMask) { KoProperties properties; QList masks = m_image->rootLayer()->childNodes(QStringList("KisSelectionMask"), properties); if (!masks.isEmpty()) { globalSelectionMask = dynamic_cast(masks.first().data()); } } if (globalSelectionMask) { if (showSelections) { activateNode = globalSelectionMask; } } if (activateNode != lastActiveNode) { m_nodeManager->slotNonUiActivatedNode(activateNode); } else if (lastActiveNode) { setCurrentNode(lastActiveNode); } if (showSelections && !globalSelectionMask) { KisProcessingApplicator applicator(m_image, 0, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Quick Selection Mask")); applicator.applyCommand( new KisLayerUtils::KeepNodesSelectedCommand( m_nodeManager->selectedNodes(), KisNodeList(), lastActiveNode, 0, m_image, false), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KisSetEmptyGlobalSelectionCommand(m_image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KisLayerUtils::SelectGlobalSelectionMask(m_image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.end(); } else if (!showSelections && globalSelectionMask && globalSelectionMask->selection()->selectedRect().isEmpty()) { KisProcessingApplicator applicator(m_image, 0, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Cancel Quick Selection Mask")); applicator.applyCommand(new KisSetGlobalSelectionCommand(m_image, 0), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.end(); } if (showSelections) { m_savedNodeBeforeEditSelectionMode = lastActiveNode; } } void LayerBox::selectionChanged(const QModelIndexList selection) { if (!m_nodeManager) return; /** * When the user clears the extended selection by clicking on the * empty area of the docker, the selection should be reset on to * the active layer, which might be even unselected(!). */ if (selection.isEmpty() && m_nodeManager->activeNode()) { QModelIndex selectedIndex = m_filteringModel->indexFromNode(m_nodeManager->activeNode()); m_wdgLayerBox->listLayers->selectionModel()-> setCurrentIndex(selectedIndex, QItemSelectionModel::ClearAndSelect); return; } QList selectedNodes; Q_FOREACH (const QModelIndex &idx, selection) { selectedNodes << m_filteringModel->nodeFromIndex(idx); } m_nodeManager->slotSetSelectedNodes(selectedNodes); updateUI(); } void LayerBox::slotAboutToRemoveRows(const QModelIndex &parent, int start, int end) { /** * Qt has changed its behavior when deleting an item. Previously * the selection priority was on the next item in the list, and * now it has shanged to the previous item. Here we just adjust * the selected item after the node removal. Please take care that * this method overrides what was done by the corresponding method * of QItemSelectionModel, which *has already done* its work. That * is why we use (start - 1) and (end + 1) in the activation * condition. * * See bug: https://bugs.kde.org/show_bug.cgi?id=345601 */ QModelIndex currentIndex = m_wdgLayerBox->listLayers->currentIndex(); QAbstractItemModel *model = m_filteringModel; if (currentIndex.isValid() && parent == currentIndex.parent() && currentIndex.row() >= start - 1 && currentIndex.row() <= end + 1) { QModelIndex old = currentIndex; if (model && end < model->rowCount(parent) - 1) // there are rows left below the change currentIndex = model->index(end + 1, old.column(), parent); else if (model && start > 0) // there are rows left above the change currentIndex = model->index(start - 1, old.column(), parent); else // there are no rows left in the table currentIndex = QModelIndex(); if (currentIndex.isValid() && currentIndex != old) { m_wdgLayerBox->listLayers->setCurrentIndex(currentIndex); } } } void LayerBox::slotNodeManagerChangedSelection(const KisNodeList &nodes) { if (!m_nodeManager) return; QModelIndexList newSelection; Q_FOREACH(KisNodeSP node, nodes) { newSelection << m_filteringModel->indexFromNode(node); } QItemSelectionModel *model = m_wdgLayerBox->listLayers->selectionModel(); if (KritaUtils::compareListsUnordered(newSelection, model->selectedIndexes())) { return; } QItemSelection selection; Q_FOREACH(const QModelIndex &idx, newSelection) { selection.select(idx, idx); } model->select(selection, QItemSelectionModel::ClearAndSelect); } void LayerBox::updateThumbnail() { m_wdgLayerBox->listLayers->updateNode(m_wdgLayerBox->listLayers->currentIndex()); } void LayerBox::slotRenameCurrentNode() { m_wdgLayerBox->listLayers->edit(m_wdgLayerBox->listLayers->currentIndex()); } void LayerBox::slotColorLabelChanged(int label) { - KisNodeList nodes = m_nodeManager->selectedNodes(); + KisNodeList selectedNodes = m_nodeManager->selectedNodes(); + + Q_FOREACH(KisNodeSP selectedNode, selectedNodes) { + //Always apply label to selected nodes.. + selectedNode->setColorLabelIndex(label); + + //Apply label only to unlabelled children.. + KisNodeList children = selectedNode->childNodes(QStringList(), KoProperties()); - Q_FOREACH(KisNodeSP node, nodes) { auto applyLabelFunc = - [label](KisNodeSP node) { - node->setColorLabelIndex(label); + [label](KisNodeSP child) { + if (child->colorLabelIndex() == 0) { + child->setColorLabelIndex(label); + } }; - KisLayerUtils::recursiveApplyNodes(node, applyLabelFunc); + Q_FOREACH(KisNodeSP child, children) { + KisLayerUtils::recursiveApplyNodes(child, applyLabelFunc); + } } } void LayerBox::updateAvailableLabels() { if (!m_image) return; - m_wdgLayerBox->cmbFilter->updateAvailableLabels(m_image->root()); + layerFilterWidget->updateColorLabels(m_image->root()); } void LayerBox::updateLayerFiltering() { - m_filteringModel->setAcceptedLabels(m_wdgLayerBox->cmbFilter->selectedColors()); + m_filteringModel->setAcceptedLabels(layerFilterWidget->getActiveColors()); + m_filteringModel->setTextFilter(layerFilterWidget->getTextFilter()); } void LayerBox::slotKeyframeChannelAdded(KisKeyframeChannel *channel) { if (channel->id() == KisKeyframeChannel::Opacity.id()) { watchOpacityChannel(channel); } } void LayerBox::watchOpacityChannel(KisKeyframeChannel *channel) { if (m_opacityChannel) { m_opacityChannel->disconnect(this); } m_opacityChannel = channel; if (m_opacityChannel) { connect(m_opacityChannel, SIGNAL(sigKeyframeAdded(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP))); connect(m_opacityChannel, SIGNAL(sigKeyframeRemoved(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP))); connect(m_opacityChannel, SIGNAL(sigKeyframeMoved(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeMoved(KisKeyframeSP))); connect(m_opacityChannel, SIGNAL(sigKeyframeChanged(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP))); } } void LayerBox::slotOpacityKeyframeChanged(KisKeyframeSP keyframe) { Q_UNUSED(keyframe); if (m_blockOpacityUpdate) return; updateUI(); } void LayerBox::slotOpacityKeyframeMoved(KisKeyframeSP keyframe, int fromTime) { Q_UNUSED(fromTime); slotOpacityKeyframeChanged(keyframe); } void LayerBox::slotImageTimeChanged(int time) { Q_UNUSED(time); updateUI(); } void LayerBox::slotForgetAboutSavedNodeBeforeEditSelectionMode() { m_savedNodeBeforeEditSelectionMode = 0; } void LayerBox::slotUpdateIcons() { m_wdgLayerBox->bnAdd->setIcon(KisIconUtils::loadIcon("addlayer")); m_wdgLayerBox->bnRaise->setIcon(KisIconUtils::loadIcon("arrowupblr")); m_wdgLayerBox->bnDelete->setIcon(KisIconUtils::loadIcon("deletelayer")); m_wdgLayerBox->bnLower->setIcon(KisIconUtils::loadIcon("arrowdown")); m_wdgLayerBox->bnProperties->setIcon(KisIconUtils::loadIcon("properties")); m_wdgLayerBox->bnDuplicate->setIcon(KisIconUtils::loadIcon("duplicatelayer")); // call child function about needing to update icons m_wdgLayerBox->listLayers->slotUpdateIcons(); } void LayerBox::slotUpdateThumbnailIconSize() { KisConfig cfg(false); cfg.setLayerThumbnailSize(thumbnailSizeSlider->value()); // this is a hack to force the layers list to update its display and // re-layout all the layers with the new thumbnail size resize(this->width()+1, this->height()+1); resize(this->width()-1, this->height()-1); } #include "moc_LayerBox.cpp" diff --git a/plugins/dockers/layerdocker/LayerBox.h b/plugins/dockers/layerdocker/LayerBox.h index 799b8ea8a3..766564fbaf 100644 --- a/plugins/dockers/layerdocker/LayerBox.h +++ b/plugins/dockers/layerdocker/LayerBox.h @@ -1,205 +1,209 @@ /* * LayerBox.h - part of Krita aka Krayon aka KimageShop * * Copyright (c) 2002 Patrick Julien * Copyright (C) 2006 Gábor Lehel * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007-2009 Boudewijn Rempt * * 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_LAYERBOX_H #define KIS_LAYERBOX_H #include #include #include #include #include #include #include #include #include #include "kis_action.h" #include "KisViewManager.h" #include "kis_mainwindow_observer.h" #include "kis_signal_compressor.h" +#include "kis_layer_filter_widget.h" #include class QModelIndex; typedef QList QModelIndexList; class QMenu; class QAbstractButton; class KoCompositeOp; class KisCanvas2; class KisNodeModel; class KisNodeFilterProxyModel; class Ui_WdgLayerBox; class KisNodeJugglerCompressed; class KisColorLabelSelectorWidget; class QWidgetAction; class KisKeyframeChannel; class KisSelectionActionsAdapter; /** * A widget that shows a visualization of the layer structure. * * The center of the layer box is KisNodeModel, which shows the actual layers. * This widget adds docking functionality and command buttons. * */ class LayerBox : public QDockWidget, public KisMainwindowObserver { Q_OBJECT public: LayerBox(); ~LayerBox() override; QString observerName() override { return "LayerBox"; } /// reimplemented from KisMainwindowObserver void setViewManager(KisViewManager* kisview) override; void setCanvas(KoCanvasBase *canvas) override; void unsetCanvas() override; private Q_SLOTS: void notifyImageDeleted(); void slotContextMenuRequested(const QPoint &pos, const QModelIndex &index); void slotMinimalView(); void slotDetailedView(); void slotThumbnailView(); // From the node manager to the layerbox void slotSetCompositeOp(const KoCompositeOp* compositeOp); void slotSetOpacity(double opacity); void updateUI(); void setCurrentNode(KisNodeSP node); void slotModelReset(); // from the layerbox to the node manager void slotRmClicked(); void slotRaiseClicked(); void slotLowerClicked(); void slotPropertiesClicked(); void slotChangeCloneSourceClicked(); void slotCompositeOpChanged(int index); void slotOpacityChanged(); void slotOpacitySliderMoved(qreal opacity); void slotCollapsed(const QModelIndex &index); void slotExpanded(const QModelIndex &index); void slotSelectOpaque(); void slotNodeCollapsedChanged(); void slotEditGlobalSelection(bool showSelections); void slotRenameCurrentNode(); void slotAboutToRemoveRows(const QModelIndex &parent, int first, int last); void selectionChanged(const QModelIndexList selection); void slotNodeManagerChangedSelection(const QList &nodes); void slotColorLabelChanged(int index); void slotUpdateIcons(); void slotAddLayerBnClicked(); void updateThumbnail(); void updateAvailableLabels(); void updateLayerFiltering(); void slotUpdateThumbnailIconSize(); // Opacity keyframing void slotKeyframeChannelAdded(KisKeyframeChannel *channel); void slotOpacityKeyframeChanged(KisKeyframeSP keyframe); void slotOpacityKeyframeMoved(KisKeyframeSP keyframe, int fromTime); void slotImageTimeChanged(int time); - void slotForgetAboutSavedNodeBeforeEditSelectionMode(); +Q_SIGNALS: + void imageChanged(); + private: inline void connectActionToButton(KisViewManager* view, QAbstractButton *button, const QString &id); inline void addActionToMenu(QMenu *menu, const QString &id); void watchOpacityChannel(KisKeyframeChannel *channel); KisNodeSP findNonHidableNode(KisNodeSP startNode); private: QPointer m_canvas; QScopedPointer m_selectionActionsAdapter; QMenu *m_newLayerMenu; KisImageWSP m_image; QPointer m_nodeModel; QPointer m_filteringModel; QPointer m_nodeManager; QPointer m_colorSelector; QPointer m_colorSelectorAction; Ui_WdgLayerBox* m_wdgLayerBox; QTimer m_opacityDelayTimer; int m_newOpacity; QVector m_actions; KisAction* m_removeAction; KisAction* m_propertiesAction; KisAction* m_changeCloneSourceAction; KisSignalCompressor m_thumbnailCompressor; KisSignalCompressor m_colorLabelCompressor; KisSignalCompressor m_thumbnailSizeCompressor; + KisLayerFilterWidget* layerFilterWidget; QSlider* thumbnailSizeSlider; KisNodeSP m_activeNode; KisNodeWSP m_savedNodeBeforeEditSelectionMode; QPointer m_opacityChannel; bool m_blockOpacityUpdate {false}; }; class LayerBoxFactory : public KoDockFactoryBase { public: LayerBoxFactory() { } QString id() const override { return QString("KisLayerBox"); } QDockWidget* createDockWidget() override { LayerBox * dockWidget = new LayerBox(); dockWidget->setObjectName(id()); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; #endif // KIS_LAYERBOX_H diff --git a/plugins/dockers/layerdocker/WdgLayerBox.ui b/plugins/dockers/layerdocker/WdgLayerBox.ui index 6822e8329c..9b3ca1d4e0 100644 --- a/plugins/dockers/layerdocker/WdgLayerBox.ui +++ b/plugins/dockers/layerdocker/WdgLayerBox.ui @@ -1,349 +1,377 @@ WdgLayerBox 0 0 220 384 1 0 0 0 0 0 0 0 0 0 0 + + + 0 + 20 + + Blending Mode Select the blending mode for the layer. - - + + - 22 - 22 + 32 + 20 + + + 32 + 20 + + + + ... + 0 0 Opacity: 0 0 Layer Opacity Adjust the transparency of the layer 0 0 + + + 32 + 32 + + + + + 32 + 32 + + ... 22 22 0 0 2 0 0 28 16777215 28 ... true 28 28 Duplicate layer or mask ... 22 22 true 28 28 Move layer or mask down ... 22 22 true 28 28 Move layer or mask up ... 22 22 true 28 28 View or change the layer properties ... 22 22 true Qt::Horizontal QSizePolicy::Expanding 0 28 28 28 Delete the layer or mask ... 22 22 true - - KisCompositeOpComboBox - QComboBox -
widgets/kis_cmb_composite.h
-
KisDoubleSliderSpinBox QWidget
kis_slider_spin_box.h
+ 1 +
+ + KisCompositeOpComboBox + QComboBox +
widgets/kis_cmb_composite.h
NodeView
NodeView.h
KisToolButton QToolButton
kis_tool_button.h
- KisColorFilterCombo - QComboBox -
kis_color_filter_combo.h
+ KisLayerFilterWidgetToolButton + QToolButton +
libs/ui/widgets/kis_layer_filter_widget.h
cmbComposite bnDuplicate bnLower bnRaise bnProperties bnDelete