From ee83ea5423e3a6d3c7b598953d37dadb02f8060d Mon Sep 17 00:00:00 2001 From: rtlhq Date: Mon, 21 Nov 2022 12:38:32 +0800 Subject: [PATCH] Import Upstream version 22.08.3 --- .gitignore | 30 + .gitlab-ci.yml | 7 + .kde-ci.yml | 15 + .krazy | 1 + AUTHORS | 38 + BUGS | 138 + CMakeLists.txt | 365 ++ COPYING | 139 + COPYING.DOC | 397 ++ COPYING.LIB | 481 +++ ChangeLog | 8 + Messages.sh | 3 + NEWS | 386 ++ README.md | 104 + .../effects/kpEffectBalanceCommand.cpp | 55 + .../imagelib/effects/kpEffectBalanceCommand.h | 56 + .../effects/kpEffectBlurSharpenCommand.cpp | 65 + .../effects/kpEffectBlurSharpenCommand.h | 57 + .../imagelib/effects/kpEffectClearCommand.cpp | 110 + .../imagelib/effects/kpEffectClearCommand.h | 62 + .../imagelib/effects/kpEffectCommandBase.cpp | 124 + .../imagelib/effects/kpEffectCommandBase.h | 67 + .../effects/kpEffectEmbossCommand.cpp | 55 + .../imagelib/effects/kpEffectEmbossCommand.h | 53 + .../effects/kpEffectFlattenCommand.cpp | 61 + .../imagelib/effects/kpEffectFlattenCommand.h | 59 + .../effects/kpEffectGrayscaleCommand.cpp | 59 + .../effects/kpEffectGrayscaleCommand.h | 57 + .../imagelib/effects/kpEffectHSVCommand.cpp | 50 + .../imagelib/effects/kpEffectHSVCommand.h | 51 + .../effects/kpEffectInvertCommand.cpp | 66 + .../imagelib/effects/kpEffectInvertCommand.h | 63 + .../effects/kpEffectReduceColorsCommand.cpp | 80 + .../effects/kpEffectReduceColorsCommand.h | 59 + .../effects/kpEffectToneEnhanceCommand.cpp | 54 + .../effects/kpEffectToneEnhanceCommand.h | 53 + .../imagelib/kpDocumentMetaInfoCommand.cpp | 86 + commands/imagelib/kpDocumentMetaInfoCommand.h | 58 + .../transforms/kpTransformFlipCommand.cpp | 135 + .../transforms/kpTransformFlipCommand.h | 60 + .../kpTransformResizeScaleCommand.cpp | 486 +++ .../kpTransformResizeScaleCommand.h | 100 + .../transforms/kpTransformRotateCommand.cpp | 220 ++ .../transforms/kpTransformRotateCommand.h | 68 + .../transforms/kpTransformSkewCommand.cpp | 195 + .../transforms/kpTransformSkewCommand.h | 65 + commands/kpCommand.cpp | 83 + commands/kpCommand.h | 90 + commands/kpCommandHistory.cpp | 131 + commands/kpCommandHistory.h | 105 + commands/kpCommandHistoryBase.cpp | 714 ++++ commands/kpCommandHistoryBase.h | 146 + commands/kpCommandSize.cpp | 152 + commands/kpCommandSize.h | 87 + commands/kpMacroCommand.cpp | 133 + commands/kpMacroCommand.h | 66 + commands/kpNamedCommand.cpp | 45 + commands/kpNamedCommand.h | 50 + commands/tools/flow/kpToolFlowCommand.cpp | 140 + commands/tools/flow/kpToolFlowCommand.h | 64 + commands/tools/kpToolColorPickerCommand.cpp | 81 + commands/tools/kpToolColorPickerCommand.h | 59 + commands/tools/kpToolFloodFillCommand.cpp | 169 + commands/tools/kpToolFloodFillCommand.h | 68 + .../polygonal/kpToolPolygonalCommand.cpp | 123 + .../tools/polygonal/kpToolPolygonalCommand.h | 68 + .../rectangular/kpToolRectangularCommand.cpp | 126 + .../rectangular/kpToolRectangularCommand.h | 62 + .../kpAbstractSelectionContentCommand.cpp | 69 + .../kpAbstractSelectionContentCommand.h | 71 + ...pToolImageSelectionTransparencyCommand.cpp | 96 + .../kpToolImageSelectionTransparencyCommand.h | 56 + .../kpToolSelectionCreateCommand.cpp | 161 + .../selection/kpToolSelectionCreateCommand.h | 62 + .../kpToolSelectionDestroyCommand.cpp | 176 + .../selection/kpToolSelectionDestroyCommand.h | 61 + .../selection/kpToolSelectionMoveCommand.cpp | 219 ++ .../selection/kpToolSelectionMoveCommand.h | 74 + ...kpToolSelectionPullFromDocumentCommand.cpp | 140 + .../kpToolSelectionPullFromDocumentCommand.h | 59 + .../kpToolSelectionResizeScaleCommand.cpp | 257 ++ .../kpToolSelectionResizeScaleCommand.h | 104 + .../text/kpToolTextBackspaceCommand.cpp | 152 + .../text/kpToolTextBackspaceCommand.h | 64 + .../text/kpToolTextChangeStyleCommand.cpp | 96 + .../text/kpToolTextChangeStyleCommand.h | 55 + .../text/kpToolTextDeleteCommand.cpp | 140 + .../selection/text/kpToolTextDeleteCommand.h | 64 + .../selection/text/kpToolTextEnterCommand.cpp | 126 + .../selection/text/kpToolTextEnterCommand.h | 63 + .../text/kpToolTextGiveContentCommand.cpp | 151 + .../text/kpToolTextGiveContentCommand.h | 59 + .../text/kpToolTextInsertCommand.cpp | 110 + .../selection/text/kpToolTextInsertCommand.h | 56 + cursors/kpCursorLightCross.cpp | 131 + cursors/kpCursorLightCross.h | 39 + cursors/kpCursorProvider.cpp | 48 + cursors/kpCursorProvider.h | 43 + dialogs/imagelib/effects/kpEffectsDialog.cpp | 372 ++ dialogs/imagelib/effects/kpEffectsDialog.h | 91 + dialogs/imagelib/kpDocumentMetaInfoDialog.cpp | 753 ++++ dialogs/imagelib/kpDocumentMetaInfoDialog.h | 114 + .../transforms/kpTransformPreviewDialog.cpp | 467 +++ .../transforms/kpTransformPreviewDialog.h | 145 + .../kpTransformResizeScaleDialog.cpp | 840 ++++ .../transforms/kpTransformResizeScaleDialog.h | 122 + .../transforms/kpTransformRotateDialog.cpp | 336 ++ .../transforms/kpTransformRotateDialog.h | 92 + .../transforms/kpTransformSkewDialog.cpp | 297 ++ .../transforms/kpTransformSkewDialog.h | 83 + dialogs/kpColorSimilarityDialog.cpp | 141 + dialogs/kpColorSimilarityDialog.h | 68 + .../kpDocumentSaveOptionsPreviewDialog.cpp | 232 ++ dialogs/kpDocumentSaveOptionsPreviewDialog.h | 82 + doc/CMakeLists.txt | 2 + doc/KolourPaint.png | Bin 0 -> 20907 bytes doc/brush_shapes.png | Bin 0 -> 241 bytes doc/color_box.png | Bin 0 -> 1672 bytes doc/eraser_shapes.png | Bin 0 -> 185 bytes doc/fcc_std_text.png | Bin 0 -> 5378 bytes doc/fcc_trans_text.png | Bin 0 -> 6386 bytes doc/fill_color_similarity.png | Bin 0 -> 15250 bytes doc/fill_style.png | Bin 0 -> 179 bytes doc/image_balance.png | Bin 0 -> 27700 bytes doc/image_emboss.png | Bin 0 -> 41528 bytes doc/image_flatten.png | Bin 0 -> 19123 bytes doc/image_invert.png | Bin 0 -> 21804 bytes doc/image_reduce_colors.png | Bin 0 -> 21606 bytes doc/image_resize_scale.png | Bin 0 -> 27797 bytes doc/image_rotate.png | Bin 0 -> 22127 bytes doc/image_skew.png | Bin 0 -> 16772 bytes doc/image_soften_sharpen.png | Bin 0 -> 52335 bytes doc/index.docbook | 1505 +++++++ doc/line_width.png | Bin 0 -> 168 bytes doc/lines_30_45_deg.png | Bin 0 -> 589 bytes doc/lines_30_deg.png | Bin 0 -> 462 bytes doc/lines_45_deg.png | Bin 0 -> 382 bytes doc/rotate_image_30.png | Bin 0 -> 1094 bytes doc/rotate_selection_30.png | Bin 0 -> 1484 bytes doc/screenshot_acquiring.png | Bin 0 -> 8132 bytes doc/selections_opaque_transparent.png | Bin 0 -> 984 bytes doc/spraycan_patterns.png | Bin 0 -> 404 bytes doc/text_zoom_grid.png | Bin 0 -> 484 bytes doc/tool_brush.png | Bin 0 -> 528 bytes doc/tool_color_picker.png | Bin 0 -> 504 bytes doc/tool_color_washer.png | Bin 0 -> 480 bytes doc/tool_curve.png | Bin 0 -> 444 bytes doc/tool_ellipse.png | Bin 0 -> 489 bytes doc/tool_elliptical_selection.png | Bin 0 -> 556 bytes doc/tool_eraser.png | Bin 0 -> 398 bytes doc/tool_flood_fill.png | Bin 0 -> 534 bytes doc/tool_free_form_selection.png | Bin 0 -> 580 bytes doc/tool_line.png | Bin 0 -> 342 bytes doc/tool_pen.png | Bin 0 -> 612 bytes doc/tool_polygon.png | Bin 0 -> 385 bytes doc/tool_polyline.png | Bin 0 -> 384 bytes doc/tool_polystar.png | Bin 0 -> 371 bytes doc/tool_rect_selection.png | Bin 0 -> 408 bytes doc/tool_rectangle.png | Bin 0 -> 310 bytes doc/tool_rectangles.png | Bin 0 -> 390 bytes doc/tool_rounded_rectangle.png | Bin 0 -> 493 bytes doc/tool_selections.png | Bin 0 -> 631 bytes doc/tool_spraycan.png | Bin 0 -> 379 bytes doc/tool_text.png | Bin 0 -> 331 bytes doc/view_thumbnails.png | Bin 0 -> 44736 bytes document/kpDocument.cpp | 457 +++ document/kpDocument.h | 360 ++ document/kpDocumentPrivate.h | 47 + document/kpDocumentSaveOptions.cpp | 532 +++ document/kpDocumentSaveOptions.h | 150 + document/kpDocument_Open.cpp | 254 ++ document/kpDocument_Save.cpp | 472 +++ document/kpDocument_Selection.cpp | 350 ++ .../commands/kpCommandEnvironment.cpp | 102 + environments/commands/kpCommandEnvironment.h | 79 + .../kpTransformDialogEnvironment.cpp | 42 + .../transforms/kpTransformDialogEnvironment.h | 52 + .../document/kpDocumentEnvironment.cpp | 211 + environments/document/kpDocumentEnvironment.h | 71 + environments/kpEnvironmentBase.cpp | 120 + environments/kpEnvironmentBase.h | 99 + environments/tools/kpToolEnvironment.cpp | 217 ++ environments/tools/kpToolEnvironment.h | 163 + .../selection/kpToolSelectionEnvironment.cpp | 96 + .../selection/kpToolSelectionEnvironment.h | 72 + gen_cmake_include_dirs | 7 + gen_cmake_srcs | 7 + generic/kpSetOverrideCursorSaver.cpp | 43 + generic/kpSetOverrideCursorSaver.h | 107 + generic/kpWidgetMapper.cpp | 80 + generic/kpWidgetMapper.h | 48 + generic/widgets/kpResizeSignallingLabel.cpp | 65 + generic/widgets/kpResizeSignallingLabel.h | 56 + generic/widgets/kpSubWindow.cpp | 35 + generic/widgets/kpSubWindow.h | 57 + imagelib/effects/blitz.cpp | 715 ++++ imagelib/effects/blitz.h | 42 + imagelib/effects/kpEffectBalance.cpp | 187 + imagelib/effects/kpEffectBalance.h | 53 + imagelib/effects/kpEffectBlurSharpen.cpp | 184 + imagelib/effects/kpEffectBlurSharpen.h | 57 + imagelib/effects/kpEffectEmboss.cpp | 81 + imagelib/effects/kpEffectEmboss.h | 52 + imagelib/effects/kpEffectFlatten.cpp | 54 + imagelib/effects/kpEffectFlatten.h | 47 + imagelib/effects/kpEffectGrayscale.cpp | 75 + imagelib/effects/kpEffectGrayscale.h | 47 + imagelib/effects/kpEffectHSV.cpp | 189 + imagelib/effects/kpEffectHSV.h | 44 + imagelib/effects/kpEffectInvert.cpp | 89 + imagelib/effects/kpEffectInvert.h | 62 + imagelib/effects/kpEffectReduceColors.cpp | 240 ++ imagelib/effects/kpEffectReduceColors.h | 51 + imagelib/effects/kpEffectToneEnhance.cpp | 308 ++ imagelib/effects/kpEffectToneEnhance.h | 61 + imagelib/kpColor.cpp | 317 ++ imagelib/kpColor.h | 156 + imagelib/kpColor_Constants.cpp | 117 + imagelib/kpDocumentMetaInfo.cpp | 277 ++ imagelib/kpDocumentMetaInfo.h | 107 + imagelib/kpFloodFill.cpp | 423 ++ imagelib/kpFloodFill.h | 128 + imagelib/kpImage.h | 38 + imagelib/kpPainter.cpp | 512 +++ imagelib/kpPainter.h | 137 + imagelib/transforms/kpTransformAutoCrop.cpp | 756 ++++ imagelib/transforms/kpTransformAutoCrop.h | 85 + imagelib/transforms/kpTransformCrop.cpp | 77 + imagelib/transforms/kpTransformCrop.h | 83 + imagelib/transforms/kpTransformCropPrivate.h | 49 + .../kpTransformCrop_ImageSelection.cpp | 257 ++ .../kpTransformCrop_TextSelection.cpp | 76 + kolourpaint.cpp | 138 + kolourpaint.qrc | 19 + kolourpaintui.rc | 228 ++ kpDefs.h | 138 + kpLogCategories.cpp | 41 + kpLogCategories.h | 46 + kpThumbnail.cpp | 183 + kpThumbnail.h | 76 + kpViewScrollableContainer.cpp | 1209 ++++++ kpViewScrollableContainer.h | 217 ++ .../image/kpAbstractImageSelection.cpp | 589 +++ .../image/kpAbstractImageSelection.h | 266 ++ .../image/kpEllipticalImageSelection.cpp | 185 + .../image/kpEllipticalImageSelection.h | 118 + .../image/kpFreeFormImageSelection.cpp | 394 ++ .../image/kpFreeFormImageSelection.h | 159 + .../image/kpImageSelectionTransparency.cpp | 205 + .../image/kpImageSelectionTransparency.h | 82 + .../image/kpRectangularImageSelection.cpp | 149 + .../image/kpRectangularImageSelection.h | 119 + layers/selections/kpAbstractSelection.cpp | 315 ++ layers/selections/kpAbstractSelection.h | 278 ++ layers/selections/kpSelectionDrag.cpp | 166 + layers/selections/kpSelectionDrag.h | 52 + layers/selections/kpSelectionFactory.cpp | 93 + layers/selections/kpSelectionFactory.h | 48 + layers/selections/text/kpPreeditText.cpp | 142 + layers/selections/text/kpPreeditText.h | 65 + layers/selections/text/kpTextSelection.cpp | 346 ++ layers/selections/text/kpTextSelection.h | 294 ++ .../selections/text/kpTextSelectionPrivate.h | 47 + .../text/kpTextSelection_Cursor.cpp | 129 + .../selections/text/kpTextSelection_Paint.cpp | 274 ++ layers/selections/text/kpTextStyle.cpp | 267 ++ layers/selections/text/kpTextStyle.h | 107 + layers/tempImage/kpTempImage.cpp | 218 ++ layers/tempImage/kpTempImage.h | 110 + lgpl/CMakeLists.txt | 36 + lgpl/generic/kpColorCollection.cpp | 518 +++ lgpl/generic/kpColorCollection.h | 254 ++ lgpl/generic/kpUrlFormatter.cpp | 65 + lgpl/generic/kpUrlFormatter.h | 57 + lgpl/generic/widgets/kpColorCellsBase.cpp | 563 +++ lgpl/generic/widgets/kpColorCellsBase.h | 188 + logo.png | Bin 0 -> 7198 bytes mainWindow/kpMainWindow.cpp | 923 +++++ mainWindow/kpMainWindow.h | 692 ++++ mainWindow/kpMainWindowPrivate.h | 445 +++ mainWindow/kpMainWindow_Colors.cpp | 495 +++ mainWindow/kpMainWindow_Edit.cpp | 923 +++++ mainWindow/kpMainWindow_File.cpp | 1510 ++++++++ mainWindow/kpMainWindow_Image.cpp | 624 +++ mainWindow/kpMainWindow_Settings.cpp | 161 + mainWindow/kpMainWindow_StatusBar.cpp | 440 +++ mainWindow/kpMainWindow_Text.cpp | 435 +++ mainWindow/kpMainWindow_Tools.cpp | 824 ++++ mainWindow/kpMainWindow_View.cpp | 163 + mainWindow/kpMainWindow_View_Thumbnail.cpp | 479 +++ mainWindow/kpMainWindow_View_Zoom.cpp | 692 ++++ org.kde.kolourpaint.appdata.xml | 429 ++ org.kde.kolourpaint.desktop | 206 + patches/checkerboard-faster-render.diff | 147 + patches/linear-sharpen-effect.diff | 150 + pics/CMakeLists.txt | 2 + pics/action/16-actions-tool_brush.png | Bin 0 -> 675 bytes pics/action/16-actions-tool_color_eraser.png | Bin 0 -> 1007 bytes pics/action/16-actions-tool_color_picker.png | Bin 0 -> 634 bytes pics/action/16-actions-tool_curve.png | Bin 0 -> 478 bytes pics/action/16-actions-tool_ellipse.png | Bin 0 -> 807 bytes .../16-actions-tool_elliptical_selection.png | Bin 0 -> 886 bytes pics/action/16-actions-tool_eraser.png | Bin 0 -> 798 bytes pics/action/16-actions-tool_flood_fill.png | Bin 0 -> 636 bytes .../16-actions-tool_free_form_selection.png | Bin 0 -> 836 bytes pics/action/16-actions-tool_line.png | Bin 0 -> 390 bytes pics/action/16-actions-tool_pen.png | Bin 0 -> 572 bytes pics/action/16-actions-tool_polygon.png | Bin 0 -> 879 bytes pics/action/16-actions-tool_polyline.png | Bin 0 -> 542 bytes .../action/16-actions-tool_rect_selection.png | Bin 0 -> 910 bytes pics/action/16-actions-tool_rectangle.png | Bin 0 -> 548 bytes .../16-actions-tool_rounded_rectangle.png | Bin 0 -> 723 bytes pics/action/16-actions-tool_spraycan.png | Bin 0 -> 776 bytes pics/action/16-actions-tool_text.png | Bin 0 -> 434 bytes pics/action/22-actions-tool_brush.png | Bin 0 -> 965 bytes pics/action/22-actions-tool_color_eraser.png | Bin 0 -> 1493 bytes pics/action/22-actions-tool_color_picker.png | Bin 0 -> 941 bytes pics/action/22-actions-tool_curve.png | Bin 0 -> 639 bytes pics/action/22-actions-tool_ellipse.png | Bin 0 -> 1155 bytes .../22-actions-tool_elliptical_selection.png | Bin 0 -> 1316 bytes pics/action/22-actions-tool_eraser.png | Bin 0 -> 1218 bytes pics/action/22-actions-tool_flood_fill.png | Bin 0 -> 836 bytes .../22-actions-tool_free_form_selection.png | Bin 0 -> 1241 bytes pics/action/22-actions-tool_line.png | Bin 0 -> 395 bytes pics/action/22-actions-tool_pen.png | Bin 0 -> 827 bytes pics/action/22-actions-tool_polygon.png | Bin 0 -> 1303 bytes pics/action/22-actions-tool_polyline.png | Bin 0 -> 772 bytes .../action/22-actions-tool_rect_selection.png | Bin 0 -> 1263 bytes pics/action/22-actions-tool_rectangle.png | Bin 0 -> 795 bytes .../22-actions-tool_rounded_rectangle.png | Bin 0 -> 1096 bytes pics/action/22-actions-tool_spraycan.png | Bin 0 -> 1149 bytes pics/action/22-actions-tool_text.png | Bin 0 -> 728 bytes pics/action/32-actions-tool_brush.png | Bin 0 -> 1557 bytes pics/action/32-actions-tool_color_eraser.png | Bin 0 -> 2538 bytes pics/action/32-actions-tool_color_picker.png | Bin 0 -> 1422 bytes pics/action/32-actions-tool_curve.png | Bin 0 -> 907 bytes pics/action/32-actions-tool_ellipse.png | Bin 0 -> 1726 bytes .../32-actions-tool_elliptical_selection.png | Bin 0 -> 1855 bytes pics/action/32-actions-tool_eraser.png | Bin 0 -> 1939 bytes pics/action/32-actions-tool_flood_fill.png | Bin 0 -> 1229 bytes .../32-actions-tool_free_form_selection.png | Bin 0 -> 2085 bytes pics/action/32-actions-tool_line.png | Bin 0 -> 602 bytes pics/action/32-actions-tool_pen.png | Bin 0 -> 1192 bytes pics/action/32-actions-tool_polygon.png | Bin 0 -> 1930 bytes pics/action/32-actions-tool_polyline.png | Bin 0 -> 1061 bytes .../action/32-actions-tool_rect_selection.png | Bin 0 -> 2055 bytes pics/action/32-actions-tool_rectangle.png | Bin 0 -> 942 bytes .../32-actions-tool_rounded_rectangle.png | Bin 0 -> 1514 bytes pics/action/32-actions-tool_spraycan.png | Bin 0 -> 1912 bytes pics/action/32-actions-tool_text.png | Bin 0 -> 1047 bytes pics/action/48-actions-tool_brush.png | Bin 0 -> 2662 bytes pics/action/48-actions-tool_color_eraser.png | Bin 0 -> 4381 bytes pics/action/48-actions-tool_color_picker.png | Bin 0 -> 2422 bytes pics/action/48-actions-tool_curve.png | Bin 0 -> 1310 bytes pics/action/48-actions-tool_ellipse.png | Bin 0 -> 2590 bytes .../48-actions-tool_elliptical_selection.png | Bin 0 -> 2964 bytes pics/action/48-actions-tool_eraser.png | Bin 0 -> 3381 bytes pics/action/48-actions-tool_flood_fill.png | Bin 0 -> 1527 bytes .../48-actions-tool_free_form_selection.png | Bin 0 -> 3631 bytes pics/action/48-actions-tool_line.png | Bin 0 -> 802 bytes pics/action/48-actions-tool_pen.png | Bin 0 -> 2005 bytes pics/action/48-actions-tool_polygon.png | Bin 0 -> 2967 bytes pics/action/48-actions-tool_polyline.png | Bin 0 -> 1394 bytes .../action/48-actions-tool_rect_selection.png | Bin 0 -> 3509 bytes pics/action/48-actions-tool_rectangle.png | Bin 0 -> 1378 bytes .../48-actions-tool_rounded_rectangle.png | Bin 0 -> 2237 bytes pics/action/48-actions-tool_spraycan.png | Bin 0 -> 3253 bytes pics/action/48-actions-tool_text.png | Bin 0 -> 1263 bytes pics/action/CMakeLists.txt | 94 + pics/action/sc-actions-tool_brush.svgz | Bin 0 -> 8136 bytes pics/action/sc-actions-tool_color_eraser.svgz | Bin 0 -> 7818 bytes pics/action/sc-actions-tool_color_picker.svgz | Bin 0 -> 10939 bytes pics/action/sc-actions-tool_curve.svgz | Bin 0 -> 2380 bytes pics/action/sc-actions-tool_ellipse.svgz | Bin 0 -> 1994 bytes .../sc-actions-tool_elliptical_selection.svgz | Bin 0 -> 3597 bytes pics/action/sc-actions-tool_eraser.svgz | Bin 0 -> 6959 bytes pics/action/sc-actions-tool_flood_fill.svgz | Bin 0 -> 2903 bytes .../sc-actions-tool_free_form_selection.svgz | Bin 0 -> 4661 bytes pics/action/sc-actions-tool_line.svgz | Bin 0 -> 1333 bytes pics/action/sc-actions-tool_pen.svgz | Bin 0 -> 4924 bytes pics/action/sc-actions-tool_polygon.svgz | Bin 0 -> 2276 bytes pics/action/sc-actions-tool_polyline.svgz | Bin 0 -> 2098 bytes .../sc-actions-tool_rect_selection.svgz | Bin 0 -> 3819 bytes pics/action/sc-actions-tool_rectangle.svgz | Bin 0 -> 1960 bytes .../sc-actions-tool_rounded_rectangle.svgz | Bin 0 -> 2220 bytes pics/action/sc-actions-tool_spraycan.svgz | Bin 0 -> 8587 bytes pics/action/sc-actions-tool_text.svgz | Bin 0 -> 2666 bytes pics/app/128-apps-kolourpaint.png | Bin 0 -> 27246 bytes pics/app/16-apps-kolourpaint.png | Bin 0 -> 812 bytes pics/app/22-apps-kolourpaint.png | Bin 0 -> 1259 bytes pics/app/32-apps-kolourpaint.png | Bin 0 -> 2139 bytes pics/app/48-apps-kolourpaint.png | Bin 0 -> 3975 bytes pics/app/CMakeLists.txt | 10 + pics/app/sc-apps-kolourpaint.svgz | Bin 0 -> 12874 bytes pics/custom/color_transparent_26x26.png | Bin 0 -> 972 bytes pics/custom/colorbutton_swap_16x16.png | Bin 0 -> 119 bytes pics/custom/image_rotate_anticlockwise.png | Bin 0 -> 203 bytes pics/custom/image_rotate_clockwise.png | Bin 0 -> 196 bytes pics/custom/image_skew_horizontal.png | Bin 0 -> 179 bytes pics/custom/image_skew_vertical.png | Bin 0 -> 237 bytes pics/custom/option_opaque.png | Bin 0 -> 717 bytes pics/custom/option_transparent.png | Bin 0 -> 868 bytes pics/custom/resize.png | Bin 0 -> 4009 bytes pics/custom/scale.png | Bin 0 -> 1724 bytes pics/custom/smooth_scale.png | Bin 0 -> 5097 bytes pics/custom/tool_spraycan_17x17.png | Bin 0 -> 130 bytes pics/custom/tool_spraycan_29x29.png | Bin 0 -> 210 bytes pics/custom/tool_spraycan_9x9.png | Bin 0 -> 92 bytes pixmapfx/kpPixmapFX.h | 249 ++ pixmapfx/kpPixmapFX_DrawShapes.cpp | 285 ++ pixmapfx/kpPixmapFX_GetSetPixmapParts.cpp | 142 + pixmapfx/kpPixmapFX_Transforms.cpp | 669 ++++ po/ar/kolourpaint.po | 2968 ++++++++++++++ po/be/kolourpaint.po | 2611 +++++++++++++ po/bg/kolourpaint.po | 2732 +++++++++++++ po/bs/kolourpaint.po | 2799 +++++++++++++ po/ca/docs/kolourpaint/KolourPaint.png | Bin 0 -> 35090 bytes po/ca/docs/kolourpaint/image_balance.png | Bin 0 -> 46871 bytes po/ca/docs/kolourpaint/image_emboss.png | Bin 0 -> 69751 bytes po/ca/docs/kolourpaint/image_flatten.png | Bin 0 -> 31500 bytes po/ca/docs/kolourpaint/image_invert.png | Bin 0 -> 36608 bytes .../docs/kolourpaint/image_reduce_colors.png | Bin 0 -> 36445 bytes po/ca/docs/kolourpaint/image_resize_scale.png | Bin 0 -> 46635 bytes po/ca/docs/kolourpaint/image_rotate.png | Bin 0 -> 41350 bytes po/ca/docs/kolourpaint/image_skew.png | Bin 0 -> 32740 bytes .../docs/kolourpaint/image_soften_sharpen.png | Bin 0 -> 70505 bytes po/ca/docs/kolourpaint/index.docbook | 1770 +++++++++ po/ca/kolourpaint.po | 2742 +++++++++++++ po/ca@valencia/kolourpaint.po | 2743 +++++++++++++ po/cs/kolourpaint.po | 2664 +++++++++++++ po/da/kolourpaint.po | 3012 ++++++++++++++ po/de/docs/kolourpaint/KolourPaint.png | Bin 0 -> 60093 bytes po/de/docs/kolourpaint/image_balance.png | Bin 0 -> 54598 bytes po/de/docs/kolourpaint/image_emboss.png | Bin 0 -> 91496 bytes po/de/docs/kolourpaint/image_flatten.png | Bin 0 -> 36474 bytes po/de/docs/kolourpaint/image_flip.png | Bin 0 -> 15311 bytes po/de/docs/kolourpaint/image_invert.png | Bin 0 -> 43611 bytes .../docs/kolourpaint/image_reduce_colors.png | Bin 0 -> 35955 bytes po/de/docs/kolourpaint/image_resize_scale.png | Bin 0 -> 42993 bytes po/de/docs/kolourpaint/image_rotate.png | Bin 0 -> 44461 bytes po/de/docs/kolourpaint/image_skew.png | Bin 0 -> 32838 bytes .../docs/kolourpaint/image_soften_sharpen.png | Bin 0 -> 105466 bytes po/de/docs/kolourpaint/index.docbook | 1786 +++++++++ po/de/kolourpaint.po | 3058 +++++++++++++++ po/el/kolourpaint.po | 3041 +++++++++++++++ po/en_GB/kolourpaint.po | 3154 +++++++++++++++ po/eo/kolourpaint.po | 2726 +++++++++++++ po/es/docs/kolourpaint/brush_shapes.png | Bin 0 -> 242 bytes po/es/docs/kolourpaint/color_box.png | Bin 0 -> 1694 bytes po/es/docs/kolourpaint/eraser_shapes.png | Bin 0 -> 188 bytes po/es/docs/kolourpaint/fcc_std_text.png | Bin 0 -> 5756 bytes po/es/docs/kolourpaint/fcc_trans_text.png | Bin 0 -> 6789 bytes .../kolourpaint/fill_color_similarity.png | Bin 0 -> 13479 bytes po/es/docs/kolourpaint/fill_style.png | Bin 0 -> 181 bytes po/es/docs/kolourpaint/image_balance.png | Bin 0 -> 129314 bytes po/es/docs/kolourpaint/image_emboss.png | Bin 0 -> 384561 bytes po/es/docs/kolourpaint/image_flatten.png | Bin 0 -> 72622 bytes po/es/docs/kolourpaint/image_invert.png | Bin 0 -> 123192 bytes .../docs/kolourpaint/image_reduce_colors.png | Bin 0 -> 35491 bytes po/es/docs/kolourpaint/image_resize_scale.png | Bin 0 -> 42094 bytes po/es/docs/kolourpaint/image_rotate.png | Bin 0 -> 56751 bytes po/es/docs/kolourpaint/image_skew.png | Bin 0 -> 43749 bytes .../docs/kolourpaint/image_soften_sharpen.png | Bin 0 -> 285108 bytes po/es/docs/kolourpaint/index.docbook | 1814 +++++++++ po/es/docs/kolourpaint/line_width.png | Bin 0 -> 168 bytes po/es/docs/kolourpaint/lines_30_45_deg.png | Bin 0 -> 551 bytes po/es/docs/kolourpaint/lines_30_deg.png | Bin 0 -> 430 bytes po/es/docs/kolourpaint/lines_45_deg.png | Bin 0 -> 368 bytes po/es/docs/kolourpaint/rotate_image_30.png | Bin 0 -> 1202 bytes .../docs/kolourpaint/rotate_selection_30.png | Bin 0 -> 1508 bytes .../selections_opaque_transparent.png | Bin 0 -> 969 bytes po/es/docs/kolourpaint/spraycan_patterns.png | Bin 0 -> 406 bytes po/es/docs/kolourpaint/text_zoom_grid.png | Bin 0 -> 216 bytes po/es/docs/kolourpaint/tool_brush.png | Bin 0 -> 675 bytes po/es/docs/kolourpaint/tool_color_picker.png | Bin 0 -> 635 bytes po/es/docs/kolourpaint/tool_color_washer.png | Bin 0 -> 1007 bytes po/es/docs/kolourpaint/tool_curve.png | Bin 0 -> 478 bytes po/es/docs/kolourpaint/tool_ellipse.png | Bin 0 -> 807 bytes .../kolourpaint/tool_elliptical_selection.png | Bin 0 -> 886 bytes po/es/docs/kolourpaint/tool_eraser.png | Bin 0 -> 798 bytes po/es/docs/kolourpaint/tool_flood_fill.png | Bin 0 -> 636 bytes .../kolourpaint/tool_free_form_selection.png | Bin 0 -> 836 bytes po/es/docs/kolourpaint/tool_line.png | Bin 0 -> 390 bytes po/es/docs/kolourpaint/tool_pen.png | Bin 0 -> 572 bytes po/es/docs/kolourpaint/tool_polygon.png | Bin 0 -> 879 bytes po/es/docs/kolourpaint/tool_polyline.png | Bin 0 -> 542 bytes po/es/docs/kolourpaint/tool_polystar.png | Bin 0 -> 1308 bytes .../docs/kolourpaint/tool_rect_selection.png | Bin 0 -> 910 bytes po/es/docs/kolourpaint/tool_rectangle.png | Bin 0 -> 548 bytes po/es/docs/kolourpaint/tool_rectangles.png | Bin 0 -> 790 bytes .../kolourpaint/tool_rounded_rectangle.png | Bin 0 -> 723 bytes po/es/docs/kolourpaint/tool_selections.png | Bin 0 -> 1562 bytes po/es/docs/kolourpaint/tool_spraycan.png | Bin 0 -> 776 bytes po/es/docs/kolourpaint/tool_text.png | Bin 0 -> 385 bytes po/es/docs/kolourpaint/view_thumbnails.png | Bin 0 -> 124412 bytes po/es/kolourpaint.po | 2811 ++++++++++++++ po/et/docs/kolourpaint/index.docbook | 1792 +++++++++ po/et/kolourpaint.po | 3003 ++++++++++++++ po/eu/kolourpaint.po | 3028 +++++++++++++++ po/fa/kolourpaint.po | 2987 ++++++++++++++ po/fi/kolourpaint.po | 2974 ++++++++++++++ po/fr/docs/kolourpaint/index.docbook | 1766 +++++++++ po/fr/kolourpaint.po | 3094 +++++++++++++++ po/ga/kolourpaint.po | 2958 ++++++++++++++ po/gl/docs/kolourpaint/index.docbook | 1749 +++++++++ po/gl/kolourpaint.po | 2825 ++++++++++++++ po/he/kolourpaint.po | 2633 +++++++++++++ po/hi/kolourpaint.po | 2807 ++++++++++++++ po/hr/kolourpaint.po | 2573 ++++++++++++ po/hu/kolourpaint.po | 3031 +++++++++++++++ po/ia/kolourpaint.po | 2803 +++++++++++++ po/id/kolourpaint.po | 2550 ++++++++++++ po/is/kolourpaint.po | 2793 +++++++++++++ po/it/docs/kolourpaint/index.docbook | 1782 +++++++++ po/it/kolourpaint.po | 3451 +++++++++++++++++ po/ja/kolourpaint.po | 2772 +++++++++++++ po/kk/kolourpaint.po | 3004 ++++++++++++++ po/km/kolourpaint.po | 2758 +++++++++++++ po/ko/kolourpaint.po | 2752 +++++++++++++ po/lt/kolourpaint.po | 2811 ++++++++++++++ po/lv/kolourpaint.po | 3083 +++++++++++++++ po/ml/kolourpaint.po | 2551 ++++++++++++ po/mr/kolourpaint.po | 2805 ++++++++++++++ po/nb/kolourpaint.po | 2704 +++++++++++++ po/nds/kolourpaint.po | 3039 +++++++++++++++ po/nl/docs/kolourpaint/KolourPaint.png | Bin 0 -> 66195 bytes po/nl/docs/kolourpaint/index.docbook | 1764 +++++++++ po/nl/kolourpaint.po | 3042 +++++++++++++++ po/nn/kolourpaint.po | 2727 +++++++++++++ po/pa/kolourpaint.po | 2683 +++++++++++++ po/pl/docs/kolourpaint/fullscreen_mode.png | Bin 0 -> 232793 bytes po/pl/docs/kolourpaint/image_balance.png | Bin 0 -> 43446 bytes po/pl/docs/kolourpaint/image_emboss.png | Bin 0 -> 83067 bytes po/pl/docs/kolourpaint/image_flatten.png | Bin 0 -> 32014 bytes po/pl/docs/kolourpaint/image_flip.png | Bin 0 -> 15170 bytes po/pl/docs/kolourpaint/image_invert.png | Bin 0 -> 38275 bytes .../docs/kolourpaint/image_reduce_colors.png | Bin 0 -> 32547 bytes po/pl/docs/kolourpaint/image_resize_scale.png | Bin 0 -> 39441 bytes po/pl/docs/kolourpaint/image_rotate.png | Bin 0 -> 38906 bytes po/pl/docs/kolourpaint/image_skew.png | Bin 0 -> 30313 bytes .../docs/kolourpaint/image_soften_sharpen.png | Bin 0 -> 78834 bytes po/pl/docs/kolourpaint/index.docbook | 1754 +++++++++ po/pl/kolourpaint.po | 3024 +++++++++++++++ po/pt/docs/kolourpaint/index.docbook | 1776 +++++++++ po/pt/kolourpaint.po | 2749 +++++++++++++ po/pt_BR/docs/kolourpaint/KolourPaint.png | Bin 0 -> 46057 bytes po/pt_BR/docs/kolourpaint/brush_shapes.png | Bin 0 -> 390 bytes po/pt_BR/docs/kolourpaint/color_box.png | Bin 0 -> 1829 bytes po/pt_BR/docs/kolourpaint/eraser_shapes.png | Bin 0 -> 320 bytes po/pt_BR/docs/kolourpaint/fcc_std_text.png | Bin 0 -> 5718 bytes po/pt_BR/docs/kolourpaint/fcc_trans_text.png | Bin 0 -> 6890 bytes .../kolourpaint/fill_color_similarity.png | Bin 0 -> 13616 bytes po/pt_BR/docs/kolourpaint/fill_style.png | Bin 0 -> 317 bytes po/pt_BR/docs/kolourpaint/image_balance.png | Bin 0 -> 46334 bytes po/pt_BR/docs/kolourpaint/image_emboss.png | Bin 0 -> 78486 bytes po/pt_BR/docs/kolourpaint/image_flatten.png | Bin 0 -> 35130 bytes po/pt_BR/docs/kolourpaint/image_invert.png | Bin 0 -> 41700 bytes .../docs/kolourpaint/image_reduce_colors.png | Bin 0 -> 34133 bytes .../docs/kolourpaint/image_resize_scale.png | Bin 0 -> 40626 bytes po/pt_BR/docs/kolourpaint/image_rotate.png | Bin 0 -> 38513 bytes po/pt_BR/docs/kolourpaint/image_skew.png | Bin 0 -> 31688 bytes .../docs/kolourpaint/image_soften_sharpen.png | Bin 0 -> 82350 bytes po/pt_BR/docs/kolourpaint/index.docbook | 1819 +++++++++ po/pt_BR/docs/kolourpaint/line_width.png | Bin 0 -> 306 bytes po/pt_BR/docs/kolourpaint/lines_30_45_deg.png | Bin 0 -> 706 bytes po/pt_BR/docs/kolourpaint/lines_30_deg.png | Bin 0 -> 562 bytes po/pt_BR/docs/kolourpaint/lines_45_deg.png | Bin 0 -> 520 bytes po/pt_BR/docs/kolourpaint/rotate_image_30.png | Bin 0 -> 1331 bytes .../docs/kolourpaint/rotate_selection_30.png | Bin 0 -> 1640 bytes .../docs/kolourpaint/screenshot_acquiring.png | Bin 0 -> 20614 bytes .../selections_opaque_transparent.png | Bin 0 -> 1129 bytes .../docs/kolourpaint/spraycan_patterns.png | Bin 0 -> 524 bytes po/pt_BR/docs/kolourpaint/text_zoom_grid.png | Bin 0 -> 382 bytes po/pt_BR/docs/kolourpaint/tool_brush.png | Bin 0 -> 824 bytes .../docs/kolourpaint/tool_color_picker.png | Bin 0 -> 750 bytes .../docs/kolourpaint/tool_color_washer.png | Bin 0 -> 1184 bytes po/pt_BR/docs/kolourpaint/tool_curve.png | Bin 0 -> 675 bytes po/pt_BR/docs/kolourpaint/tool_ellipse.png | Bin 0 -> 990 bytes .../kolourpaint/tool_elliptical_selection.png | Bin 0 -> 995 bytes po/pt_BR/docs/kolourpaint/tool_eraser.png | Bin 0 -> 961 bytes po/pt_BR/docs/kolourpaint/tool_flood_fill.png | Bin 0 -> 816 bytes .../kolourpaint/tool_free_form_selection.png | Bin 0 -> 982 bytes po/pt_BR/docs/kolourpaint/tool_line.png | Bin 0 -> 506 bytes po/pt_BR/docs/kolourpaint/tool_pen.png | Bin 0 -> 731 bytes po/pt_BR/docs/kolourpaint/tool_polygon.png | Bin 0 -> 1095 bytes po/pt_BR/docs/kolourpaint/tool_polyline.png | Bin 0 -> 813 bytes po/pt_BR/docs/kolourpaint/tool_polystar.png | Bin 0 -> 1387 bytes .../docs/kolourpaint/tool_rect_selection.png | Bin 0 -> 1107 bytes po/pt_BR/docs/kolourpaint/tool_rectangle.png | Bin 0 -> 735 bytes po/pt_BR/docs/kolourpaint/tool_rectangles.png | Bin 0 -> 866 bytes .../kolourpaint/tool_rounded_rectangle.png | Bin 0 -> 860 bytes po/pt_BR/docs/kolourpaint/tool_selections.png | Bin 0 -> 1678 bytes po/pt_BR/docs/kolourpaint/tool_spraycan.png | Bin 0 -> 1034 bytes po/pt_BR/docs/kolourpaint/tool_text.png | Bin 0 -> 608 bytes po/pt_BR/docs/kolourpaint/view_thumbnails.png | Bin 0 -> 116411 bytes po/pt_BR/kolourpaint.po | 2808 ++++++++++++++ po/ro/kolourpaint.po | 2772 +++++++++++++ po/ru/docs/kolourpaint/KolourPaint.png | Bin 0 -> 26471 bytes po/ru/docs/kolourpaint/image_balance.png | Bin 0 -> 32518 bytes po/ru/docs/kolourpaint/image_emboss.png | Bin 0 -> 40836 bytes po/ru/docs/kolourpaint/image_flatten.png | Bin 0 -> 27328 bytes po/ru/docs/kolourpaint/image_invert.png | Bin 0 -> 28625 bytes .../docs/kolourpaint/image_reduce_colors.png | Bin 0 -> 28756 bytes po/ru/docs/kolourpaint/image_resize_scale.png | Bin 0 -> 21914 bytes po/ru/docs/kolourpaint/image_rotate.png | Bin 0 -> 31111 bytes po/ru/docs/kolourpaint/image_skew.png | Bin 0 -> 25788 bytes .../docs/kolourpaint/image_soften_sharpen.png | Bin 0 -> 43377 bytes po/ru/docs/kolourpaint/index.docbook | 1836 +++++++++ .../docs/kolourpaint/screenshot_acquiring.png | Bin 0 -> 21007 bytes po/ru/docs/kolourpaint/view_thumbnails.png | Bin 0 -> 37540 bytes po/ru/kolourpaint.po | 3141 +++++++++++++++ po/se/kolourpaint.po | 2580 ++++++++++++ po/sk/kolourpaint.po | 2778 +++++++++++++ po/sl/kolourpaint.po | 2788 +++++++++++++ po/sq/kolourpaint.po | 2591 +++++++++++++ po/sv/docs/kolourpaint/image_balance.png | Bin 0 -> 10854 bytes po/sv/docs/kolourpaint/image_emboss.png | Bin 0 -> 7427 bytes po/sv/docs/kolourpaint/image_flatten.png | Bin 0 -> 8105 bytes po/sv/docs/kolourpaint/image_flip.png | Bin 0 -> 4316 bytes po/sv/docs/kolourpaint/image_invert.png | Bin 0 -> 8307 bytes .../docs/kolourpaint/image_reduce_colors.png | Bin 0 -> 9654 bytes po/sv/docs/kolourpaint/image_resize_scale.png | Bin 0 -> 12942 bytes po/sv/docs/kolourpaint/image_rotate.png | Bin 0 -> 10897 bytes po/sv/docs/kolourpaint/image_skew.png | Bin 0 -> 9899 bytes .../docs/kolourpaint/image_soften_sharpen.png | Bin 0 -> 8195 bytes po/sv/docs/kolourpaint/index.docbook | 1784 +++++++++ po/sv/docs/kolourpaint/view_thumbnails.png | Bin 0 -> 15735 bytes po/sv/kolourpaint.po | 3000 ++++++++++++++ po/ta/kolourpaint.po | 3091 +++++++++++++++ po/tg/kolourpaint.po | 3062 +++++++++++++++ po/tr/kolourpaint.po | 2735 +++++++++++++ po/ug/kolourpaint.po | 2558 ++++++++++++ po/uk/docs/kolourpaint/KolourPaint.png | Bin 0 -> 15975 bytes po/uk/docs/kolourpaint/image_balance.png | Bin 0 -> 20306 bytes po/uk/docs/kolourpaint/image_emboss.png | Bin 0 -> 54281 bytes po/uk/docs/kolourpaint/image_flatten.png | Bin 0 -> 25417 bytes po/uk/docs/kolourpaint/image_invert.png | Bin 0 -> 21893 bytes .../docs/kolourpaint/image_reduce_colors.png | Bin 0 -> 10979 bytes po/uk/docs/kolourpaint/image_resize_scale.png | Bin 0 -> 12718 bytes po/uk/docs/kolourpaint/image_rotate.png | Bin 0 -> 13501 bytes po/uk/docs/kolourpaint/image_skew.png | Bin 0 -> 11651 bytes .../docs/kolourpaint/image_soften_sharpen.png | Bin 0 -> 47488 bytes po/uk/docs/kolourpaint/index.docbook | 1781 +++++++++ .../docs/kolourpaint/screenshot_acquiring.png | Bin 0 -> 4969 bytes po/uk/kolourpaint.po | 3031 +++++++++++++++ po/zh_CN/kolourpaint.po | 2658 +++++++++++++ po/zh_TW/kolourpaint.po | 2665 +++++++++++++ scan/sanedialog.cpp | 118 + scan/sanedialog.h | 67 + tests/45deg_line.png | Bin 0 -> 94 bytes tests/4x4-transparent.png | Bin 0 -> 98 bytes tests/5x5.png | Bin 0 -> 99 bytes tests/depth1.bmp | Bin 0 -> 462 bytes tests/dither.png | Bin 0 -> 859 bytes tests/freeform-selection.png | Bin 0 -> 227 bytes tests/kolourcircles.png | Bin 0 -> 4889 bytes tests/paste_in_new_window.png | Bin 0 -> 248 bytes tests/paste_in_new_window.txt | 15 + tests/rotate.png | Bin 0 -> 79 bytes tests/selections.txt | 12 + tests/small16x16.png | Bin 0 -> 120 bytes tests/tool_fill_xlimit.png | Bin 0 -> 119 bytes tests/transforms-rotate-me-90-clockwise.png | Bin 0 -> 222 bytes tests/transforms.png | Bin 0 -> 213 bytes tests/transparent.png | Bin 0 -> 149 bytes tests/transparent_selection.png | Bin 0 -> 226 bytes tools/flow/kpToolBrush.cpp | 56 + tools/flow/kpToolBrush.h | 50 + tools/flow/kpToolColorEraser.cpp | 160 + tools/flow/kpToolColorEraser.h | 65 + tools/flow/kpToolEraser.cpp | 79 + tools/flow/kpToolEraser.h | 55 + tools/flow/kpToolFlowBase.cpp | 492 +++ tools/flow/kpToolFlowBase.h | 120 + tools/flow/kpToolFlowPixmapBase.cpp | 81 + tools/flow/kpToolFlowPixmapBase.h | 58 + tools/flow/kpToolPen.cpp | 84 + tools/flow/kpToolPen.h | 50 + tools/flow/kpToolSpraycan.cpp | 259 ++ tools/flow/kpToolSpraycan.h | 90 + tools/kpTool.cpp | 262 ++ tools/kpTool.h | 464 +++ tools/kpToolAction.cpp | 70 + tools/kpToolAction.h | 53 + tools/kpToolColorPicker.cpp | 137 + tools/kpToolColorPicker.h | 71 + tools/kpToolFloodFill.cpp | 169 + tools/kpToolFloodFill.h | 60 + tools/kpToolPrivate.h | 83 + tools/kpToolZoom.cpp | 254 ++ tools/kpToolZoom.h | 66 + tools/kpTool_Drawing.cpp | 423 ++ tools/kpTool_KeyboardEvents.cpp | 431 ++ tools/kpTool_MouseEvents.cpp | 337 ++ tools/kpTool_OtherEvents.cpp | 168 + tools/kpTool_UserNotifications.cpp | 165 + tools/kpTool_Utilities.cpp | 294 ++ tools/polygonal/kpToolCurve.cpp | 196 + tools/polygonal/kpToolCurve.h | 54 + tools/polygonal/kpToolLine.cpp | 73 + tools/polygonal/kpToolLine.h | 51 + tools/polygonal/kpToolPolygon.cpp | 190 + tools/polygonal/kpToolPolygon.h | 62 + tools/polygonal/kpToolPolygonalBase.cpp | 498 +++ tools/polygonal/kpToolPolygonalBase.h | 200 + tools/polygonal/kpToolPolyline.cpp | 125 + tools/polygonal/kpToolPolyline.h | 58 + tools/rectangular/kpToolEllipse.cpp | 86 + tools/rectangular/kpToolEllipse.h | 50 + tools/rectangular/kpToolRectangle.cpp | 83 + tools/rectangular/kpToolRectangle.h | 50 + tools/rectangular/kpToolRectangularBase.cpp | 388 ++ tools/rectangular/kpToolRectangularBase.h | 99 + tools/rectangular/kpToolRoundedRectangle.cpp | 89 + tools/rectangular/kpToolRoundedRectangle.h | 50 + .../image/kpAbstractImageSelectionTool.cpp | 103 + .../image/kpAbstractImageSelectionTool.h | 106 + ...bstractImageSelectionTool_Transparency.cpp | 207 + .../image/kpToolEllipticalSelection.cpp | 79 + .../image/kpToolEllipticalSelection.h | 50 + .../image/kpToolFreeFormSelection.cpp | 139 + .../selection/image/kpToolFreeFormSelection.h | 50 + tools/selection/image/kpToolRectSelection.cpp | 81 + tools/selection/image/kpToolRectSelection.h | 50 + tools/selection/kpAbstractSelectionTool.cpp | 641 +++ tools/selection/kpAbstractSelectionTool.h | 600 +++ .../kpAbstractSelectionToolPrivate.h | 91 + .../kpAbstractSelectionTool_Create.cpp | 300 ++ ...kpAbstractSelectionTool_KeyboardEvents.cpp | 102 + .../kpAbstractSelectionTool_Move.cpp | 407 ++ .../kpAbstractSelectionTool_ResizeScale.cpp | 450 +++ tools/selection/text/kpToolText.cpp | 222 ++ tools/selection/text/kpToolText.h | 725 ++++ tools/selection/text/kpToolTextPrivate.h | 51 + tools/selection/text/kpToolText_Commands.cpp | 125 + tools/selection/text/kpToolText_Create.cpp | 293 ++ .../selection/text/kpToolText_CursorCalc.cpp | 220 ++ .../text/kpToolText_InputMethodEvents.cpp | 96 + .../text/kpToolText_KeyboardEvents.cpp | 212 + ...oolText_KeyboardEvents_HandleArrowKeys.cpp | 221 ++ ...olText_KeyboardEvents_HandleTypingKeys.cpp | 201 + tools/selection/text/kpToolText_Move.cpp | 63 + .../selection/text/kpToolText_ResizeScale.cpp | 55 + .../selection/text/kpToolText_SelectText.cpp | 137 + tools/selection/text/kpToolText_TextStyle.cpp | 334 ++ views/kpThumbnailView.cpp | 96 + views/kpThumbnailView.h | 90 + views/kpUnzoomedThumbnailView.cpp | 222 ++ views/kpUnzoomedThumbnailView.h | 106 + views/kpView.cpp | 687 ++++ views/kpView.h | 602 +++ views/kpViewPrivate.h | 77 + views/kpView_Events.cpp | 285 ++ views/kpView_Paint.cpp | 637 +++ views/kpView_Selections.cpp | 381 ++ views/kpZoomedThumbnailView.cpp | 144 + views/kpZoomedThumbnailView.h | 95 + views/kpZoomedView.cpp | 101 + views/kpZoomedView.h | 96 + views/manager/kpViewManager.cpp | 356 ++ views/manager/kpViewManager.h | 254 ++ views/manager/kpViewManagerPrivate.h | 85 + views/manager/kpViewManager_TextCursor.cpp | 236 ++ views/manager/kpViewManager_ViewUpdates.cpp | 254 ++ .../kpColorSimilarityCubeRenderer.cpp | 236 ++ .../kpColorSimilarityCubeRenderer.h | 54 + .../kpColorSimilarityFrame.cpp | 78 + .../colorSimilarity/kpColorSimilarityFrame.h | 51 + .../kpColorSimilarityHolder.cpp | 190 + .../colorSimilarity/kpColorSimilarityHolder.h | 65 + .../kpColorSimilarityToolBarItem.cpp | 286 ++ .../kpColorSimilarityToolBarItem.h | 104 + .../effects/kpEffectBalanceWidget.cpp | 330 ++ .../imagelib/effects/kpEffectBalanceWidget.h | 85 + .../effects/kpEffectBlurSharpenWidget.cpp | 184 + .../effects/kpEffectBlurSharpenWidget.h | 72 + .../imagelib/effects/kpEffectEmbossWidget.cpp | 106 + .../imagelib/effects/kpEffectEmbossWidget.h | 62 + .../effects/kpEffectFlattenWidget.cpp | 182 + .../imagelib/effects/kpEffectFlattenWidget.h | 80 + .../imagelib/effects/kpEffectHSVWidget.cpp | 120 + widgets/imagelib/effects/kpEffectHSVWidget.h | 62 + .../imagelib/effects/kpEffectInvertWidget.cpp | 212 + .../imagelib/effects/kpEffectInvertWidget.h | 77 + .../effects/kpEffectReduceColorsWidget.cpp | 178 + .../effects/kpEffectReduceColorsWidget.h | 73 + .../effects/kpEffectToneEnhanceWidget.cpp | 138 + .../effects/kpEffectToneEnhanceWidget.h | 68 + .../imagelib/effects/kpEffectWidgetBase.cpp | 48 + widgets/imagelib/effects/kpEffectWidgetBase.h | 73 + widgets/imagelib/effects/kpNumInput.cpp | 705 ++++ widgets/imagelib/effects/kpNumInput.h | 492 +++ widgets/kpColorCells.cpp | 604 +++ widgets/kpColorCells.h | 168 + widgets/kpColorPalette.cpp | 126 + widgets/kpColorPalette.h | 63 + widgets/kpDefaultColorCollection.cpp | 69 + widgets/kpDefaultColorCollection.h | 50 + widgets/kpDocumentSaveOptionsWidget.cpp | 754 ++++ widgets/kpDocumentSaveOptionsWidget.h | 156 + widgets/kpDualColorButton.cpp | 469 +++ widgets/kpDualColorButton.h | 96 + widgets/kpPrintDialogPage.cpp | 102 + widgets/kpPrintDialogPage.h | 52 + widgets/kpTransparentColorCell.cpp | 127 + widgets/kpTransparentColorCell.h | 66 + widgets/toolbars/kpColorToolBar.cpp | 341 ++ widgets/toolbars/kpColorToolBar.h | 124 + widgets/toolbars/kpToolToolBar.cpp | 479 +++ widgets/toolbars/kpToolToolBar.h | 126 + widgets/toolbars/options/kpToolWidgetBase.cpp | 754 ++++ widgets/toolbars/options/kpToolWidgetBase.h | 114 + .../toolbars/options/kpToolWidgetBrush.cpp | 296 ++ widgets/toolbars/options/kpToolWidgetBrush.h | 80 + .../options/kpToolWidgetEraserSize.cpp | 183 + .../toolbars/options/kpToolWidgetEraserSize.h | 81 + .../options/kpToolWidgetFillStyle.cpp | 175 + .../toolbars/options/kpToolWidgetFillStyle.h | 78 + .../options/kpToolWidgetLineWidth.cpp | 88 + .../toolbars/options/kpToolWidgetLineWidth.h | 54 + .../kpToolWidgetOpaqueOrTransparent.cpp | 101 + .../options/kpToolWidgetOpaqueOrTransparent.h | 57 + .../options/kpToolWidgetSpraycanSize.cpp | 119 + .../options/kpToolWidgetSpraycanSize.h | 54 + 824 files changed, 267531 insertions(+) create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 .kde-ci.yml create mode 100644 .krazy create mode 100644 AUTHORS create mode 100644 BUGS create mode 100644 CMakeLists.txt create mode 100644 COPYING create mode 100644 COPYING.DOC create mode 100644 COPYING.LIB create mode 100644 ChangeLog create mode 100644 Messages.sh create mode 100644 NEWS create mode 100644 README.md create mode 100644 commands/imagelib/effects/kpEffectBalanceCommand.cpp create mode 100644 commands/imagelib/effects/kpEffectBalanceCommand.h create mode 100644 commands/imagelib/effects/kpEffectBlurSharpenCommand.cpp create mode 100644 commands/imagelib/effects/kpEffectBlurSharpenCommand.h create mode 100644 commands/imagelib/effects/kpEffectClearCommand.cpp create mode 100644 commands/imagelib/effects/kpEffectClearCommand.h create mode 100644 commands/imagelib/effects/kpEffectCommandBase.cpp create mode 100644 commands/imagelib/effects/kpEffectCommandBase.h create mode 100644 commands/imagelib/effects/kpEffectEmbossCommand.cpp create mode 100644 commands/imagelib/effects/kpEffectEmbossCommand.h create mode 100644 commands/imagelib/effects/kpEffectFlattenCommand.cpp create mode 100644 commands/imagelib/effects/kpEffectFlattenCommand.h create mode 100644 commands/imagelib/effects/kpEffectGrayscaleCommand.cpp create mode 100644 commands/imagelib/effects/kpEffectGrayscaleCommand.h create mode 100644 commands/imagelib/effects/kpEffectHSVCommand.cpp create mode 100644 commands/imagelib/effects/kpEffectHSVCommand.h create mode 100644 commands/imagelib/effects/kpEffectInvertCommand.cpp create mode 100644 commands/imagelib/effects/kpEffectInvertCommand.h create mode 100644 commands/imagelib/effects/kpEffectReduceColorsCommand.cpp create mode 100644 commands/imagelib/effects/kpEffectReduceColorsCommand.h create mode 100644 commands/imagelib/effects/kpEffectToneEnhanceCommand.cpp create mode 100644 commands/imagelib/effects/kpEffectToneEnhanceCommand.h create mode 100644 commands/imagelib/kpDocumentMetaInfoCommand.cpp create mode 100644 commands/imagelib/kpDocumentMetaInfoCommand.h create mode 100644 commands/imagelib/transforms/kpTransformFlipCommand.cpp create mode 100644 commands/imagelib/transforms/kpTransformFlipCommand.h create mode 100644 commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp create mode 100644 commands/imagelib/transforms/kpTransformResizeScaleCommand.h create mode 100644 commands/imagelib/transforms/kpTransformRotateCommand.cpp create mode 100644 commands/imagelib/transforms/kpTransformRotateCommand.h create mode 100644 commands/imagelib/transforms/kpTransformSkewCommand.cpp create mode 100644 commands/imagelib/transforms/kpTransformSkewCommand.h create mode 100644 commands/kpCommand.cpp create mode 100644 commands/kpCommand.h create mode 100644 commands/kpCommandHistory.cpp create mode 100644 commands/kpCommandHistory.h create mode 100644 commands/kpCommandHistoryBase.cpp create mode 100644 commands/kpCommandHistoryBase.h create mode 100644 commands/kpCommandSize.cpp create mode 100644 commands/kpCommandSize.h create mode 100644 commands/kpMacroCommand.cpp create mode 100644 commands/kpMacroCommand.h create mode 100644 commands/kpNamedCommand.cpp create mode 100644 commands/kpNamedCommand.h create mode 100644 commands/tools/flow/kpToolFlowCommand.cpp create mode 100644 commands/tools/flow/kpToolFlowCommand.h create mode 100644 commands/tools/kpToolColorPickerCommand.cpp create mode 100644 commands/tools/kpToolColorPickerCommand.h create mode 100644 commands/tools/kpToolFloodFillCommand.cpp create mode 100644 commands/tools/kpToolFloodFillCommand.h create mode 100644 commands/tools/polygonal/kpToolPolygonalCommand.cpp create mode 100644 commands/tools/polygonal/kpToolPolygonalCommand.h create mode 100644 commands/tools/rectangular/kpToolRectangularCommand.cpp create mode 100644 commands/tools/rectangular/kpToolRectangularCommand.h create mode 100644 commands/tools/selection/kpAbstractSelectionContentCommand.cpp create mode 100644 commands/tools/selection/kpAbstractSelectionContentCommand.h create mode 100644 commands/tools/selection/kpToolImageSelectionTransparencyCommand.cpp create mode 100644 commands/tools/selection/kpToolImageSelectionTransparencyCommand.h create mode 100644 commands/tools/selection/kpToolSelectionCreateCommand.cpp create mode 100644 commands/tools/selection/kpToolSelectionCreateCommand.h create mode 100644 commands/tools/selection/kpToolSelectionDestroyCommand.cpp create mode 100644 commands/tools/selection/kpToolSelectionDestroyCommand.h create mode 100644 commands/tools/selection/kpToolSelectionMoveCommand.cpp create mode 100644 commands/tools/selection/kpToolSelectionMoveCommand.h create mode 100644 commands/tools/selection/kpToolSelectionPullFromDocumentCommand.cpp create mode 100644 commands/tools/selection/kpToolSelectionPullFromDocumentCommand.h create mode 100644 commands/tools/selection/kpToolSelectionResizeScaleCommand.cpp create mode 100644 commands/tools/selection/kpToolSelectionResizeScaleCommand.h create mode 100644 commands/tools/selection/text/kpToolTextBackspaceCommand.cpp create mode 100644 commands/tools/selection/text/kpToolTextBackspaceCommand.h create mode 100644 commands/tools/selection/text/kpToolTextChangeStyleCommand.cpp create mode 100644 commands/tools/selection/text/kpToolTextChangeStyleCommand.h create mode 100644 commands/tools/selection/text/kpToolTextDeleteCommand.cpp create mode 100644 commands/tools/selection/text/kpToolTextDeleteCommand.h create mode 100644 commands/tools/selection/text/kpToolTextEnterCommand.cpp create mode 100644 commands/tools/selection/text/kpToolTextEnterCommand.h create mode 100644 commands/tools/selection/text/kpToolTextGiveContentCommand.cpp create mode 100644 commands/tools/selection/text/kpToolTextGiveContentCommand.h create mode 100644 commands/tools/selection/text/kpToolTextInsertCommand.cpp create mode 100644 commands/tools/selection/text/kpToolTextInsertCommand.h create mode 100644 cursors/kpCursorLightCross.cpp create mode 100644 cursors/kpCursorLightCross.h create mode 100644 cursors/kpCursorProvider.cpp create mode 100644 cursors/kpCursorProvider.h create mode 100644 dialogs/imagelib/effects/kpEffectsDialog.cpp create mode 100644 dialogs/imagelib/effects/kpEffectsDialog.h create mode 100644 dialogs/imagelib/kpDocumentMetaInfoDialog.cpp create mode 100644 dialogs/imagelib/kpDocumentMetaInfoDialog.h create mode 100644 dialogs/imagelib/transforms/kpTransformPreviewDialog.cpp create mode 100644 dialogs/imagelib/transforms/kpTransformPreviewDialog.h create mode 100644 dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp create mode 100644 dialogs/imagelib/transforms/kpTransformResizeScaleDialog.h create mode 100644 dialogs/imagelib/transforms/kpTransformRotateDialog.cpp create mode 100644 dialogs/imagelib/transforms/kpTransformRotateDialog.h create mode 100644 dialogs/imagelib/transforms/kpTransformSkewDialog.cpp create mode 100644 dialogs/imagelib/transforms/kpTransformSkewDialog.h create mode 100644 dialogs/kpColorSimilarityDialog.cpp create mode 100644 dialogs/kpColorSimilarityDialog.h create mode 100644 dialogs/kpDocumentSaveOptionsPreviewDialog.cpp create mode 100644 dialogs/kpDocumentSaveOptionsPreviewDialog.h create mode 100644 doc/CMakeLists.txt create mode 100644 doc/KolourPaint.png create mode 100644 doc/brush_shapes.png create mode 100644 doc/color_box.png create mode 100644 doc/eraser_shapes.png create mode 100644 doc/fcc_std_text.png create mode 100644 doc/fcc_trans_text.png create mode 100644 doc/fill_color_similarity.png create mode 100644 doc/fill_style.png create mode 100644 doc/image_balance.png create mode 100644 doc/image_emboss.png create mode 100644 doc/image_flatten.png create mode 100644 doc/image_invert.png create mode 100644 doc/image_reduce_colors.png create mode 100644 doc/image_resize_scale.png create mode 100644 doc/image_rotate.png create mode 100644 doc/image_skew.png create mode 100644 doc/image_soften_sharpen.png create mode 100644 doc/index.docbook create mode 100644 doc/line_width.png create mode 100644 doc/lines_30_45_deg.png create mode 100644 doc/lines_30_deg.png create mode 100644 doc/lines_45_deg.png create mode 100644 doc/rotate_image_30.png create mode 100644 doc/rotate_selection_30.png create mode 100644 doc/screenshot_acquiring.png create mode 100644 doc/selections_opaque_transparent.png create mode 100644 doc/spraycan_patterns.png create mode 100644 doc/text_zoom_grid.png create mode 100644 doc/tool_brush.png create mode 100644 doc/tool_color_picker.png create mode 100644 doc/tool_color_washer.png create mode 100644 doc/tool_curve.png create mode 100644 doc/tool_ellipse.png create mode 100644 doc/tool_elliptical_selection.png create mode 100644 doc/tool_eraser.png create mode 100644 doc/tool_flood_fill.png create mode 100644 doc/tool_free_form_selection.png create mode 100644 doc/tool_line.png create mode 100644 doc/tool_pen.png create mode 100644 doc/tool_polygon.png create mode 100644 doc/tool_polyline.png create mode 100644 doc/tool_polystar.png create mode 100644 doc/tool_rect_selection.png create mode 100644 doc/tool_rectangle.png create mode 100644 doc/tool_rectangles.png create mode 100644 doc/tool_rounded_rectangle.png create mode 100644 doc/tool_selections.png create mode 100644 doc/tool_spraycan.png create mode 100644 doc/tool_text.png create mode 100644 doc/view_thumbnails.png create mode 100644 document/kpDocument.cpp create mode 100644 document/kpDocument.h create mode 100644 document/kpDocumentPrivate.h create mode 100644 document/kpDocumentSaveOptions.cpp create mode 100644 document/kpDocumentSaveOptions.h create mode 100644 document/kpDocument_Open.cpp create mode 100644 document/kpDocument_Save.cpp create mode 100644 document/kpDocument_Selection.cpp create mode 100644 environments/commands/kpCommandEnvironment.cpp create mode 100644 environments/commands/kpCommandEnvironment.h create mode 100644 environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.cpp create mode 100644 environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.h create mode 100644 environments/document/kpDocumentEnvironment.cpp create mode 100644 environments/document/kpDocumentEnvironment.h create mode 100644 environments/kpEnvironmentBase.cpp create mode 100644 environments/kpEnvironmentBase.h create mode 100644 environments/tools/kpToolEnvironment.cpp create mode 100644 environments/tools/kpToolEnvironment.h create mode 100644 environments/tools/selection/kpToolSelectionEnvironment.cpp create mode 100644 environments/tools/selection/kpToolSelectionEnvironment.h create mode 100755 gen_cmake_include_dirs create mode 100755 gen_cmake_srcs create mode 100644 generic/kpSetOverrideCursorSaver.cpp create mode 100644 generic/kpSetOverrideCursorSaver.h create mode 100644 generic/kpWidgetMapper.cpp create mode 100644 generic/kpWidgetMapper.h create mode 100644 generic/widgets/kpResizeSignallingLabel.cpp create mode 100644 generic/widgets/kpResizeSignallingLabel.h create mode 100644 generic/widgets/kpSubWindow.cpp create mode 100644 generic/widgets/kpSubWindow.h create mode 100644 imagelib/effects/blitz.cpp create mode 100644 imagelib/effects/blitz.h create mode 100644 imagelib/effects/kpEffectBalance.cpp create mode 100644 imagelib/effects/kpEffectBalance.h create mode 100644 imagelib/effects/kpEffectBlurSharpen.cpp create mode 100644 imagelib/effects/kpEffectBlurSharpen.h create mode 100644 imagelib/effects/kpEffectEmboss.cpp create mode 100644 imagelib/effects/kpEffectEmboss.h create mode 100644 imagelib/effects/kpEffectFlatten.cpp create mode 100644 imagelib/effects/kpEffectFlatten.h create mode 100644 imagelib/effects/kpEffectGrayscale.cpp create mode 100644 imagelib/effects/kpEffectGrayscale.h create mode 100644 imagelib/effects/kpEffectHSV.cpp create mode 100644 imagelib/effects/kpEffectHSV.h create mode 100644 imagelib/effects/kpEffectInvert.cpp create mode 100644 imagelib/effects/kpEffectInvert.h create mode 100644 imagelib/effects/kpEffectReduceColors.cpp create mode 100644 imagelib/effects/kpEffectReduceColors.h create mode 100644 imagelib/effects/kpEffectToneEnhance.cpp create mode 100644 imagelib/effects/kpEffectToneEnhance.h create mode 100644 imagelib/kpColor.cpp create mode 100644 imagelib/kpColor.h create mode 100644 imagelib/kpColor_Constants.cpp create mode 100644 imagelib/kpDocumentMetaInfo.cpp create mode 100644 imagelib/kpDocumentMetaInfo.h create mode 100644 imagelib/kpFloodFill.cpp create mode 100644 imagelib/kpFloodFill.h create mode 100644 imagelib/kpImage.h create mode 100644 imagelib/kpPainter.cpp create mode 100644 imagelib/kpPainter.h create mode 100644 imagelib/transforms/kpTransformAutoCrop.cpp create mode 100644 imagelib/transforms/kpTransformAutoCrop.h create mode 100644 imagelib/transforms/kpTransformCrop.cpp create mode 100644 imagelib/transforms/kpTransformCrop.h create mode 100644 imagelib/transforms/kpTransformCropPrivate.h create mode 100644 imagelib/transforms/kpTransformCrop_ImageSelection.cpp create mode 100644 imagelib/transforms/kpTransformCrop_TextSelection.cpp create mode 100644 kolourpaint.cpp create mode 100644 kolourpaint.qrc create mode 100644 kolourpaintui.rc create mode 100644 kpDefs.h create mode 100644 kpLogCategories.cpp create mode 100644 kpLogCategories.h create mode 100644 kpThumbnail.cpp create mode 100644 kpThumbnail.h create mode 100644 kpViewScrollableContainer.cpp create mode 100644 kpViewScrollableContainer.h create mode 100644 layers/selections/image/kpAbstractImageSelection.cpp create mode 100644 layers/selections/image/kpAbstractImageSelection.h create mode 100644 layers/selections/image/kpEllipticalImageSelection.cpp create mode 100644 layers/selections/image/kpEllipticalImageSelection.h create mode 100644 layers/selections/image/kpFreeFormImageSelection.cpp create mode 100644 layers/selections/image/kpFreeFormImageSelection.h create mode 100644 layers/selections/image/kpImageSelectionTransparency.cpp create mode 100644 layers/selections/image/kpImageSelectionTransparency.h create mode 100644 layers/selections/image/kpRectangularImageSelection.cpp create mode 100644 layers/selections/image/kpRectangularImageSelection.h create mode 100644 layers/selections/kpAbstractSelection.cpp create mode 100644 layers/selections/kpAbstractSelection.h create mode 100644 layers/selections/kpSelectionDrag.cpp create mode 100644 layers/selections/kpSelectionDrag.h create mode 100644 layers/selections/kpSelectionFactory.cpp create mode 100644 layers/selections/kpSelectionFactory.h create mode 100644 layers/selections/text/kpPreeditText.cpp create mode 100644 layers/selections/text/kpPreeditText.h create mode 100644 layers/selections/text/kpTextSelection.cpp create mode 100644 layers/selections/text/kpTextSelection.h create mode 100644 layers/selections/text/kpTextSelectionPrivate.h create mode 100644 layers/selections/text/kpTextSelection_Cursor.cpp create mode 100644 layers/selections/text/kpTextSelection_Paint.cpp create mode 100644 layers/selections/text/kpTextStyle.cpp create mode 100644 layers/selections/text/kpTextStyle.h create mode 100644 layers/tempImage/kpTempImage.cpp create mode 100644 layers/tempImage/kpTempImage.h create mode 100644 lgpl/CMakeLists.txt create mode 100644 lgpl/generic/kpColorCollection.cpp create mode 100644 lgpl/generic/kpColorCollection.h create mode 100644 lgpl/generic/kpUrlFormatter.cpp create mode 100644 lgpl/generic/kpUrlFormatter.h create mode 100644 lgpl/generic/widgets/kpColorCellsBase.cpp create mode 100644 lgpl/generic/widgets/kpColorCellsBase.h create mode 100644 logo.png create mode 100644 mainWindow/kpMainWindow.cpp create mode 100644 mainWindow/kpMainWindow.h create mode 100644 mainWindow/kpMainWindowPrivate.h create mode 100644 mainWindow/kpMainWindow_Colors.cpp create mode 100644 mainWindow/kpMainWindow_Edit.cpp create mode 100644 mainWindow/kpMainWindow_File.cpp create mode 100644 mainWindow/kpMainWindow_Image.cpp create mode 100644 mainWindow/kpMainWindow_Settings.cpp create mode 100644 mainWindow/kpMainWindow_StatusBar.cpp create mode 100644 mainWindow/kpMainWindow_Text.cpp create mode 100644 mainWindow/kpMainWindow_Tools.cpp create mode 100644 mainWindow/kpMainWindow_View.cpp create mode 100644 mainWindow/kpMainWindow_View_Thumbnail.cpp create mode 100644 mainWindow/kpMainWindow_View_Zoom.cpp create mode 100644 org.kde.kolourpaint.appdata.xml create mode 100644 org.kde.kolourpaint.desktop create mode 100644 patches/checkerboard-faster-render.diff create mode 100644 patches/linear-sharpen-effect.diff create mode 100644 pics/CMakeLists.txt create mode 100644 pics/action/16-actions-tool_brush.png create mode 100644 pics/action/16-actions-tool_color_eraser.png create mode 100644 pics/action/16-actions-tool_color_picker.png create mode 100644 pics/action/16-actions-tool_curve.png create mode 100644 pics/action/16-actions-tool_ellipse.png create mode 100644 pics/action/16-actions-tool_elliptical_selection.png create mode 100644 pics/action/16-actions-tool_eraser.png create mode 100644 pics/action/16-actions-tool_flood_fill.png create mode 100644 pics/action/16-actions-tool_free_form_selection.png create mode 100644 pics/action/16-actions-tool_line.png create mode 100644 pics/action/16-actions-tool_pen.png create mode 100644 pics/action/16-actions-tool_polygon.png create mode 100644 pics/action/16-actions-tool_polyline.png create mode 100644 pics/action/16-actions-tool_rect_selection.png create mode 100644 pics/action/16-actions-tool_rectangle.png create mode 100644 pics/action/16-actions-tool_rounded_rectangle.png create mode 100644 pics/action/16-actions-tool_spraycan.png create mode 100644 pics/action/16-actions-tool_text.png create mode 100644 pics/action/22-actions-tool_brush.png create mode 100644 pics/action/22-actions-tool_color_eraser.png create mode 100644 pics/action/22-actions-tool_color_picker.png create mode 100644 pics/action/22-actions-tool_curve.png create mode 100644 pics/action/22-actions-tool_ellipse.png create mode 100644 pics/action/22-actions-tool_elliptical_selection.png create mode 100644 pics/action/22-actions-tool_eraser.png create mode 100644 pics/action/22-actions-tool_flood_fill.png create mode 100644 pics/action/22-actions-tool_free_form_selection.png create mode 100644 pics/action/22-actions-tool_line.png create mode 100644 pics/action/22-actions-tool_pen.png create mode 100644 pics/action/22-actions-tool_polygon.png create mode 100644 pics/action/22-actions-tool_polyline.png create mode 100644 pics/action/22-actions-tool_rect_selection.png create mode 100644 pics/action/22-actions-tool_rectangle.png create mode 100644 pics/action/22-actions-tool_rounded_rectangle.png create mode 100644 pics/action/22-actions-tool_spraycan.png create mode 100644 pics/action/22-actions-tool_text.png create mode 100644 pics/action/32-actions-tool_brush.png create mode 100644 pics/action/32-actions-tool_color_eraser.png create mode 100644 pics/action/32-actions-tool_color_picker.png create mode 100644 pics/action/32-actions-tool_curve.png create mode 100644 pics/action/32-actions-tool_ellipse.png create mode 100644 pics/action/32-actions-tool_elliptical_selection.png create mode 100644 pics/action/32-actions-tool_eraser.png create mode 100644 pics/action/32-actions-tool_flood_fill.png create mode 100644 pics/action/32-actions-tool_free_form_selection.png create mode 100644 pics/action/32-actions-tool_line.png create mode 100644 pics/action/32-actions-tool_pen.png create mode 100644 pics/action/32-actions-tool_polygon.png create mode 100644 pics/action/32-actions-tool_polyline.png create mode 100644 pics/action/32-actions-tool_rect_selection.png create mode 100644 pics/action/32-actions-tool_rectangle.png create mode 100644 pics/action/32-actions-tool_rounded_rectangle.png create mode 100644 pics/action/32-actions-tool_spraycan.png create mode 100644 pics/action/32-actions-tool_text.png create mode 100644 pics/action/48-actions-tool_brush.png create mode 100644 pics/action/48-actions-tool_color_eraser.png create mode 100644 pics/action/48-actions-tool_color_picker.png create mode 100644 pics/action/48-actions-tool_curve.png create mode 100644 pics/action/48-actions-tool_ellipse.png create mode 100644 pics/action/48-actions-tool_elliptical_selection.png create mode 100644 pics/action/48-actions-tool_eraser.png create mode 100644 pics/action/48-actions-tool_flood_fill.png create mode 100644 pics/action/48-actions-tool_free_form_selection.png create mode 100644 pics/action/48-actions-tool_line.png create mode 100644 pics/action/48-actions-tool_pen.png create mode 100644 pics/action/48-actions-tool_polygon.png create mode 100644 pics/action/48-actions-tool_polyline.png create mode 100644 pics/action/48-actions-tool_rect_selection.png create mode 100644 pics/action/48-actions-tool_rectangle.png create mode 100644 pics/action/48-actions-tool_rounded_rectangle.png create mode 100644 pics/action/48-actions-tool_spraycan.png create mode 100644 pics/action/48-actions-tool_text.png create mode 100644 pics/action/CMakeLists.txt create mode 100644 pics/action/sc-actions-tool_brush.svgz create mode 100644 pics/action/sc-actions-tool_color_eraser.svgz create mode 100644 pics/action/sc-actions-tool_color_picker.svgz create mode 100644 pics/action/sc-actions-tool_curve.svgz create mode 100644 pics/action/sc-actions-tool_ellipse.svgz create mode 100644 pics/action/sc-actions-tool_elliptical_selection.svgz create mode 100644 pics/action/sc-actions-tool_eraser.svgz create mode 100644 pics/action/sc-actions-tool_flood_fill.svgz create mode 100644 pics/action/sc-actions-tool_free_form_selection.svgz create mode 100644 pics/action/sc-actions-tool_line.svgz create mode 100644 pics/action/sc-actions-tool_pen.svgz create mode 100644 pics/action/sc-actions-tool_polygon.svgz create mode 100644 pics/action/sc-actions-tool_polyline.svgz create mode 100644 pics/action/sc-actions-tool_rect_selection.svgz create mode 100644 pics/action/sc-actions-tool_rectangle.svgz create mode 100644 pics/action/sc-actions-tool_rounded_rectangle.svgz create mode 100644 pics/action/sc-actions-tool_spraycan.svgz create mode 100644 pics/action/sc-actions-tool_text.svgz create mode 100644 pics/app/128-apps-kolourpaint.png create mode 100644 pics/app/16-apps-kolourpaint.png create mode 100644 pics/app/22-apps-kolourpaint.png create mode 100644 pics/app/32-apps-kolourpaint.png create mode 100644 pics/app/48-apps-kolourpaint.png create mode 100644 pics/app/CMakeLists.txt create mode 100644 pics/app/sc-apps-kolourpaint.svgz create mode 100644 pics/custom/color_transparent_26x26.png create mode 100644 pics/custom/colorbutton_swap_16x16.png create mode 100644 pics/custom/image_rotate_anticlockwise.png create mode 100644 pics/custom/image_rotate_clockwise.png create mode 100644 pics/custom/image_skew_horizontal.png create mode 100644 pics/custom/image_skew_vertical.png create mode 100644 pics/custom/option_opaque.png create mode 100644 pics/custom/option_transparent.png create mode 100644 pics/custom/resize.png create mode 100644 pics/custom/scale.png create mode 100644 pics/custom/smooth_scale.png create mode 100644 pics/custom/tool_spraycan_17x17.png create mode 100644 pics/custom/tool_spraycan_29x29.png create mode 100644 pics/custom/tool_spraycan_9x9.png create mode 100644 pixmapfx/kpPixmapFX.h create mode 100644 pixmapfx/kpPixmapFX_DrawShapes.cpp create mode 100644 pixmapfx/kpPixmapFX_GetSetPixmapParts.cpp create mode 100644 pixmapfx/kpPixmapFX_Transforms.cpp create mode 100644 po/ar/kolourpaint.po create mode 100644 po/be/kolourpaint.po create mode 100644 po/bg/kolourpaint.po create mode 100644 po/bs/kolourpaint.po create mode 100644 po/ca/docs/kolourpaint/KolourPaint.png create mode 100644 po/ca/docs/kolourpaint/image_balance.png create mode 100644 po/ca/docs/kolourpaint/image_emboss.png create mode 100644 po/ca/docs/kolourpaint/image_flatten.png create mode 100644 po/ca/docs/kolourpaint/image_invert.png create mode 100644 po/ca/docs/kolourpaint/image_reduce_colors.png create mode 100644 po/ca/docs/kolourpaint/image_resize_scale.png create mode 100644 po/ca/docs/kolourpaint/image_rotate.png create mode 100644 po/ca/docs/kolourpaint/image_skew.png create mode 100644 po/ca/docs/kolourpaint/image_soften_sharpen.png create mode 100644 po/ca/docs/kolourpaint/index.docbook create mode 100644 po/ca/kolourpaint.po create mode 100644 po/ca@valencia/kolourpaint.po create mode 100644 po/cs/kolourpaint.po create mode 100644 po/da/kolourpaint.po create mode 100644 po/de/docs/kolourpaint/KolourPaint.png create mode 100644 po/de/docs/kolourpaint/image_balance.png create mode 100644 po/de/docs/kolourpaint/image_emboss.png create mode 100644 po/de/docs/kolourpaint/image_flatten.png create mode 100644 po/de/docs/kolourpaint/image_flip.png create mode 100644 po/de/docs/kolourpaint/image_invert.png create mode 100644 po/de/docs/kolourpaint/image_reduce_colors.png create mode 100644 po/de/docs/kolourpaint/image_resize_scale.png create mode 100644 po/de/docs/kolourpaint/image_rotate.png create mode 100644 po/de/docs/kolourpaint/image_skew.png create mode 100644 po/de/docs/kolourpaint/image_soften_sharpen.png create mode 100644 po/de/docs/kolourpaint/index.docbook create mode 100644 po/de/kolourpaint.po create mode 100644 po/el/kolourpaint.po create mode 100644 po/en_GB/kolourpaint.po create mode 100644 po/eo/kolourpaint.po create mode 100644 po/es/docs/kolourpaint/brush_shapes.png create mode 100644 po/es/docs/kolourpaint/color_box.png create mode 100644 po/es/docs/kolourpaint/eraser_shapes.png create mode 100644 po/es/docs/kolourpaint/fcc_std_text.png create mode 100644 po/es/docs/kolourpaint/fcc_trans_text.png create mode 100644 po/es/docs/kolourpaint/fill_color_similarity.png create mode 100644 po/es/docs/kolourpaint/fill_style.png create mode 100644 po/es/docs/kolourpaint/image_balance.png create mode 100644 po/es/docs/kolourpaint/image_emboss.png create mode 100644 po/es/docs/kolourpaint/image_flatten.png create mode 100644 po/es/docs/kolourpaint/image_invert.png create mode 100644 po/es/docs/kolourpaint/image_reduce_colors.png create mode 100644 po/es/docs/kolourpaint/image_resize_scale.png create mode 100644 po/es/docs/kolourpaint/image_rotate.png create mode 100644 po/es/docs/kolourpaint/image_skew.png create mode 100644 po/es/docs/kolourpaint/image_soften_sharpen.png create mode 100644 po/es/docs/kolourpaint/index.docbook create mode 100644 po/es/docs/kolourpaint/line_width.png create mode 100644 po/es/docs/kolourpaint/lines_30_45_deg.png create mode 100644 po/es/docs/kolourpaint/lines_30_deg.png create mode 100644 po/es/docs/kolourpaint/lines_45_deg.png create mode 100644 po/es/docs/kolourpaint/rotate_image_30.png create mode 100644 po/es/docs/kolourpaint/rotate_selection_30.png create mode 100644 po/es/docs/kolourpaint/selections_opaque_transparent.png create mode 100644 po/es/docs/kolourpaint/spraycan_patterns.png create mode 100644 po/es/docs/kolourpaint/text_zoom_grid.png create mode 100644 po/es/docs/kolourpaint/tool_brush.png create mode 100644 po/es/docs/kolourpaint/tool_color_picker.png create mode 100644 po/es/docs/kolourpaint/tool_color_washer.png create mode 100644 po/es/docs/kolourpaint/tool_curve.png create mode 100644 po/es/docs/kolourpaint/tool_ellipse.png create mode 100644 po/es/docs/kolourpaint/tool_elliptical_selection.png create mode 100644 po/es/docs/kolourpaint/tool_eraser.png create mode 100644 po/es/docs/kolourpaint/tool_flood_fill.png create mode 100644 po/es/docs/kolourpaint/tool_free_form_selection.png create mode 100644 po/es/docs/kolourpaint/tool_line.png create mode 100644 po/es/docs/kolourpaint/tool_pen.png create mode 100644 po/es/docs/kolourpaint/tool_polygon.png create mode 100644 po/es/docs/kolourpaint/tool_polyline.png create mode 100644 po/es/docs/kolourpaint/tool_polystar.png create mode 100644 po/es/docs/kolourpaint/tool_rect_selection.png create mode 100644 po/es/docs/kolourpaint/tool_rectangle.png create mode 100644 po/es/docs/kolourpaint/tool_rectangles.png create mode 100644 po/es/docs/kolourpaint/tool_rounded_rectangle.png create mode 100644 po/es/docs/kolourpaint/tool_selections.png create mode 100644 po/es/docs/kolourpaint/tool_spraycan.png create mode 100644 po/es/docs/kolourpaint/tool_text.png create mode 100644 po/es/docs/kolourpaint/view_thumbnails.png create mode 100644 po/es/kolourpaint.po create mode 100644 po/et/docs/kolourpaint/index.docbook create mode 100644 po/et/kolourpaint.po create mode 100644 po/eu/kolourpaint.po create mode 100644 po/fa/kolourpaint.po create mode 100644 po/fi/kolourpaint.po create mode 100644 po/fr/docs/kolourpaint/index.docbook create mode 100644 po/fr/kolourpaint.po create mode 100644 po/ga/kolourpaint.po create mode 100644 po/gl/docs/kolourpaint/index.docbook create mode 100644 po/gl/kolourpaint.po create mode 100644 po/he/kolourpaint.po create mode 100644 po/hi/kolourpaint.po create mode 100644 po/hr/kolourpaint.po create mode 100644 po/hu/kolourpaint.po create mode 100644 po/ia/kolourpaint.po create mode 100644 po/id/kolourpaint.po create mode 100644 po/is/kolourpaint.po create mode 100644 po/it/docs/kolourpaint/index.docbook create mode 100644 po/it/kolourpaint.po create mode 100644 po/ja/kolourpaint.po create mode 100644 po/kk/kolourpaint.po create mode 100644 po/km/kolourpaint.po create mode 100644 po/ko/kolourpaint.po create mode 100644 po/lt/kolourpaint.po create mode 100644 po/lv/kolourpaint.po create mode 100644 po/ml/kolourpaint.po create mode 100644 po/mr/kolourpaint.po create mode 100644 po/nb/kolourpaint.po create mode 100644 po/nds/kolourpaint.po create mode 100644 po/nl/docs/kolourpaint/KolourPaint.png create mode 100644 po/nl/docs/kolourpaint/index.docbook create mode 100644 po/nl/kolourpaint.po create mode 100644 po/nn/kolourpaint.po create mode 100644 po/pa/kolourpaint.po create mode 100644 po/pl/docs/kolourpaint/fullscreen_mode.png create mode 100644 po/pl/docs/kolourpaint/image_balance.png create mode 100644 po/pl/docs/kolourpaint/image_emboss.png create mode 100644 po/pl/docs/kolourpaint/image_flatten.png create mode 100644 po/pl/docs/kolourpaint/image_flip.png create mode 100644 po/pl/docs/kolourpaint/image_invert.png create mode 100644 po/pl/docs/kolourpaint/image_reduce_colors.png create mode 100644 po/pl/docs/kolourpaint/image_resize_scale.png create mode 100644 po/pl/docs/kolourpaint/image_rotate.png create mode 100644 po/pl/docs/kolourpaint/image_skew.png create mode 100644 po/pl/docs/kolourpaint/image_soften_sharpen.png create mode 100644 po/pl/docs/kolourpaint/index.docbook create mode 100644 po/pl/kolourpaint.po create mode 100644 po/pt/docs/kolourpaint/index.docbook create mode 100644 po/pt/kolourpaint.po create mode 100644 po/pt_BR/docs/kolourpaint/KolourPaint.png create mode 100644 po/pt_BR/docs/kolourpaint/brush_shapes.png create mode 100644 po/pt_BR/docs/kolourpaint/color_box.png create mode 100644 po/pt_BR/docs/kolourpaint/eraser_shapes.png create mode 100644 po/pt_BR/docs/kolourpaint/fcc_std_text.png create mode 100644 po/pt_BR/docs/kolourpaint/fcc_trans_text.png create mode 100644 po/pt_BR/docs/kolourpaint/fill_color_similarity.png create mode 100644 po/pt_BR/docs/kolourpaint/fill_style.png create mode 100644 po/pt_BR/docs/kolourpaint/image_balance.png create mode 100644 po/pt_BR/docs/kolourpaint/image_emboss.png create mode 100644 po/pt_BR/docs/kolourpaint/image_flatten.png create mode 100644 po/pt_BR/docs/kolourpaint/image_invert.png create mode 100644 po/pt_BR/docs/kolourpaint/image_reduce_colors.png create mode 100644 po/pt_BR/docs/kolourpaint/image_resize_scale.png create mode 100644 po/pt_BR/docs/kolourpaint/image_rotate.png create mode 100644 po/pt_BR/docs/kolourpaint/image_skew.png create mode 100644 po/pt_BR/docs/kolourpaint/image_soften_sharpen.png create mode 100644 po/pt_BR/docs/kolourpaint/index.docbook create mode 100644 po/pt_BR/docs/kolourpaint/line_width.png create mode 100644 po/pt_BR/docs/kolourpaint/lines_30_45_deg.png create mode 100644 po/pt_BR/docs/kolourpaint/lines_30_deg.png create mode 100644 po/pt_BR/docs/kolourpaint/lines_45_deg.png create mode 100644 po/pt_BR/docs/kolourpaint/rotate_image_30.png create mode 100644 po/pt_BR/docs/kolourpaint/rotate_selection_30.png create mode 100644 po/pt_BR/docs/kolourpaint/screenshot_acquiring.png create mode 100644 po/pt_BR/docs/kolourpaint/selections_opaque_transparent.png create mode 100644 po/pt_BR/docs/kolourpaint/spraycan_patterns.png create mode 100644 po/pt_BR/docs/kolourpaint/text_zoom_grid.png create mode 100644 po/pt_BR/docs/kolourpaint/tool_brush.png create mode 100644 po/pt_BR/docs/kolourpaint/tool_color_picker.png create mode 100644 po/pt_BR/docs/kolourpaint/tool_color_washer.png create mode 100644 po/pt_BR/docs/kolourpaint/tool_curve.png create mode 100644 po/pt_BR/docs/kolourpaint/tool_ellipse.png create mode 100644 po/pt_BR/docs/kolourpaint/tool_elliptical_selection.png create mode 100644 po/pt_BR/docs/kolourpaint/tool_eraser.png create mode 100644 po/pt_BR/docs/kolourpaint/tool_flood_fill.png create mode 100644 po/pt_BR/docs/kolourpaint/tool_free_form_selection.png create mode 100644 po/pt_BR/docs/kolourpaint/tool_line.png create mode 100644 po/pt_BR/docs/kolourpaint/tool_pen.png create mode 100644 po/pt_BR/docs/kolourpaint/tool_polygon.png create mode 100644 po/pt_BR/docs/kolourpaint/tool_polyline.png create mode 100644 po/pt_BR/docs/kolourpaint/tool_polystar.png create mode 100644 po/pt_BR/docs/kolourpaint/tool_rect_selection.png create mode 100644 po/pt_BR/docs/kolourpaint/tool_rectangle.png create mode 100644 po/pt_BR/docs/kolourpaint/tool_rectangles.png create mode 100644 po/pt_BR/docs/kolourpaint/tool_rounded_rectangle.png create mode 100644 po/pt_BR/docs/kolourpaint/tool_selections.png create mode 100644 po/pt_BR/docs/kolourpaint/tool_spraycan.png create mode 100644 po/pt_BR/docs/kolourpaint/tool_text.png create mode 100644 po/pt_BR/docs/kolourpaint/view_thumbnails.png create mode 100644 po/pt_BR/kolourpaint.po create mode 100644 po/ro/kolourpaint.po create mode 100644 po/ru/docs/kolourpaint/KolourPaint.png create mode 100644 po/ru/docs/kolourpaint/image_balance.png create mode 100644 po/ru/docs/kolourpaint/image_emboss.png create mode 100644 po/ru/docs/kolourpaint/image_flatten.png create mode 100644 po/ru/docs/kolourpaint/image_invert.png create mode 100644 po/ru/docs/kolourpaint/image_reduce_colors.png create mode 100644 po/ru/docs/kolourpaint/image_resize_scale.png create mode 100644 po/ru/docs/kolourpaint/image_rotate.png create mode 100644 po/ru/docs/kolourpaint/image_skew.png create mode 100644 po/ru/docs/kolourpaint/image_soften_sharpen.png create mode 100644 po/ru/docs/kolourpaint/index.docbook create mode 100644 po/ru/docs/kolourpaint/screenshot_acquiring.png create mode 100644 po/ru/docs/kolourpaint/view_thumbnails.png create mode 100644 po/ru/kolourpaint.po create mode 100644 po/se/kolourpaint.po create mode 100644 po/sk/kolourpaint.po create mode 100644 po/sl/kolourpaint.po create mode 100644 po/sq/kolourpaint.po create mode 100644 po/sv/docs/kolourpaint/image_balance.png create mode 100644 po/sv/docs/kolourpaint/image_emboss.png create mode 100644 po/sv/docs/kolourpaint/image_flatten.png create mode 100644 po/sv/docs/kolourpaint/image_flip.png create mode 100644 po/sv/docs/kolourpaint/image_invert.png create mode 100644 po/sv/docs/kolourpaint/image_reduce_colors.png create mode 100644 po/sv/docs/kolourpaint/image_resize_scale.png create mode 100644 po/sv/docs/kolourpaint/image_rotate.png create mode 100644 po/sv/docs/kolourpaint/image_skew.png create mode 100644 po/sv/docs/kolourpaint/image_soften_sharpen.png create mode 100644 po/sv/docs/kolourpaint/index.docbook create mode 100644 po/sv/docs/kolourpaint/view_thumbnails.png create mode 100644 po/sv/kolourpaint.po create mode 100644 po/ta/kolourpaint.po create mode 100644 po/tg/kolourpaint.po create mode 100644 po/tr/kolourpaint.po create mode 100644 po/ug/kolourpaint.po create mode 100644 po/uk/docs/kolourpaint/KolourPaint.png create mode 100644 po/uk/docs/kolourpaint/image_balance.png create mode 100644 po/uk/docs/kolourpaint/image_emboss.png create mode 100644 po/uk/docs/kolourpaint/image_flatten.png create mode 100644 po/uk/docs/kolourpaint/image_invert.png create mode 100644 po/uk/docs/kolourpaint/image_reduce_colors.png create mode 100644 po/uk/docs/kolourpaint/image_resize_scale.png create mode 100644 po/uk/docs/kolourpaint/image_rotate.png create mode 100644 po/uk/docs/kolourpaint/image_skew.png create mode 100644 po/uk/docs/kolourpaint/image_soften_sharpen.png create mode 100644 po/uk/docs/kolourpaint/index.docbook create mode 100644 po/uk/docs/kolourpaint/screenshot_acquiring.png create mode 100644 po/uk/kolourpaint.po create mode 100644 po/zh_CN/kolourpaint.po create mode 100644 po/zh_TW/kolourpaint.po create mode 100644 scan/sanedialog.cpp create mode 100644 scan/sanedialog.h create mode 100644 tests/45deg_line.png create mode 100644 tests/4x4-transparent.png create mode 100644 tests/5x5.png create mode 100644 tests/depth1.bmp create mode 100644 tests/dither.png create mode 100644 tests/freeform-selection.png create mode 100644 tests/kolourcircles.png create mode 100644 tests/paste_in_new_window.png create mode 100644 tests/paste_in_new_window.txt create mode 100644 tests/rotate.png create mode 100644 tests/selections.txt create mode 100644 tests/small16x16.png create mode 100644 tests/tool_fill_xlimit.png create mode 100644 tests/transforms-rotate-me-90-clockwise.png create mode 100644 tests/transforms.png create mode 100644 tests/transparent.png create mode 100644 tests/transparent_selection.png create mode 100644 tools/flow/kpToolBrush.cpp create mode 100644 tools/flow/kpToolBrush.h create mode 100644 tools/flow/kpToolColorEraser.cpp create mode 100644 tools/flow/kpToolColorEraser.h create mode 100644 tools/flow/kpToolEraser.cpp create mode 100644 tools/flow/kpToolEraser.h create mode 100644 tools/flow/kpToolFlowBase.cpp create mode 100644 tools/flow/kpToolFlowBase.h create mode 100644 tools/flow/kpToolFlowPixmapBase.cpp create mode 100644 tools/flow/kpToolFlowPixmapBase.h create mode 100644 tools/flow/kpToolPen.cpp create mode 100644 tools/flow/kpToolPen.h create mode 100644 tools/flow/kpToolSpraycan.cpp create mode 100644 tools/flow/kpToolSpraycan.h create mode 100644 tools/kpTool.cpp create mode 100644 tools/kpTool.h create mode 100644 tools/kpToolAction.cpp create mode 100644 tools/kpToolAction.h create mode 100644 tools/kpToolColorPicker.cpp create mode 100644 tools/kpToolColorPicker.h create mode 100644 tools/kpToolFloodFill.cpp create mode 100644 tools/kpToolFloodFill.h create mode 100644 tools/kpToolPrivate.h create mode 100644 tools/kpToolZoom.cpp create mode 100644 tools/kpToolZoom.h create mode 100644 tools/kpTool_Drawing.cpp create mode 100644 tools/kpTool_KeyboardEvents.cpp create mode 100644 tools/kpTool_MouseEvents.cpp create mode 100644 tools/kpTool_OtherEvents.cpp create mode 100644 tools/kpTool_UserNotifications.cpp create mode 100644 tools/kpTool_Utilities.cpp create mode 100644 tools/polygonal/kpToolCurve.cpp create mode 100644 tools/polygonal/kpToolCurve.h create mode 100644 tools/polygonal/kpToolLine.cpp create mode 100644 tools/polygonal/kpToolLine.h create mode 100644 tools/polygonal/kpToolPolygon.cpp create mode 100644 tools/polygonal/kpToolPolygon.h create mode 100644 tools/polygonal/kpToolPolygonalBase.cpp create mode 100644 tools/polygonal/kpToolPolygonalBase.h create mode 100644 tools/polygonal/kpToolPolyline.cpp create mode 100644 tools/polygonal/kpToolPolyline.h create mode 100644 tools/rectangular/kpToolEllipse.cpp create mode 100644 tools/rectangular/kpToolEllipse.h create mode 100644 tools/rectangular/kpToolRectangle.cpp create mode 100644 tools/rectangular/kpToolRectangle.h create mode 100644 tools/rectangular/kpToolRectangularBase.cpp create mode 100644 tools/rectangular/kpToolRectangularBase.h create mode 100644 tools/rectangular/kpToolRoundedRectangle.cpp create mode 100644 tools/rectangular/kpToolRoundedRectangle.h create mode 100644 tools/selection/image/kpAbstractImageSelectionTool.cpp create mode 100644 tools/selection/image/kpAbstractImageSelectionTool.h create mode 100644 tools/selection/image/kpAbstractImageSelectionTool_Transparency.cpp create mode 100644 tools/selection/image/kpToolEllipticalSelection.cpp create mode 100644 tools/selection/image/kpToolEllipticalSelection.h create mode 100644 tools/selection/image/kpToolFreeFormSelection.cpp create mode 100644 tools/selection/image/kpToolFreeFormSelection.h create mode 100644 tools/selection/image/kpToolRectSelection.cpp create mode 100644 tools/selection/image/kpToolRectSelection.h create mode 100644 tools/selection/kpAbstractSelectionTool.cpp create mode 100644 tools/selection/kpAbstractSelectionTool.h create mode 100644 tools/selection/kpAbstractSelectionToolPrivate.h create mode 100644 tools/selection/kpAbstractSelectionTool_Create.cpp create mode 100644 tools/selection/kpAbstractSelectionTool_KeyboardEvents.cpp create mode 100644 tools/selection/kpAbstractSelectionTool_Move.cpp create mode 100644 tools/selection/kpAbstractSelectionTool_ResizeScale.cpp create mode 100644 tools/selection/text/kpToolText.cpp create mode 100644 tools/selection/text/kpToolText.h create mode 100644 tools/selection/text/kpToolTextPrivate.h create mode 100644 tools/selection/text/kpToolText_Commands.cpp create mode 100644 tools/selection/text/kpToolText_Create.cpp create mode 100644 tools/selection/text/kpToolText_CursorCalc.cpp create mode 100644 tools/selection/text/kpToolText_InputMethodEvents.cpp create mode 100644 tools/selection/text/kpToolText_KeyboardEvents.cpp create mode 100644 tools/selection/text/kpToolText_KeyboardEvents_HandleArrowKeys.cpp create mode 100644 tools/selection/text/kpToolText_KeyboardEvents_HandleTypingKeys.cpp create mode 100644 tools/selection/text/kpToolText_Move.cpp create mode 100644 tools/selection/text/kpToolText_ResizeScale.cpp create mode 100644 tools/selection/text/kpToolText_SelectText.cpp create mode 100644 tools/selection/text/kpToolText_TextStyle.cpp create mode 100644 views/kpThumbnailView.cpp create mode 100644 views/kpThumbnailView.h create mode 100644 views/kpUnzoomedThumbnailView.cpp create mode 100644 views/kpUnzoomedThumbnailView.h create mode 100644 views/kpView.cpp create mode 100644 views/kpView.h create mode 100644 views/kpViewPrivate.h create mode 100644 views/kpView_Events.cpp create mode 100644 views/kpView_Paint.cpp create mode 100644 views/kpView_Selections.cpp create mode 100644 views/kpZoomedThumbnailView.cpp create mode 100644 views/kpZoomedThumbnailView.h create mode 100644 views/kpZoomedView.cpp create mode 100644 views/kpZoomedView.h create mode 100644 views/manager/kpViewManager.cpp create mode 100644 views/manager/kpViewManager.h create mode 100644 views/manager/kpViewManagerPrivate.h create mode 100644 views/manager/kpViewManager_TextCursor.cpp create mode 100644 views/manager/kpViewManager_ViewUpdates.cpp create mode 100644 widgets/colorSimilarity/kpColorSimilarityCubeRenderer.cpp create mode 100644 widgets/colorSimilarity/kpColorSimilarityCubeRenderer.h create mode 100644 widgets/colorSimilarity/kpColorSimilarityFrame.cpp create mode 100644 widgets/colorSimilarity/kpColorSimilarityFrame.h create mode 100644 widgets/colorSimilarity/kpColorSimilarityHolder.cpp create mode 100644 widgets/colorSimilarity/kpColorSimilarityHolder.h create mode 100644 widgets/colorSimilarity/kpColorSimilarityToolBarItem.cpp create mode 100644 widgets/colorSimilarity/kpColorSimilarityToolBarItem.h create mode 100644 widgets/imagelib/effects/kpEffectBalanceWidget.cpp create mode 100644 widgets/imagelib/effects/kpEffectBalanceWidget.h create mode 100644 widgets/imagelib/effects/kpEffectBlurSharpenWidget.cpp create mode 100644 widgets/imagelib/effects/kpEffectBlurSharpenWidget.h create mode 100644 widgets/imagelib/effects/kpEffectEmbossWidget.cpp create mode 100644 widgets/imagelib/effects/kpEffectEmbossWidget.h create mode 100644 widgets/imagelib/effects/kpEffectFlattenWidget.cpp create mode 100644 widgets/imagelib/effects/kpEffectFlattenWidget.h create mode 100644 widgets/imagelib/effects/kpEffectHSVWidget.cpp create mode 100644 widgets/imagelib/effects/kpEffectHSVWidget.h create mode 100644 widgets/imagelib/effects/kpEffectInvertWidget.cpp create mode 100644 widgets/imagelib/effects/kpEffectInvertWidget.h create mode 100644 widgets/imagelib/effects/kpEffectReduceColorsWidget.cpp create mode 100644 widgets/imagelib/effects/kpEffectReduceColorsWidget.h create mode 100644 widgets/imagelib/effects/kpEffectToneEnhanceWidget.cpp create mode 100644 widgets/imagelib/effects/kpEffectToneEnhanceWidget.h create mode 100644 widgets/imagelib/effects/kpEffectWidgetBase.cpp create mode 100644 widgets/imagelib/effects/kpEffectWidgetBase.h create mode 100644 widgets/imagelib/effects/kpNumInput.cpp create mode 100644 widgets/imagelib/effects/kpNumInput.h create mode 100644 widgets/kpColorCells.cpp create mode 100644 widgets/kpColorCells.h create mode 100644 widgets/kpColorPalette.cpp create mode 100644 widgets/kpColorPalette.h create mode 100644 widgets/kpDefaultColorCollection.cpp create mode 100644 widgets/kpDefaultColorCollection.h create mode 100644 widgets/kpDocumentSaveOptionsWidget.cpp create mode 100644 widgets/kpDocumentSaveOptionsWidget.h create mode 100644 widgets/kpDualColorButton.cpp create mode 100644 widgets/kpDualColorButton.h create mode 100644 widgets/kpPrintDialogPage.cpp create mode 100644 widgets/kpPrintDialogPage.h create mode 100644 widgets/kpTransparentColorCell.cpp create mode 100644 widgets/kpTransparentColorCell.h create mode 100644 widgets/toolbars/kpColorToolBar.cpp create mode 100644 widgets/toolbars/kpColorToolBar.h create mode 100644 widgets/toolbars/kpToolToolBar.cpp create mode 100644 widgets/toolbars/kpToolToolBar.h create mode 100644 widgets/toolbars/options/kpToolWidgetBase.cpp create mode 100644 widgets/toolbars/options/kpToolWidgetBase.h create mode 100644 widgets/toolbars/options/kpToolWidgetBrush.cpp create mode 100644 widgets/toolbars/options/kpToolWidgetBrush.h create mode 100644 widgets/toolbars/options/kpToolWidgetEraserSize.cpp create mode 100644 widgets/toolbars/options/kpToolWidgetEraserSize.h create mode 100644 widgets/toolbars/options/kpToolWidgetFillStyle.cpp create mode 100644 widgets/toolbars/options/kpToolWidgetFillStyle.h create mode 100644 widgets/toolbars/options/kpToolWidgetLineWidth.cpp create mode 100644 widgets/toolbars/options/kpToolWidgetLineWidth.h create mode 100644 widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.cpp create mode 100644 widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.h create mode 100644 widgets/toolbars/options/kpToolWidgetSpraycanSize.cpp create mode 100644 widgets/toolbars/options/kpToolWidgetSpraycanSize.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e24c81 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Ignore the following files +*~ +*.[oa] +*.diff +*.kate-swp +*.kdev4 +.kdev_include_paths +*.kdevelop.pcs +*.moc +*.moc.cpp +*.orig +*.user +.*.swp +.swp.* +Doxyfile +Makefile +avail +random_seed +/build*/ +CMakeLists.txt.user* +*.unc-backup* +.cmake/ + +# LSP & IDE +/.clang-format +/compile_commands.json +.clangd +.cache +.idea +/cmake-build* diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..2eb710e --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: None +# SPDX-License-Identifier: CC0-1.0 + +include: + - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml + - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml + - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml diff --git a/.kde-ci.yml b/.kde-ci.yml new file mode 100644 index 0000000..9b582da --- /dev/null +++ b/.kde-ci.yml @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: None +# SPDX-License-Identifier: CC0-1.0 + +Dependencies: +- 'on': ['@all'] + 'require': + 'frameworks/extra-cmake-modules': '@stable' + 'frameworks/kdoctools': '@stable' + 'frameworks/ki18n': '@stable' + 'frameworks/kguiaddons': '@stable' + 'frameworks/kwidgetsaddons': '@stable' + 'frameworks/kio': '@stable' + 'frameworks/kxmlgui': '@stable' + 'frameworks/ktextwidgets': '@stable' + 'frameworks/kjobwidgets': '@stable' diff --git a/.krazy b/.krazy new file mode 100644 index 0000000..d850d7b --- /dev/null +++ b/.krazy @@ -0,0 +1 @@ +EXTRA multiclasses diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..9822db5 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,38 @@ + +Authors +======= + +Clarence Dang +Project Founder + +Thurston Dang +Chief Investigator + +Kristof Borrey +Icons + +Kazuki Ohta +InputMethod Support + +Nuno Pinheiro +Icons + +Danny Allen +Icons + +Mike Gashler +Imaqe Effects + +Laurent Montel +KDE 4 Porting + +Martin Koller +Scanning Support, Current Maintainer + +Tasuku Suzuki +InputMethod Support + +Thanks To +========= + +Thanks to the many others who have helped to make this program possible. diff --git a/BUGS b/BUGS new file mode 100644 index 0000000..93c622f --- /dev/null +++ b/BUGS @@ -0,0 +1,138 @@ + +Please send bug reports and feature requests to http://bugs.kde.org/. +Don't hesitate to report bugs nor hesitate to send us your wishes - it +provides valuable feedback that will help to improve future versions of +KolourPaint and you will not receive flames for reporting duplicates. + + +This file lists known bugs in this version that are not considered +"release critical" and are difficult to fix: + + +3. Tool Box & Colour Box RMB ToolBar Menus do not work. + +4. Image dialog spinboxes should accept Enter Key (instead of the dialog's + OK button) after the user has typed something. + + OR + + Spinboxes should signal that their values have changed every time the + user changes the text (rather than after pressing Enter or clicking on + another spinbox etc.). + + The need for the "Update Preview" button and the difficulty of keeping + the percentages and dimensions in sync in the Resize / Scale dialog are + manifestations of the current QSpinBox behaviour. + + Update: Text input is broken in KDE4. + +6. a) The undo history and document modified state are not updated during + the drawing of multi-segment shapes (Polygon, Connected Lines, + Curve). They are however updated after shapes' completion. + + b) The brush-like tools set the document modified flag even if + user cancels the draw operation. + + c) Select a region, manipulate it (e.g. move), undo - the document is + still marked as modified (because 2 commands - the create selection + and the move - were added but only one was undone). + +7. Certain shapes may have the wrong size (usually only a pixel off and + only in extreme cases). This is a Qt bug. + +8. At zoom levels that aren't multiples of 100%, parts of the image may + appear to move when the user interacts with it. Other minor redraw + glitches may also occur at such zoom levels. + +9. Keyboard shortcut changes do not propagate to other KolourPaint windows + (but will propagate to future windows). + +10. "File/Open Recent" entries are not updated interprocess. + +11. The blinking text cursor will "disappear" if you type more text than + you can fit in a text box. + +12. You cannot select only parts of the text you write. + +14. The text cursor may be momentarily misrendered when scrolling the view. + +17. a) Using KolourPaint on a remote X display may result in redraw errors + and pixel data corruption. + +19. Read support for EPS files is extremely slow. You should not enable + the "Save Preview" dialog when saving to EPS. This is an issue with + KDE. + +20. Pasting a large image (esp. one that doesn't compress well as PNG) + into an image editor (not necessarily KolourPaint) running as + different process from the KolourPaint which was the source of the + image, on a sufficiently slow computer, may fail with the following + output to STDERR: + + "kolourpaint: ERROR: kpMainWindow::paste() with sel without pixmap + QClipboard: timed out while sending data" + + This is a Qt bug. + +21. It is not always possible to copy and paste between 2 instances of + KolourPaint running different Qt versions. See + QDataStream::setVersion(). + +23. Changing tool options while in the middle of a drawing option should + work but confuses KolourPaint instead. For instance: + + a) With the brush tools, the cursor incorrectly appears. + + b) With the rectangle-based tools, the temporary pixmap does not resize + when the line width increases. + +25. Sometimes when you take a screenshot of a window, and then paste in a + new window, it will be greyscale. When pasting again, it will still be + greyscale. Cannot consistently reproduce. [Thurston] + +26. Drawing with the keyboard is unreliable. Depending on the X server, + either holding down Enter may continually switch between drawing and + not drawing or KolourPaint may fail to detect the release of the Enter + key. + +27. InputMethod had not been tested at zoom levels other than 100%. + +28. KolourPaint has not been tested against invalid or malicious clipboard + data. + +29. The Tool Box and Color Tool Bar are no longer movable or floatable. + +30. The "Skew", "Rotate" and "Smooth Scale" effects produce low quality + results. + +31. The rendering quality of a text box with opaque text but a see-through + background, on top of transparent document areas, is lower than in KDE 3 + versions of KolourPaint. + + +Issue with XFree86 <= 3.3.6 with the "Emulate3Buttons" Option +============================================================= + +When drawing, clicking the left or right mouse button that did not +initiate the current operation will, in this order: + +1. finalise the current drawing operation +2. attempt to paste the contents of the middle-mouse-button clipboard + +instead of canceling the current drawing operation. + +This is due to XFree86 sending a release notification for the button that +initiated the drawing operation, followed by a press notification for the +emulated 3rd button; instead of just a single press notification for the +button that is intended to cancel the operation. This works correctly in +XFree86 4.x with "Emulate3Buttons" on because it is harder to trigger the +emulation for the 3rd button as it is only invoked if the left and right +buttons are pressed at almost the same time. + +Possible solutions: + +a) Use XFree86 4.x or an X server from another vendor (e.g. X.org). +b) Press Escape in KolourPaint to cancel the current drawing operation + instead of using the problematic click method described above. +c) Disable "Emulate3Buttons". + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0242a2b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,365 @@ +# KDE Application Version, managed by release script +set(RELEASE_SERVICE_VERSION_MAJOR "22") +set(RELEASE_SERVICE_VERSION_MINOR "08") +set(RELEASE_SERVICE_VERSION_MICRO "3") +set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}") + +cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) + +project(kolourpaint VERSION ${RELEASE_SERVICE_VERSION}) + +set(QT_MIN_VERSION "5.11.0") +set(KF5_MIN_VERSION "5.87.0") +set(KDE_COMPILERSETTINGS_LEVEL "5.84.0") + +find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) +set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) + +add_definitions(-DTRANSLATION_DOMAIN="kolourpaint") + +include(KDEInstallDirs) +include(KDECompilerSettings NO_POLICY_SCOPE) +include(KDECMakeSettings) +include(ECMInstallIcons) +include(ECMAddAppIcon) +include(ECMSetupVersion) +include(FeatureSummary) + +find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS + Core + Widgets + PrintSupport +) + +find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS + DocTools + I18n + GuiAddons + WidgetsAddons + KIO + XmlGui + TextWidgets + JobWidgets +) + +add_definitions(-DQT_USE_QSTRINGBUILDER) + +find_package(KF5Sane "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}") + +if(KF5Sane_FOUND) + add_definitions(-DHAVE_KSANE=1) + set(KSANE_LIBRARIES KF5::Sane) +else(KF5Sane_FOUND) + add_definitions(-DHAVE_KSANE=0) +endif(KF5Sane_FOUND) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} +) + + + +## Generate header with version number +ecm_setup_version(${RELEASE_SERVICE_VERSION} + VARIABLE_PREFIX KOLOURPAINT + VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kpVersion.h" +) + + +add_subdirectory( pics ) +add_subdirectory( doc ) + + +########### next target ############### +macro(CREATE_LICENSE _in_FILE _out_FILE) + FILE(READ ${_in_FILE} _contents) + FILE(WRITE ${_out_FILE} "static const char * const kpLicenseText =") + STRING(REGEX REPLACE "\"" "\\\\\"" _contents "${_contents}" ) + STRING(REGEX REPLACE "\n" "\\\\n\"\n\"" _contents "${_contents}" ) + FILE(APPEND ${_out_FILE} "\"${_contents}\"") + FILE(APPEND ${_out_FILE} ";\n") +endmacro(CREATE_LICENSE) + +#macro_additional_clean_files( ${CMAKE_CURRENT_BINARY_DIR}/kolourpaintlicense.h ) + +create_license(${CMAKE_CURRENT_SOURCE_DIR}/COPYING ${CMAKE_CURRENT_BINARY_DIR}/kolourpaintlicense.h) + + +# GENERATED BY ./gen_cmake_srcs | fgrep -v /lgpl/ + +set(kolourpaint_lib1_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectBalanceCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectBlurSharpenCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectClearCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectCommandBase.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectEmbossCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectFlattenCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectGrayscaleCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectHSVCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectInvertCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectReduceColorsCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/effects/kpEffectToneEnhanceCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/kpDocumentMetaInfoCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/transforms/kpTransformFlipCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/transforms/kpTransformRotateCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/imagelib/transforms/kpTransformSkewCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/kpCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/kpCommandHistoryBase.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/kpCommandHistory.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/kpCommandSize.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/kpMacroCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/kpNamedCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/flow/kpToolFlowCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/kpToolColorPickerCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/kpToolFloodFillCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/polygonal/kpToolPolygonalCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/rectangular/kpToolRectangularCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/kpAbstractSelectionContentCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/kpToolImageSelectionTransparencyCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/kpToolSelectionCreateCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/kpToolSelectionDestroyCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/kpToolSelectionMoveCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/kpToolSelectionPullFromDocumentCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/kpToolSelectionResizeScaleCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/text/kpToolTextBackspaceCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/text/kpToolTextChangeStyleCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/text/kpToolTextDeleteCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/text/kpToolTextEnterCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/text/kpToolTextGiveContentCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/commands/tools/selection/text/kpToolTextInsertCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cursors/kpCursorLightCross.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cursors/kpCursorProvider.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dialogs/imagelib/effects/kpEffectsDialog.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dialogs/imagelib/kpDocumentMetaInfoDialog.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dialogs/imagelib/transforms/kpTransformPreviewDialog.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dialogs/imagelib/transforms/kpTransformRotateDialog.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dialogs/imagelib/transforms/kpTransformSkewDialog.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dialogs/kpColorSimilarityDialog.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dialogs/kpDocumentSaveOptionsPreviewDialog.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/document/kpDocument.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/document/kpDocument_Open.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/document/kpDocument_Save.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/document/kpDocumentSaveOptions.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/document/kpDocument_Selection.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/environments/commands/kpCommandEnvironment.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/environments/document/kpDocumentEnvironment.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/environments/kpEnvironmentBase.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/environments/tools/kpToolEnvironment.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/environments/tools/selection/kpToolSelectionEnvironment.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/generic/kpSetOverrideCursorSaver.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/generic/kpWidgetMapper.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/generic/widgets/kpResizeSignallingLabel.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/generic/widgets/kpSubWindow.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/blitz.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectBalance.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectBlurSharpen.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectEmboss.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectFlatten.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectGrayscale.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectHSV.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectInvert.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectReduceColors.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/imagelib/effects/kpEffectToneEnhance.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/imagelib/kpColor_Constants.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/imagelib/kpColor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/imagelib/kpDocumentMetaInfo.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/imagelib/kpFloodFill.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/imagelib/kpPainter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/imagelib/transforms/kpTransformAutoCrop.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/imagelib/transforms/kpTransformCrop.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/imagelib/transforms/kpTransformCrop_ImageSelection.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/imagelib/transforms/kpTransformCrop_TextSelection.cpp +) # kolourpaint_lib1_SRCS + +set(kolourpaint_lib2_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/kpLogCategories.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/kolourpaint.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/kpThumbnail.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/kpViewScrollableContainer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/image/kpAbstractImageSelection.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/image/kpEllipticalImageSelection.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/image/kpFreeFormImageSelection.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/image/kpImageSelectionTransparency.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/image/kpRectangularImageSelection.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/kpAbstractSelection.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/kpSelectionDrag.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/kpSelectionFactory.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/text/kpTextSelection.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/text/kpTextSelection_Cursor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/text/kpTextSelection_Paint.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/text/kpTextStyle.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/layers/selections/text/kpPreeditText.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/layers/tempImage/kpTempImage.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_Colors.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_Edit.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_File.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_Image.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_Settings.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_StatusBar.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_Text.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_Tools.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_View.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_View_Thumbnail.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mainWindow/kpMainWindow_View_Zoom.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/pixmapfx/kpPixmapFX_DrawShapes.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/pixmapfx/kpPixmapFX_GetSetPixmapParts.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/pixmapfx/kpPixmapFX_Transforms.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/flow/kpToolBrush.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/flow/kpToolColorEraser.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/flow/kpToolEraser.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/flow/kpToolFlowBase.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/flow/kpToolFlowPixmapBase.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/flow/kpToolPen.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/flow/kpToolSpraycan.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/kpToolAction.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/kpToolColorPicker.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/kpTool.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/kpTool_Drawing.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/kpToolFloodFill.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/kpTool_KeyboardEvents.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/kpTool_MouseEvents.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/kpTool_OtherEvents.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/kpTool_UserNotifications.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/kpTool_Utilities.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/kpToolZoom.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/polygonal/kpToolCurve.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/polygonal/kpToolLine.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/polygonal/kpToolPolygonalBase.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/polygonal/kpToolPolygon.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/polygonal/kpToolPolyline.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/rectangular/kpToolEllipse.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/rectangular/kpToolRectangle.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/rectangular/kpToolRectangularBase.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/rectangular/kpToolRoundedRectangle.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/image/kpAbstractImageSelectionTool.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/image/kpAbstractImageSelectionTool_Transparency.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/image/kpToolEllipticalSelection.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/image/kpToolFreeFormSelection.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/image/kpToolRectSelection.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/kpAbstractSelectionTool.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/kpAbstractSelectionTool_Create.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/kpAbstractSelectionTool_KeyboardEvents.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/kpAbstractSelectionTool_Move.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/kpAbstractSelectionTool_ResizeScale.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_Commands.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_Create.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_CursorCalc.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_InputMethodEvents.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_KeyboardEvents.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_KeyboardEvents_HandleArrowKeys.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_KeyboardEvents_HandleTypingKeys.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_Move.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_ResizeScale.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_SelectText.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tools/selection/text/kpToolText_TextStyle.cpp +) # kolourpaint_lib2_SRCS + + +if(KF5Sane_FOUND) + set(kolourpaint_lib2_SRCS + ${kolourpaint_lib2_SRCS} + ${CMAKE_CURRENT_SOURCE_DIR}/scan/sanedialog.cpp + ) +endif(KF5Sane_FOUND) + +set(kolourpaint_app_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/views/kpThumbnailView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/views/kpUnzoomedThumbnailView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/views/kpView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/views/kpView_Events.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/views/kpView_Paint.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/views/kpView_Selections.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/views/kpZoomedThumbnailView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/views/kpZoomedView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/views/manager/kpViewManager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/views/manager/kpViewManager_TextCursor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/views/manager/kpViewManager_ViewUpdates.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/colorSimilarity/kpColorSimilarityCubeRenderer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/colorSimilarity/kpColorSimilarityFrame.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/colorSimilarity/kpColorSimilarityHolder.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/colorSimilarity/kpColorSimilarityToolBarItem.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpNumInput.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectBalanceWidget.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectBlurSharpenWidget.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectEmbossWidget.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectFlattenWidget.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectHSVWidget.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectInvertWidget.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectReduceColorsWidget.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectToneEnhanceWidget.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/imagelib/effects/kpEffectWidgetBase.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/kpColorCells.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/kpColorPalette.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/kpDefaultColorCollection.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/kpDocumentSaveOptionsWidget.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/kpDualColorButton.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/kpPrintDialogPage.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/kpTransparentColorCell.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/kpColorToolBar.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/kpToolToolBar.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/options/kpToolWidgetBase.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/options/kpToolWidgetBrush.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/options/kpToolWidgetEraserSize.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/options/kpToolWidgetFillStyle.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/options/kpToolWidgetLineWidth.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgets/toolbars/options/kpToolWidgetSpraycanSize.cpp +) # set(kolourpaint_app_SRCS + + +set(kolourpaint_SRCS + ${kolourpaint_lib1_SRCS} + ${kolourpaint_lib2_SRCS} + ${kolourpaint_app_SRCS} + kolourpaint.qrc +) + +add_subdirectory(lgpl) + +# +# Executable +# + +ecm_add_app_icon(kolourpaint_SRCS ICONS + pics/app/16-apps-kolourpaint.png + pics/app/22-apps-kolourpaint.png + pics/app/32-apps-kolourpaint.png + pics/app/48-apps-kolourpaint.png +) + +add_executable(kolourpaint ${kolourpaint_SRCS}) + +target_link_libraries(kolourpaint + KF5::XmlGui + KF5::KIOFileWidgets + KF5::TextWidgets + Qt${QT_MAJOR_VERSION}::PrintSupport + ${KSANE_LIBRARIES} + kolourpaint_lgpl +) + +if(KSANE_FOUND) + target_link_libraries(kolourpaint + ${KSANE_LIBRARY} + ) +endif(KSANE_FOUND) + + +install(TARGETS kolourpaint ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) + + +########### install files ############### + +install(PROGRAMS org.kde.kolourpaint.desktop DESTINATION ${KDE_INSTALL_APPDIR}) +install(FILES org.kde.kolourpaint.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) +install(FILES kolourpaintui.rc DESTINATION ${KDE_INSTALL_KXMLGUIDIR}/kolourpaint) + +ki18n_install(po) +kdoctools_install(po) + +feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..6b56fcf --- /dev/null +++ b/COPYING @@ -0,0 +1,139 @@ +Copyright (c) 2003-2007 Clarence Dang +Portions Copyright (c) 2005 Kazuki Ohta +Portions Copyright (c) 2006-2007 Mike Gashler +Portions Copyright (c) 2007,2011 Martin Koller +Portions Copyright (c) 2007 John Layt +Portions Copyright (c) 2010 Tasuku Suzuki +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +"libkolourpaint" Library +~~~~~~~~~~~~~~~~~~~~~~~~ + +The "libkolourpaint" dynamic link library contains various pieces of code, +each with a different license: + + +License 1 +--------- + +Copyright (C) 1999 Waldo Bastian (bastian@kde.org) +Copyright (C) 2007 Clarence Dang (dang@kde.org) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public License +along with this library; see the file COPYING.LIB. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. + + +License 2 +--------- + +Copyright (C) 2007 David Faure + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public License +along with this library; see the file COPYING.LIB. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. + + +License 3 +--------- + +Copyright (c) 2003-2007 Clarence Dang +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +License 4 +--------- + +Copyright (C) 1997 Martin Jones (mjones@kde.org) +Copyright (C) 2007 Roberto Raggi (roberto@kdevelop.org) +Copyright (C) 2007 Clarence Dang (dang@kde.org) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public License +along with this library; see the file COPYING.LIB. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. + + +Icons +~~~~~ + +Copyright (c) Nuno Pinheiro +Copyright (c) Danny Allen +Portions Copyright (c) Clarence Dang +Portions Copyright (c) Kristof Borrey + diff --git a/COPYING.DOC b/COPYING.DOC new file mode 100644 index 0000000..71ec2c4 --- /dev/null +++ b/COPYING.DOC @@ -0,0 +1,397 @@ + GNU Free Documentation License + Version 1.2, November 2002 + + + Copyright (C) 2000,2001,2002 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + +0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document "free" in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of "copyleft", which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + + +1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The "Document", below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as "you". You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A "Modified Version" of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A "Secondary Section" is a named appendix or a front-matter section of +the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall subject +(or to related matters) and contains nothing that could fall directly +within that overall subject. (Thus, if the Document is in part a +textbook of mathematics, a Secondary Section may not explain any +mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The "Invariant Sections" are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, LaTeX input format, SGML +or XML using a publicly available DTD, and standard-conforming simple +HTML, PostScript or PDF designed for human modification. Examples of +transparent image formats include PNG, XCF and JPG. Opaque formats +include proprietary formats that can be read and edited only by +proprietary word processors, SGML or XML for which the DTD and/or +processing tools are not generally available, and the +machine-generated HTML, PostScript or PDF produced by some word +processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, "Title Page" means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +A section "Entitled XYZ" means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as "Acknowledgements", +"Dedications", "Endorsements", or "History".) To "Preserve the Title" +of such a section when you modify the Document means that it remains a +section "Entitled XYZ" according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + + +2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no other +conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + + +3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to give +them a chance to provide you with an updated version of the Document. + + +4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct + from that of the Document, and from those of previous versions + (which should, if there were any, be listed in the History section + of the Document). You may use the same title as a previous version + if the original publisher of that version gives permission. +B. List on the Title Page, as authors, one or more persons or entities + responsible for authorship of the modifications in the Modified + Version, together with at least five of the principal authors of the + Document (all of its principal authors, if it has fewer than five), + unless they release you from this requirement. +C. State on the Title page the name of the publisher of the + Modified Version, as the publisher. +D. Preserve all the copyright notices of the Document. +E. Add an appropriate copyright notice for your modifications + adjacent to the other copyright notices. +F. Include, immediately after the copyright notices, a license notice + giving the public permission to use the Modified Version under the + terms of this License, in the form shown in the Addendum below. +G. Preserve in that license notice the full lists of Invariant Sections + and required Cover Texts given in the Document's license notice. +H. Include an unaltered copy of this License. +I. Preserve the section Entitled "History", Preserve its Title, and add + to it an item stating at least the title, year, new authors, and + publisher of the Modified Version as given on the Title Page. If + there is no section Entitled "History" in the Document, create one + stating the title, year, authors, and publisher of the Document as + given on its Title Page, then add an item describing the Modified + Version as stated in the previous sentence. +J. Preserve the network location, if any, given in the Document for + public access to a Transparent copy of the Document, and likewise + the network locations given in the Document for previous versions + it was based on. These may be placed in the "History" section. + You may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission. +K. For any section Entitled "Acknowledgements" or "Dedications", + Preserve the Title of the section, and preserve in the section all + the substance and tone of each of the contributor acknowledgements + and/or dedications given therein. +L. Preserve all the Invariant Sections of the Document, + unaltered in their text and in their titles. Section numbers + or the equivalent are not considered part of the section titles. +M. Delete any section Entitled "Endorsements". Such a section + may not be included in the Modified Version. +N. Do not retitle any existing section to be Entitled "Endorsements" + or to conflict in title with any Invariant Section. +O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled "Endorsements", provided it contains +nothing but endorsements of your Modified Version by various +parties--for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + + +5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled "History" +in the various original documents, forming one section Entitled +"History"; likewise combine any sections Entitled "Acknowledgements", +and any sections Entitled "Dedications". You must delete all sections +Entitled "Endorsements". + + +6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents +released under this License, and replace the individual copies of this +License in the various documents with a single copy that is included in +the collection, provided that you follow the rules of this License for +verbatim copying of each of the documents in all other respects. + +You may extract a single document from such a collection, and distribute +it individually under this License, provided you insert a copy of this +License into the extracted document, and follow this License in all +other respects regarding verbatim copying of that document. + + +7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an "aggregate" if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + + +8. TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", +"Dedications", or "History", the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + + +9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except +as expressly provided for under this License. Any other attempt to +copy, modify, sublicense or distribute the Document is void, and will +automatically terminate your rights under this License. However, +parties who have received copies, or rights, from you under this +License will not have their licenses terminated so long as such +parties remain in full compliance. + + +10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions +of the GNU Free Documentation License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. See +https://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. + + +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + + Copyright (c) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the "with...Texts." line with this: + + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. diff --git a/COPYING.LIB b/COPYING.LIB new file mode 100644 index 0000000..2676d08 --- /dev/null +++ b/COPYING.LIB @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..569bc33 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,8 @@ + +For logs of _every_ single change made to KolourPaint between any date or +revision, visit: + +https://quickgit.kde.org/?p=kolourpaint.git&a=log + +For a summary of user-visible changes between each release, read NEWS. + diff --git a/Messages.sh b/Messages.sh new file mode 100644 index 0000000..5ac09ab --- /dev/null +++ b/Messages.sh @@ -0,0 +1,3 @@ +#! /bin/sh +$EXTRACTRC `find . -name "*.rc" -o -name "*.ui" ` >> rc.cpp +$XGETTEXT `find . -name "*.cpp" -o -name "*.h"` -o $podir/kolourpaint.pot diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..8a6afb4 --- /dev/null +++ b/NEWS @@ -0,0 +1,386 @@ +KolourPaint - KF5 based + +* Port to KF5 + +KolourPaint 4 Series (trunk/KDE/) +==================== + +KolourPaint 4.0.0 BETA (Frozen 2007-10-26) + + * Port to KDE 4 (Clarence Dang, Laurent Montel) + + Note: This is a beta release and has not been through a full QA review. + If you want stability, use KolourPaint/KDE3, which can be + installed in parallel with KolourPaint4 BETA and runs under KDE4. + + * Configurable Color Palette + + * Add "Hue, Saturation, Value" effect (Mike Gashler) + + * Add "Histogram Equalizer" effect (Mike Gashler) + + * Add Zoom Tool + + * Add "File / Properties..." + + * Rectangles, rounded rectangles and ellipsed are now bounded by + the dimensions of the dragged out rectangle + + * Add, to the print dialog, a choice between printing the image at the + top-left of the page or at the center (this was previously a hidden + configuration option) + (Bug #33481) + + * Add hidden configuration option "Open Images in the Same Window" + (Bug #125116) + + * Add "Rotate Left" (CTRL+SHIFT+Left) and "Rotate Right" (CTRL+SHIFT+R) + to "Image" menu as a quick way to access the common types of "Rotate..." + (Bug #135184, #141530)) + + * Add "Fit to Page", "Fit to Page Width" and "Fit to Page Height" to the + "View" Menu + + * Add "Image / Draw Opaque" menu item since some users expect it. + It duplicates the functionality of the already existent Tool Box widget. + + * Animate the Color Similarity Tool Bar Item, to highlight the existence + of the feature + - And make the configuration more accessible + - Also add "Image / Draw With Color Similarity" to duplicate the tool + bar item + + * Make the Tool Box use as much vertical space as possible, since it + needs it for the option widgets. + + * Save local files atomically - KolourPaint will no longer truncate + an existing file if the KImageIO plugin for the file format is + missing or if you run out of disk space. + [also in branches/KDE/3.5] + + * Add "File / Scan..." feature (Martin Koller) + [also in branches/KDE/3.5/] + + * Fix crash triggered by rapidly deselecting a selection after + drag-scaling the selection (Bug #117866) + [also in branches/KDE/3.[345]/, branches/kolourpaint/1.2_kde3/] + + * Add global session save/restore (Bug #94651) + [also in branches/KDE/3.5/] + + * Make "File / Open Recent" consistently work when multiple windows are + open + [also in branches/KDE/3.5/] + + * CTRL+C'ing a text box also places the text in the middle-mouse-button + clipboard, in lieu of being able to highlight the text to do this + [also in branches/KDE/3.5/] + + * Change minimum allowed zoom level for the grid from 600% to 400% + [also in branches/KDE/3.5/] + + * Printing improvements (Bug #108976) + - Respect image DPI + - Fit image to page if image is too big + - Center image on page + [also in branches/KDE/3.5/] + + * Paste transparent pixels as black instead of uninitialized colors, + when the app does not support pasting transparent pixels (such as + OpenOffice.org) + [also in branches/KDE/3.5/] + + * Make "Edit / Paste in New Window" always paste white pixels as white + (it used to paste them as transparent when the selection transparency + mode was set to Transparent) + [also in branches/KDE/3.5/] + + * REGRESSION: The "Skew", "Rotate" and "Smooth Scale" effects produce + low quality results + (Bug #30) + + * REGRESSION: The rendering quality of a text box with opaque text but + a see-through background, on top of transparent document + areas, is lower than in KDE 3 versions of KolourPaint + (Bug #31) + + * REGRESSION: Spinboxes do not support text input + (see the "Update" part of Bug #4) + + * REGRESSION: InputMethod support was not ported to Qt4 so has been disabled + (Bug #27) + + * REGRESSION: The Tool Box and Color Tool Bar are no longer movable or + floatable + (Bug #29) + + KolourPaint 4.0.0 BETA contains all the fixes and features in KolourPaint + 1.4.8_relight (KDE 3.5.8), even though not all of them were listed + above. + + +KolourPaint 1.4_relight Series (branches/KDE/3.5/) +=============================== + +KolourPaint 1.4.1_relight (Frozen 2006-01-15) + + * Updated documentation (Thurston) + +KolourPaint 1.4_relight (Frozen 2005-11-08) + + * New icons (Danny Allen, Nuno Pinheiro) + + * Tool Box icon size is 22x22, not 16x16, at screen resolution >= 1024x768 + + * CTRL + Mouse Wheel = Zoom + + * While freehand selection scaling, holding Shift maintains aspect ratio + + * Prevent accidental drags in the Colour Palette from pasting text + containing the colour code + [also in branches/KDE/3.[34]/, branches/kolourpaint/1.2_kde3/] + + * Cells in the bottom row and cells in the rightmost column of the Colour + Palette are now the same size as the other cells + [also in branches/KDE/3.[34]/, branches/kolourpaint/1.2_kde3/] + + * Text drops to the empty part of the scrollview will not be placed + outside the document + [also in branches/KDE/3.[34]/, branches/kolourpaint/1.2_kde3/] + + * Rename icons from "hi" to "cr" - back to the state of 1.0 (Danny Allen) + but leave application icons as "hi" (Jonathan Riddell) + + * Enforce text box font height to prevent e.g. Chinese characters in + buggy fonts from enlarging the text box and putting the cursor out of + sync with the text + [also in branches/KDE/3.[34]/, branches/kolourpaint/1.2_kde3/] + + * Clicking in a text box selects a character based on its midpoint - + not leftmost point - to be consistent with all text editors + (esp. noticeable with big fonts) + [also in branches/KDE/3.[34]/, branches/kolourpaint/1.2_kde3/] + + * Return and Numpad 5 Key now draw + [also in branches/KDE/3.[34]/, branches/kolourpaint/1.2_kde3/] + + * Tool Actions placed outside the Tool Box resize with their toolbars + [also in branches/KDE/3.[34]/, branches/kolourpaint/1.2_kde3/] + + * Ensure Color Similarity maximum is 30, not 29 due to gcc4 + [also in branches/KDE/3.[34]/, branches/kolourpaint/1.2_kde3/] + + * Tool Box traps right clicks (for the RMB Menu) on top of tool options + widgets and the empty part of the Tool Box + [also in branches/KDE/3.[34]/, branches/kolourpaint/1.2_kde3/] + + * Correct and update image format associations to all formats supported + by KDE 3.5 (kdelibs/kimgio/:r466654) + + * String fixes (Stefan Winter) + [also in branches/KDE/3.4/] + + * Other string fixes (Malcolm Hunter, Clarence Dang, Stephan Binner) + + +KolourPaint 1.4_light Series (branches/KDE/3.4/) +============================ + +KolourPaint 1.4_light (Frozen 2005-02-22) + * Antialias text when the text box has a transparent background (Bug #24) + [later backported to branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Add Unzoomed Thumbnail Mode and Thumbnail Rectangle + * Add RMB context menu for when a selection tool is active (closing KDE + Bug #92882) + * More intuitive "Set as Image" behaviour (esp. with selection borders). + Thanks to Michael Lake for the feedback. + [later backported to branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * InputMethod support + [later backported to branches/kolourpaint/1.2_kde3/] + * Save "More Effects" dialog's last effect to config file + * Save "Resize / Scale" dialog's last "Keep aspect ratio" setting to + config file + * Add "Help / Acquiring Screenshots" + * Fix selection regressions introduced in 1.2: + - Make selection dragging with CTRL work again (copies selection onto + document) + - When creating freeform selections, include the starting point; also + avoids a QRegion crash with constructing 1-point regions + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Fix other selection bugs: + - When the user drags very quickly on a resize handle, resize the + selection instead of moving it + - Draw resize handles above the grid lines - not below - so that the + handles are always visible if they are supposed to be there + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Smaller selection and text box resize handles (visually not + actually) - covers up fewer selected pixels, doesn't cover up text + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Restore mouse cursor after deselecting selection/text tools + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Empty text clipboard fixes: + - Don't get stuck on a wait cursor after attempting to paste empty + text into a text box + - Prevent pasting text from creating a new text box if text is empty + - Prevent copying of empty text box + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Speed up renderer (most noticeable with diagonal drag-scrolling at + high zoom) + - Don't paint anything outside of the view's visible region + (previously, clipped only on view _widget_ region) + - Region-aware: paint component rectangles of the update region, + rather than the bounding rectangle + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * When changing between colour depth and quality widgets in the save + filedialog, make sure "Convert to:" and "Quality:" are correctly + rendered (hacking around a Qt redraw glitch) + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Fix crash after using the Colour Picker if it was the first used tool + [kolourpaint-1.2.2_kde3-color_picker_crash.diff] + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Fix crash due to text box when scaling image behind it + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Even when the thumbnail has focus (and not the main window), blink the + text cursor in all views + [kolourpaint-1.2.2_kde3-thumbnail_blink_text_cursor.diff] + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Correct "Soften" and "Sharpen" commands' command history names + * Correct invert commands' command history names + * Fix remaining untranslatable strings (closing KDE Bug #85785) + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Update image format associations to all formats supported by KDE 3.4 + * Remove unused images in doc directory + [also in branches/KDE/3.3/, branches/kolourpaint/1.2_kde3/] + * Correct kolourpaint.desktop "Terminal=" and "Categories=" syntax + (Benjamin Meyer) + + +KolourPaint 1.2 Series (branches/KDE/3.3/) +====================== + +Version 1.2 "ByFiat Everytime" (2004-08-18) + * Add up to 500 levels of Undo/Redo (minimum of 10 levels, maximum of + 500 as long as the total history size < 16MB) + * Add freehand resizing of image + * Add freehand smooth scaling of selections + * [also in 1.0 branch] New icons (Kristof Borrey) + * [also in 1.0 branch] Prefer Crystal SVG text icons over KolourPaint's + * [also in 1.0 branch] Add documentation in the KDE Help Centre + * Add drag scrolling + * Add "More Effects" dialog: + - Balance (Brightness, Contrast, Gamma) + - Emboss + - Flatten + - Invert (with choice of channels) + - Reduce Colours + - Soften & Sharpen + * File saving improvements: + - Support colour depths (optional dithering) and "colour monochrome" + - Support JPEG quality + - Realtime file dialog preview with estimated file size + - Retain PNG metadata + - Prompt when attempting lossy save + - Correctly save transparent selections (not as opaque) + * Dither more often when loading (and pasting) images for better quality + * Single key shortcuts for all tools and tool options (automatically + turned off when editing text but can then use Alt+Shift+) + * Arrow keys now move one document pixel - not view pixel - at a time + (more usable when zoomed in) + * Fix selection bugs: + - Fix duplicate "Selection: Create" undo entries (Bug #5a) + - Allow redoing of selection operation if border deselected (Bug #5b) + - Don't print to STDERR when undoing a selection border create + operation and border has already been deselected + - [also in 1.0 branch] When pulling a selection from the document, + only set the bits of the document to the background colour where the + transparent selection is opaque in the same place (this is only + noticeable with colour similarity turned on). Now moving a + selection away and then back to its original place is always a NOP + as it should be. + * Selections can be deselected using Esc or clicking on icon in Tool Box + * Accidental drag detection when deselecting selections or text boxes + * Prevent selection from being moved completely offscreen (at least 1 + pixel of the selection will stay within the view) + * Speed up copying selection when transparency is on + * Improve Text Tool usability: + - Allow single click creation of text box with a sane default size + - Allow freehand resizing of text boxes + - Add Opaque/Transparent selector for greater usability and + consistency with selections + - Minimum size is now 7x7 document pixels (1x1 - not 4x4 - border) + - Text cursor doesn't overlap border anymore + - When dropping text, paste at drop point + - When MMB pasting creates a new text box, do so at mouse position + * When MMB pasting text in an existing box, correctly paste multiline + clipboard contents + * Improve text quality: + - With a transparent background, don't antialias foreground opaque + text with arbitrarily chosen black + - Make sure transparent text shows up on opaque (usually, grey was + problematic) background + * Improve Resize/Scale dialog usability: + - Add Smooth Scale (useful for creating screenshot thumbnails) + - Allow manipulating image when selection is active + - Operation choices stand out as massive, easily clickable buttons + - Default focus on operation choices + * Warn if Resize/Scale, Rotate or Skew will take lots of memory + * Limit startup image size to 2048x2048 + * Eliminate flicker when scrolling + * Thumbnail fixes: + - Reduce flicker when appearing (Bug #2) + - More reasonable minimum size (actually enforce it) + - [also in 1.0 branch] Use deleteLater() + - [also in 1.0 branch] Save geometry even if it's closed very quickly + after a geometry change + * Restore last used tool and tool options on startup + * Add Export, Copy To File, Paste From File, Paste in New Window, + Full Screen Mode + * Add Zoom In/Out buttons to main toolbar + * Rename Crop options in an attempt to reduce confusion: + - "Autocrop" --> "Remove Internal Border" when selection active + - "Crop Outside Selection" --> "Set as Image (Crop)" + * "Set as Image" changes: + - Enable for text boxes + - Underneath transparent bits of selection, fill image with + transparent rather than with background colour + * Permit "reloading" of an empty document + * Fixes when the current URL doesn't exist: + - Don't reload if underlying file disappeared + - Don't add non-existent file to Recent Files history + - Ask to save before mailing or setting as wallpaper + * Only enable Show Path when there is a URL + * Pop up dialog (instead of printing to STDERR) and disable Edit/Paste + on CTRL+V if the clipboard contents disappeared due to the source + application quitting (and Klipper didn't retain clipboard contents) + * Image/Clear now always sets _everything_ within the selection boundary + to the background colour - including transparent pixels + * Add Preview button to Colour Similarity Dialog to work around Bug #4 + regarding spinboxes and enter key + * Colour Picker disallows trying to pick colour outside of image + * Make sure colour palette contains valid and visible colours at 8-bit + * [also in 1.0 branch] Fix (big) memory leak on kpSelection destruction + (Albert Astals Cid) + * Don't leak image dialogs' memory + * [also in 1.0 branch] Don't let C++ destruct the mask bitmap before its + painter when dbl-clicking the color eraser does NOP (avoids + QPaintDevice and X error) + * [also in 1.0 branch] Check for QImageDrag::canDecode() before calling + QImageDrag::decode() (prevents X and valgrind errors) + * [also in 1.0 branch] Fix compilation problem with QT_NO_ASCII_CAST + (Waldo Bastian) + * [also in 1.0 branch] Decrease application preference to below that of + a viewer (Stephan Kulow) + * Remember dialog dimensions + * Remove double dialog margins + * Fix missing i18n()'s + * Fix some untranslatable strings + * [also in 1.0 branch] Corrected several strings + * Remove unused icons + + +KolourPaint 1.0 Series (branches/kolourpaint/1.0/) +====================== + +Version 1.0 "Seagull" (2004-02-29) + * First stable release + diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f51ce8 --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ + +http://www.kolourpaint.org/ + +Copyright (c) 2003-2007 Clarence Dang + + +For licensing and warranty information, read COPYING. +For known problems with this release of KolourPaint, read BUGS. +For what changes have been made, read NEWS. +For developer information, checkout branches/kolourpaint/control/. +For general information, read this file (README): + + +What is KolourPaint? +==================== + +KolourPaint is a free, easy-to-use paint program by KDE. + +It aims to be conceptually simple to understand; providing a level of +functionality targeted towards the average user. It's designed for daily +tasks like: + +* Painting - drawing diagrams and "finger painting" +* Image Manipulation - editing screenshots and photos; applying effects +* Icon Editing - drawing clipart and logos with transparency + +It's not an unusable and monolithic program where simple tasks like drawing +lines become near impossible. Nor is it so simple that it lacks essential +features like Undo/Redo. + +KolourPaint is opensource software written in C++ using the Qt and KDE +libraries. + + +Features +======== + +* Undo/Redo Support (10-500 levels of history depending on memory usage) + +* Tools (single key shortcuts available for all tools) + - Brush, Color Eraser, Color Picker, Connected Lines a.k.a. Polyline + - Curve, Ellipse, Eraser, Flood Fill, Line, Pen, Polygon, Rectangle + - Rounded Rectangle, Spraycan, Text, Zoom + +* Selections (fully undo- and redo-able) + - Rectangular, Elliptical, Free-Form shapes + - Choice between Opaque and Transparent selections + - Full Clipboard/Edit Menu support + - Freehand resizeable + +* Configurable Color Palette + +* Color Similarity means that you can fill regions in dithered images and + photos + +* Transparency + - Draw transparent icons and logos on a checkerboard background + - All tools can draw in the "Transparent Color" + +* Image Effects + - Autocrop / Remove Internal Border + - Balance (Brightness, Contrast, Gamma) + - Clear, Emboss, Flatten, Flip, Histogram Equalizer + - Hue, Saturation, Value + - Invert (with choice of channels) + - Reduce Colors, Reduce to Grayscale, Resize, Rotate + - Scale, Set as Image (Crop), Skew, Smooth Scale, Soften & Sharpen + +* Close-up Editing + - Zoom (from 0.01x to 16x) + - Grid + - Thumbnail + +* File Operations + - Open/Save in all file formats provided by KImageIO + (PNG, JPEG, BMP, ICO, PCX, TIFF,...) with preview + - Print, Print Preview + - Mail + - Set as Wallpaper + + +Updates & More Information +========================== + +Visit: http://www.kolourpaint.org/ + + +Support +======= + +Visit: http://www.kolourpaint.org/ + +If you have any questions about compiling, installing or using KolourPaint, +don't be afraid to contact us. We try to support all versions of +KolourPaint and even issues with 3rd party binary packages. + + +Feedback +======== + +Please send bug reports and feature requests to http://bugs.kde.org/. +Don't hesitate to report bugs nor hesitate to send us your wishes -- it +provides valuable feedback that will help to improve future versions of +KolourPaint and you will not receive flames for reporting duplicates. diff --git a/commands/imagelib/effects/kpEffectBalanceCommand.cpp b/commands/imagelib/effects/kpEffectBalanceCommand.cpp new file mode 100644 index 0000000..4416d34 --- /dev/null +++ b/commands/imagelib/effects/kpEffectBalanceCommand.cpp @@ -0,0 +1,55 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "kpEffectBalanceCommand.h" + +#include "imagelib/effects/kpEffectBalance.h" + +#include + +//-------------------------------------------------------------------------------- + +kpEffectBalanceCommand::kpEffectBalanceCommand (int channels, + int brightness, int contrast, int gamma, + bool actOnSelection, + kpCommandEnvironment *environ) + : kpEffectCommandBase (i18n ("Balance"), actOnSelection, environ), + m_channels (channels), + m_brightness (brightness), m_contrast (contrast), m_gamma (gamma) +{ +} + +kpEffectBalanceCommand::~kpEffectBalanceCommand () = default; + + +// protected virtual [base kpEffectCommandBase] +kpImage kpEffectBalanceCommand::applyEffect (const kpImage &image) +{ + return kpEffectBalance::applyEffect (image, m_channels, + m_brightness, m_contrast, m_gamma); +} + diff --git a/commands/imagelib/effects/kpEffectBalanceCommand.h b/commands/imagelib/effects/kpEffectBalanceCommand.h new file mode 100644 index 0000000..304341e --- /dev/null +++ b/commands/imagelib/effects/kpEffectBalanceCommand.h @@ -0,0 +1,56 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectBalanceCommand_H +#define kpEffectBalanceCommand_H + + +#include "kpEffectCommandBase.h" +#include "imagelib/kpImage.h" + + +class kpEffectBalanceCommand : public kpEffectCommandBase +{ +public: + // (, & are from -50 to 50) + kpEffectBalanceCommand (int channels, + int brightness, int contrast, int gamma, + bool actOnSelection, + kpCommandEnvironment *environ); + ~kpEffectBalanceCommand () override; + +protected: + kpImage applyEffect (const kpImage &image) override; + +protected: + int m_channels; + int m_brightness, m_contrast, m_gamma; +}; + + +#endif // kpEffectBalanceCommand_H diff --git a/commands/imagelib/effects/kpEffectBlurSharpenCommand.cpp b/commands/imagelib/effects/kpEffectBlurSharpenCommand.cpp new file mode 100644 index 0000000..e7f995a --- /dev/null +++ b/commands/imagelib/effects/kpEffectBlurSharpenCommand.cpp @@ -0,0 +1,65 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "kpEffectBlurSharpenCommand.h" + +#include + +//-------------------------------------------------------------------------------- + +kpEffectBlurSharpenCommand::kpEffectBlurSharpenCommand (kpEffectBlurSharpen::Type type, + int strength, + bool actOnSelection, + kpCommandEnvironment *environ) + : kpEffectCommandBase (kpEffectBlurSharpenCommand::nameForType (type), + actOnSelection, environ), + m_type (type), + m_strength (strength) +{ +} + +//-------------------------------------------------------------------------------- + +// public static +QString kpEffectBlurSharpenCommand::nameForType (kpEffectBlurSharpen::Type type) +{ + switch (type) { + case kpEffectBlurSharpen::Blur: return i18n ("Soften"); + case kpEffectBlurSharpen::Sharpen: return i18n ("Sharpen"); + default: return {}; + } +} + +//-------------------------------------------------------------------------------- + +// protected virtual [base kpEffectCommandBase] +kpImage kpEffectBlurSharpenCommand::applyEffect (const kpImage &image) +{ + return kpEffectBlurSharpen::applyEffect (image, m_type, m_strength); +} + diff --git a/commands/imagelib/effects/kpEffectBlurSharpenCommand.h b/commands/imagelib/effects/kpEffectBlurSharpenCommand.h new file mode 100644 index 0000000..428b461 --- /dev/null +++ b/commands/imagelib/effects/kpEffectBlurSharpenCommand.h @@ -0,0 +1,57 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectBlurSharpenCommand_H +#define kpEffectBlurSharpenCommand_H + + +#include "kpEffectCommandBase.h" +#include "imagelib/effects/kpEffectBlurSharpen.h" +#include "imagelib/kpImage.h" + + +class kpEffectBlurSharpenCommand : public kpEffectCommandBase +{ +public: + kpEffectBlurSharpenCommand (kpEffectBlurSharpen::Type type, + int strength, + bool actOnSelection, + kpCommandEnvironment *environ); + + static QString nameForType (kpEffectBlurSharpen::Type type); + +protected: + kpImage applyEffect (const kpImage &image) override; + +protected: + kpEffectBlurSharpen::Type m_type; + int m_strength; +}; + + +#endif // kpEffectBlurSharpenCommand_H diff --git a/commands/imagelib/effects/kpEffectClearCommand.cpp b/commands/imagelib/effects/kpEffectClearCommand.cpp new file mode 100644 index 0000000..9cf7cf0 --- /dev/null +++ b/commands/imagelib/effects/kpEffectClearCommand.cpp @@ -0,0 +1,110 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "kpEffectClearCommand.h" + +#include "layers/selections/image/kpAbstractImageSelection.h" +#include "kpDefs.h" +#include "document/kpDocument.h" + +#include + +//-------------------------------------------------------------------------------- + +kpEffectClearCommand::kpEffectClearCommand (bool actOnSelection, + const kpColor &newColor, + kpCommandEnvironment *environ) + : kpCommand (environ), + m_actOnSelection (actOnSelection), + m_newColor (newColor), + m_oldImagePtr (nullptr) +{ +} + +kpEffectClearCommand::~kpEffectClearCommand () +{ + delete m_oldImagePtr; +} + + +// public virtual [base kpCommand] +QString kpEffectClearCommand::name () const +{ + QString opName = i18n ("Clear"); + + return (m_actOnSelection) ? i18n ("Selection: %1", opName) : opName; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpEffectClearCommand::size () const +{ + return ImageSize (m_oldImagePtr); +} + + +// public virtual [base kpCommand] +void kpEffectClearCommand::execute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + + m_oldImagePtr = new kpImage (); + *m_oldImagePtr = doc->image (m_actOnSelection); + + + // REFACTOR: Would like to derive entire class from kpEffectCommandBase but + // this code makes it difficult since it's not just acting on pixels + // (kpAbstractImageSelection::fill() takes into account the shape of a selection). + if (m_actOnSelection) + { + // OPT: could just edit pixmap directly and signal change + kpAbstractImageSelection *sel = doc->imageSelection (); + Q_ASSERT (sel); + sel->fill (m_newColor); + } + else { + doc->fill (m_newColor); + } +} + +// public virtual [base kpCommand] +void kpEffectClearCommand::unexecute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + + doc->setImage (m_actOnSelection, *m_oldImagePtr); + + + delete m_oldImagePtr; + m_oldImagePtr = nullptr; +} + diff --git a/commands/imagelib/effects/kpEffectClearCommand.h b/commands/imagelib/effects/kpEffectClearCommand.h new file mode 100644 index 0000000..d90fa9d --- /dev/null +++ b/commands/imagelib/effects/kpEffectClearCommand.h @@ -0,0 +1,62 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectClearCommand_H +#define kpEffectClearCommand_H + + +#include "commands/kpCommand.h" + +#include "imagelib/kpColor.h" +#include "imagelib/kpImage.h" + + +class kpEffectClearCommand : public kpCommand +{ +public: + kpEffectClearCommand (bool actOnSelection, + const kpColor &newColor, + kpCommandEnvironment *environ); + ~kpEffectClearCommand () override; + + QString name () const override; + + SizeType size () const override; + + void execute () override; + void unexecute () override; + +private: + bool m_actOnSelection; + + kpColor m_newColor; + kpImage *m_oldImagePtr; +}; + + +#endif // kpEffectClearCommand_H diff --git a/commands/imagelib/effects/kpEffectCommandBase.cpp b/commands/imagelib/effects/kpEffectCommandBase.cpp new file mode 100644 index 0000000..67c9070 --- /dev/null +++ b/commands/imagelib/effects/kpEffectCommandBase.cpp @@ -0,0 +1,124 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "kpEffectCommandBase.h" + +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "generic/kpSetOverrideCursorSaver.h" + +#include + +//-------------------------------------------------------------------------------- + +struct kpEffectCommandBasePrivate +{ + QString name; + bool actOnSelection{false}; + + kpImage oldImage; +}; + +kpEffectCommandBase::kpEffectCommandBase (const QString &name, + bool actOnSelection, + kpCommandEnvironment *environ) + : kpCommand (environ), + d (new kpEffectCommandBasePrivate ()) +{ + d->name = name; + d->actOnSelection = actOnSelection; +} + +kpEffectCommandBase::~kpEffectCommandBase () +{ + delete d; +} + + +// public virtual [base kpCommand] +QString kpEffectCommandBase::name () const +{ + return (d->actOnSelection) ? i18n ("Selection: %1", d->name) : d->name; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpEffectCommandBase::size () const +{ + return ImageSize (d->oldImage); +} + + +// public virtual [base kpCommand] +void kpEffectCommandBase::execute () +{ + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + kpDocument *doc = document (); + Q_ASSERT (doc); + + + const kpImage oldImage = doc->image (d->actOnSelection); + + if (!isInvertible ()) + { + d->oldImage = oldImage; + } + + + kpImage newImage = /*pure virtual*/applyEffect (oldImage); + + doc->setImage (d->actOnSelection, newImage); +} + +// public virtual [base kpCommand] +void kpEffectCommandBase::unexecute () +{ + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + kpDocument *doc = document (); + Q_ASSERT (doc); + + + kpImage newImage; + + if (!isInvertible ()) + { + newImage = d->oldImage; + } + else + { + newImage = /*pure virtual*/applyEffect (doc->image (d->actOnSelection)); + } + + doc->setImage (d->actOnSelection, newImage); + + + d->oldImage = kpImage (); +} + diff --git a/commands/imagelib/effects/kpEffectCommandBase.h b/commands/imagelib/effects/kpEffectCommandBase.h new file mode 100644 index 0000000..887e833 --- /dev/null +++ b/commands/imagelib/effects/kpEffectCommandBase.h @@ -0,0 +1,67 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectCommandBase_H +#define kpEffectCommandBase_H + + +#include + +#include "commands/kpCommand.h" +#include "imagelib/kpImage.h" + + +class kpEffectCommandBase : public kpCommand +{ +public: + kpEffectCommandBase (const QString &name, + bool actOnSelection, + kpCommandEnvironment *environ); + ~kpEffectCommandBase () override; + + QString name () const override; + SizeType size () const override; + +public: + void execute () override; + void unexecute () override; + +public: + // Return true if applyEffect(applyEffect(image)) == image + // to avoid storing the old image, saving memory. + virtual bool isInvertible () const { return false; } + +protected: + virtual kpImage applyEffect (const kpImage &image) = 0; + +private: + struct kpEffectCommandBasePrivate *d; +}; + + +#endif // kpEffectCommandBase_H diff --git a/commands/imagelib/effects/kpEffectEmbossCommand.cpp b/commands/imagelib/effects/kpEffectEmbossCommand.cpp new file mode 100644 index 0000000..08fd015 --- /dev/null +++ b/commands/imagelib/effects/kpEffectEmbossCommand.cpp @@ -0,0 +1,55 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_EMBOSS 0 + + +#include "kpEffectEmbossCommand.h" +#include "imagelib/effects/kpEffectEmboss.h" + +#include "kpLogCategories.h" +#include + + +kpEffectEmbossCommand::kpEffectEmbossCommand (int strength, + bool actOnSelection, + kpCommandEnvironment *environ) + : kpEffectCommandBase (i18n ("Emboss"), actOnSelection, environ), + m_strength (strength) +{ +} + +kpEffectEmbossCommand::~kpEffectEmbossCommand () = default; + + +// protected virtual [base kpEffectCommandBase] +kpImage kpEffectEmbossCommand::applyEffect (const kpImage &image) +{ + return kpEffectEmboss::applyEffect (image, m_strength); +} + diff --git a/commands/imagelib/effects/kpEffectEmbossCommand.h b/commands/imagelib/effects/kpEffectEmbossCommand.h new file mode 100644 index 0000000..09e1199 --- /dev/null +++ b/commands/imagelib/effects/kpEffectEmbossCommand.h @@ -0,0 +1,53 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectEmbossCommand_H +#define kpEffectEmbossCommand_H + + +#include "kpEffectCommandBase.h" +#include "imagelib/kpImage.h" + + +class kpEffectEmbossCommand : public kpEffectCommandBase +{ +public: + kpEffectEmbossCommand (int strength, + bool actOnSelection, + kpCommandEnvironment *environ); + ~kpEffectEmbossCommand () override; + +protected: + kpImage applyEffect (const kpImage &image) override; + +protected: + int m_strength; +}; + + +#endif // kpEffectEmbossCommand_H diff --git a/commands/imagelib/effects/kpEffectFlattenCommand.cpp b/commands/imagelib/effects/kpEffectFlattenCommand.cpp new file mode 100644 index 0000000..f76aa51 --- /dev/null +++ b/commands/imagelib/effects/kpEffectFlattenCommand.cpp @@ -0,0 +1,61 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_FLATTEN 0 + + +#include "kpEffectFlattenCommand.h" +#include "imagelib/effects/kpEffectFlatten.h" + +#include + +//-------------------------------------------------------------------------------- + +kpEffectFlattenCommand::kpEffectFlattenCommand (const QColor &color1, + const QColor &color2, + bool actOnSelection, + kpCommandEnvironment *environ) + : kpEffectCommandBase (i18n ("Flatten"), actOnSelection, environ), + m_color1 (color1), m_color2 (color2) +{ +} + +kpEffectFlattenCommand::~kpEffectFlattenCommand () = default; + + +// +// kpEffectFlattenCommand implements kpEffectCommandBase interface +// + +// protected virtual [base kpEffectCommandBase] +kpImage kpEffectFlattenCommand::applyEffect (const kpImage &image) +{ + return kpEffectFlatten::applyEffect (image, m_color1, m_color2); +} + + diff --git a/commands/imagelib/effects/kpEffectFlattenCommand.h b/commands/imagelib/effects/kpEffectFlattenCommand.h new file mode 100644 index 0000000..2a66fdb --- /dev/null +++ b/commands/imagelib/effects/kpEffectFlattenCommand.h @@ -0,0 +1,59 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectFlattenCommand_H +#define kpEffectFlattenCommand_H + + +#include + +#include "kpEffectCommandBase.h" +#include "imagelib/kpImage.h" + + +class kpEffectFlattenCommand : public kpEffectCommandBase +{ +public: + kpEffectFlattenCommand (const QColor &color1, const QColor &color2, + bool actOnSelection, + kpCommandEnvironment *environ); + ~kpEffectFlattenCommand () override; + + + // + // kpEffectCommandBase interface + // + +protected: + kpImage applyEffect (const kpImage &image) override; + + QColor m_color1, m_color2; +}; + + +#endif // kpEffectFlattenCommand_H diff --git a/commands/imagelib/effects/kpEffectGrayscaleCommand.cpp b/commands/imagelib/effects/kpEffectGrayscaleCommand.cpp new file mode 100644 index 0000000..4c145fe --- /dev/null +++ b/commands/imagelib/effects/kpEffectGrayscaleCommand.cpp @@ -0,0 +1,59 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "kpEffectGrayscaleCommand.h" +#include "imagelib/effects/kpEffectGrayscale.h" + +#include + +//-------------------------------------------------------------------------------- + +kpEffectGrayscaleCommand::kpEffectGrayscaleCommand ( + bool actOnSelection, + kpCommandEnvironment *environ) + + : kpEffectCommandBase ( + i18n ("Reduce to Grayscale"), + actOnSelection, + environ) +{ +} + +kpEffectGrayscaleCommand::~kpEffectGrayscaleCommand () = default; + + +// +// kpEffectGrayscaleCommand implements kpEffectCommandBase interface +// + +// protected virtual [base kpEffectCommandBase] +kpImage kpEffectGrayscaleCommand::applyEffect (const kpImage &image) +{ + return kpEffectGrayscale::applyEffect (image); +} + diff --git a/commands/imagelib/effects/kpEffectGrayscaleCommand.h b/commands/imagelib/effects/kpEffectGrayscaleCommand.h new file mode 100644 index 0000000..03b7a70 --- /dev/null +++ b/commands/imagelib/effects/kpEffectGrayscaleCommand.h @@ -0,0 +1,57 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectGrayscaleCommand_H +#define kpEffectGrayscaleCommand_H + + +#include "kpEffectCommandBase.h" +#include "imagelib/kpImage.h" + + +class kpEffectGrayscaleCommand : public kpEffectCommandBase +{ +public: + kpEffectGrayscaleCommand (bool actOnSelection, + kpCommandEnvironment *environ); + ~kpEffectGrayscaleCommand () override; + + + // + // kpEffectCommandBase interface + // + +public: + bool isInvertible () const override { return false; } + +protected: + kpImage applyEffect (const kpImage &image) override; +}; + + +#endif // kpEffectGrayscaleCommand_H diff --git a/commands/imagelib/effects/kpEffectHSVCommand.cpp b/commands/imagelib/effects/kpEffectHSVCommand.cpp new file mode 100644 index 0000000..36effb6 --- /dev/null +++ b/commands/imagelib/effects/kpEffectHSVCommand.cpp @@ -0,0 +1,50 @@ +/* + Copyright (c) 2007 Mike Gashler + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "kpEffectHSVCommand.h" +#include "imagelib/effects/kpEffectHSV.h" + +#include + +//--------------------------------------------------------------------- + +kpEffectHSVCommand::kpEffectHSVCommand (double hue, double saturation, double value, + bool actOnSelection, + kpCommandEnvironment *environ) + : kpEffectCommandBase (i18n ("Hue, Saturation, Value"), actOnSelection, environ), + m_hue (hue), m_saturation (saturation), m_value (value) +{ +} + +//--------------------------------------------------------------------- + +// protected virtual [base kpEffectCommandBase] +kpImage kpEffectHSVCommand::applyEffect (const kpImage &image) +{ + return kpEffectHSV::applyEffect (image, m_hue, m_saturation, m_value); +} + +//--------------------------------------------------------------------- diff --git a/commands/imagelib/effects/kpEffectHSVCommand.h b/commands/imagelib/effects/kpEffectHSVCommand.h new file mode 100644 index 0000000..1176dc3 --- /dev/null +++ b/commands/imagelib/effects/kpEffectHSVCommand.h @@ -0,0 +1,51 @@ + +/* + Copyright (c) 2007 Mike Gashler + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectHSVCommand_H +#define kpEffectHSVCommand_H + + +#include "kpEffectCommandBase.h" + + +class kpEffectHSVCommand : public kpEffectCommandBase +{ +public: + kpEffectHSVCommand (double hue, double saturation, double value, + bool actOnSelection, + kpCommandEnvironment *environ); + +protected: + kpImage applyEffect (const kpImage &image) override; + +protected: + double m_hue, m_saturation, m_value; +}; + + +#endif // kpEffectHSVCommand_H diff --git a/commands/imagelib/effects/kpEffectInvertCommand.cpp b/commands/imagelib/effects/kpEffectInvertCommand.cpp new file mode 100644 index 0000000..43ad1ca --- /dev/null +++ b/commands/imagelib/effects/kpEffectInvertCommand.cpp @@ -0,0 +1,66 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "kpEffectInvertCommand.h" + +#include "imagelib/effects/kpEffectInvert.h" + +#include + +//-------------------------------------------------------------------------------- + +kpEffectInvertCommand::kpEffectInvertCommand (int channels, + bool actOnSelection, + kpCommandEnvironment *environ) + : kpEffectCommandBase (channels == kpEffectInvert::RGB ? + i18n ("Invert Colors") : i18n ("Invert"), + actOnSelection, environ), + m_channels (channels) +{ +} + +kpEffectInvertCommand::kpEffectInvertCommand (bool actOnSelection, + kpCommandEnvironment *environ) + : kpEffectInvertCommand(kpEffectInvert::RGB, actOnSelection, environ) +{ +} + +kpEffectInvertCommand::~kpEffectInvertCommand () = default; + + +// +// kpEffectInvertCommand implements kpEffectCommandBase interface +// + +// protected virtual [base kpEffectCommandBase] +kpImage kpEffectInvertCommand::applyEffect (const kpImage &image) +{ + return kpEffectInvert::applyEffect (image, m_channels); +} + + diff --git a/commands/imagelib/effects/kpEffectInvertCommand.h b/commands/imagelib/effects/kpEffectInvertCommand.h new file mode 100644 index 0000000..f07df86 --- /dev/null +++ b/commands/imagelib/effects/kpEffectInvertCommand.h @@ -0,0 +1,63 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectInvertCommand_H +#define kpEffectInvertCommand_H + + +#include "kpEffectCommandBase.h" +#include "imagelib/kpImage.h" + + + +class kpEffectInvertCommand : public kpEffectCommandBase +{ +public: + kpEffectInvertCommand (int channels, + bool actOnSelection, + kpCommandEnvironment *environ); + kpEffectInvertCommand (bool actOnSelection, + kpCommandEnvironment *environ); + ~kpEffectInvertCommand () override; + + + // + // kpEffectCommandBase interface + // + +public: + bool isInvertible () const override { return true; } + +protected: + kpImage applyEffect (const kpImage &image) override; + + int m_channels; +}; + + +#endif // kpEffectInvertCommand_H diff --git a/commands/imagelib/effects/kpEffectReduceColorsCommand.cpp b/commands/imagelib/effects/kpEffectReduceColorsCommand.cpp new file mode 100644 index 0000000..d4977d6 --- /dev/null +++ b/commands/imagelib/effects/kpEffectReduceColorsCommand.cpp @@ -0,0 +1,80 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_REDUCE_COLORS 0 + + +#include "kpEffectReduceColorsCommand.h" +#include "imagelib/effects/kpEffectReduceColors.h" + +#include + +//--------------------------------------------------------------------- + +kpEffectReduceColorsCommand::kpEffectReduceColorsCommand (int depth, bool dither, + bool actOnSelection, + kpCommandEnvironment *environ) + : kpEffectCommandBase (commandName (depth, dither), actOnSelection, environ), + m_depth (depth), m_dither (dither) +{ +} + +//--------------------------------------------------------------------- + +// public +QString kpEffectReduceColorsCommand::commandName (int depth, int dither) const +{ + switch (depth) { + case 1: if (dither) { + return i18n ("Reduce to Monochrome (Dithered)"); + } + return i18n ("Reduce to Monochrome"); + + case 8: + if (dither) { + return i18n ("Reduce to 256 Color (Dithered)"); + } + return i18n ("Reduce to 256 Color"); + + default: return {}; + } +} + +//--------------------------------------------------------------------- + +// +// kpEffectReduceColorsCommand implements kpEffectCommandBase interface +// + +// protected virtual [base kpEffectCommandBase] +kpImage kpEffectReduceColorsCommand::applyEffect (const kpImage &image) +{ + return kpEffectReduceColors::applyEffect (image, m_depth, m_dither); +} + +//--------------------------------------------------------------------- diff --git a/commands/imagelib/effects/kpEffectReduceColorsCommand.h b/commands/imagelib/effects/kpEffectReduceColorsCommand.h new file mode 100644 index 0000000..1c8427b --- /dev/null +++ b/commands/imagelib/effects/kpEffectReduceColorsCommand.h @@ -0,0 +1,59 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectReduceColorsCommand_H +#define kpEffectReduceColorsCommand_H + + +#include "kpEffectCommandBase.h" +#include "imagelib/kpImage.h" + + +class kpEffectReduceColorsCommand : public kpEffectCommandBase +{ +public: + // depth must be 1 or 8 + kpEffectReduceColorsCommand (int depth, bool dither, + bool actOnSelection, + kpCommandEnvironment *environ); + + QString commandName (int depth, int dither) const; + + // + // kpEffectCommandBase interface + // + +protected: + kpImage applyEffect (const kpImage &image) override; + + int m_depth; + bool m_dither; +}; + + +#endif // kpEffectReduceColorsCommand_H diff --git a/commands/imagelib/effects/kpEffectToneEnhanceCommand.cpp b/commands/imagelib/effects/kpEffectToneEnhanceCommand.cpp new file mode 100644 index 0000000..c861dda --- /dev/null +++ b/commands/imagelib/effects/kpEffectToneEnhanceCommand.cpp @@ -0,0 +1,54 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2006 Mike Gashler + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "kpEffectToneEnhanceCommand.h" + +#include "imagelib/effects/kpEffectToneEnhance.h" + +#include + +//-------------------------------------------------------------------------------- + +kpEffectToneEnhanceCommand::kpEffectToneEnhanceCommand (double granularity, double amount, + bool actOnSelection, + kpCommandEnvironment *environ) + : kpEffectCommandBase (i18n ("Histogram Equalizer"), actOnSelection, environ), + m_granularity (granularity), m_amount (amount) +{ +} + +kpEffectToneEnhanceCommand::~kpEffectToneEnhanceCommand () = default; + + +// protected virtual [base kpEffectCommandBase] +kpImage kpEffectToneEnhanceCommand::applyEffect (const kpImage &image) +{ + return kpEffectToneEnhance::applyEffect (image, m_granularity, m_amount); +} + diff --git a/commands/imagelib/effects/kpEffectToneEnhanceCommand.h b/commands/imagelib/effects/kpEffectToneEnhanceCommand.h new file mode 100644 index 0000000..4a3c1fd --- /dev/null +++ b/commands/imagelib/effects/kpEffectToneEnhanceCommand.h @@ -0,0 +1,53 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2006 Mike Gashler + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectToneEnhanceCommand_H +#define kpEffectToneEnhanceCommand_H + + +#include "kpEffectCommandBase.h" + + +class kpEffectToneEnhanceCommand : public kpEffectCommandBase +{ +public: + kpEffectToneEnhanceCommand (double granularity, double amount, + bool actOnSelection, + kpCommandEnvironment *environ); + ~kpEffectToneEnhanceCommand () override; + +protected: + kpImage applyEffect (const kpImage &image) override; + +protected: + double m_granularity, m_amount; +}; + + +#endif // kpEffectToneEnhanceCommand_H diff --git a/commands/imagelib/kpDocumentMetaInfoCommand.cpp b/commands/imagelib/kpDocumentMetaInfoCommand.cpp new file mode 100644 index 0000000..ab7dc53 --- /dev/null +++ b/commands/imagelib/kpDocumentMetaInfoCommand.cpp @@ -0,0 +1,86 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "kpDocumentMetaInfoCommand.h" + +#include "document/kpDocument.h" +#include "imagelib/kpDocumentMetaInfo.h" +#include "kpDefs.h" + + +struct kpDocumentMetaInfoCommandPrivate +{ + kpDocumentMetaInfo metaInfo, oldMetaInfo; +}; + +kpDocumentMetaInfoCommand::kpDocumentMetaInfoCommand (const QString &name, + const kpDocumentMetaInfo &metaInfo, + const kpDocumentMetaInfo &oldMetaInfo, + kpCommandEnvironment *environ) + + : kpNamedCommand (name, environ), + d (new kpDocumentMetaInfoCommandPrivate ()) +{ + d->metaInfo = metaInfo; + d->oldMetaInfo = oldMetaInfo; +} + +kpDocumentMetaInfoCommand::~kpDocumentMetaInfoCommand () +{ + delete d; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpDocumentMetaInfoCommand::size () const +{ + return d->metaInfo.size () + d->oldMetaInfo.size (); +} + + +// public virtual [base kpCommand] +void kpDocumentMetaInfoCommand::execute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + doc->setMetaInfo (d->metaInfo); + doc->setModified (); +} + +// public virtual [base kpCommand] +void kpDocumentMetaInfoCommand::unexecute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + // REFACTOR: Document in kpDocument.h that kpDocument::setMetaInfo() does not mutate modified state + doc->setMetaInfo (d->oldMetaInfo); + doc->setModified (); +} + diff --git a/commands/imagelib/kpDocumentMetaInfoCommand.h b/commands/imagelib/kpDocumentMetaInfoCommand.h new file mode 100644 index 0000000..1b62e6a --- /dev/null +++ b/commands/imagelib/kpDocumentMetaInfoCommand.h @@ -0,0 +1,58 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpDocumentMetaInfoCommand_H +#define kpDocumentMetaInfoCommand_H + + +#include "commands/kpNamedCommand.h" + + +class kpDocumentMetaInfo; + +class kpDocumentMetaInfoCommand : public kpNamedCommand +{ +public: + kpDocumentMetaInfoCommand (const QString &name, + const kpDocumentMetaInfo &metaInfo, + const kpDocumentMetaInfo &oldMetaInfo, + kpCommandEnvironment *environ); + ~kpDocumentMetaInfoCommand () override; + + SizeType size () const override; + +public: + void execute () override; + void unexecute () override; + +private: + struct kpDocumentMetaInfoCommandPrivate * const d; +}; + + +#endif // kpDocumentMetaInfoCommand_H diff --git a/commands/imagelib/transforms/kpTransformFlipCommand.cpp b/commands/imagelib/transforms/kpTransformFlipCommand.cpp new file mode 100644 index 0000000..a086d29 --- /dev/null +++ b/commands/imagelib/transforms/kpTransformFlipCommand.cpp @@ -0,0 +1,135 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "kpTransformFlipCommand.h" + +#include + +#include "kpLogCategories.h" +#include "layers/selections/image/kpAbstractImageSelection.h" +#include "environments/commands/kpCommandEnvironment.h" +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "pixmapfx/kpPixmapFX.h" + +#include + +//--------------------------------------------------------------------- + +kpTransformFlipCommand::kpTransformFlipCommand (bool actOnSelection, + bool horiz, bool vert, + kpCommandEnvironment *environ) + : kpCommand (environ), + m_actOnSelection (actOnSelection), + m_horiz (horiz), m_vert (vert) +{ +} + +//--------------------------------------------------------------------- + +kpTransformFlipCommand::~kpTransformFlipCommand () = default; + +//--------------------------------------------------------------------- +// public virtual [base kpCommand] + +QString kpTransformFlipCommand::name () const +{ + QString opName; + + +#if 1 + opName = i18n ("Flip"); +#else // re-enable when giving full descriptions for all actions + if (m_horiz && m_vert) + opName = i18n ("Flip horizontally and vertically"); + else if (m_horiz) + opName = i18n ("Flip horizontally"); + else if (m_vert) + opName = i18n ("Flip vertically"); + else + { + qCCritical(kpLogCommands) << "kpTransformFlipCommand::name() not asked to flip"; + return {}; + } +#endif + + + if (m_actOnSelection) { + return i18n ("Selection: %1", opName); + } + + return opName; +} + +//--------------------------------------------------------------------- +// public virtual [base kpCommand] + +kpCommandSize::SizeType kpTransformFlipCommand::size () const +{ + return 0; +} + +//--------------------------------------------------------------------- +// public virtual [base kpCommand] + +void kpTransformFlipCommand::execute () +{ + flip (); +} + +//--------------------------------------------------------------------- +// public virtual [base kpCommand] + +void kpTransformFlipCommand::unexecute () +{ + flip (); +} + +//--------------------------------------------------------------------- +// private + +void kpTransformFlipCommand::flip () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + QApplication::setOverrideCursor (Qt::WaitCursor); + + if (m_actOnSelection) + { + Q_ASSERT (doc->imageSelection ()); + doc->imageSelection ()->flip (m_horiz, m_vert); + environ ()->somethingBelowTheCursorChanged (); + } + else + { + doc->setImage(doc->image().mirrored(m_horiz, m_vert)); + } + + QApplication::restoreOverrideCursor (); +} diff --git a/commands/imagelib/transforms/kpTransformFlipCommand.h b/commands/imagelib/transforms/kpTransformFlipCommand.h new file mode 100644 index 0000000..9a1dee8 --- /dev/null +++ b/commands/imagelib/transforms/kpTransformFlipCommand.h @@ -0,0 +1,60 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTransformFlipCommand_H +#define kpTransformFlipCommand_H + + +#include "commands/kpCommand.h" + + +class kpTransformFlipCommand : public kpCommand +{ +public: + kpTransformFlipCommand (bool actOnSelection, + bool horiz, bool vert, + kpCommandEnvironment *environ); + + ~kpTransformFlipCommand () override; + + QString name () const override; + + SizeType size () const override; + + void execute () override; + void unexecute () override; + +private: + void flip (); + + bool m_actOnSelection; + bool m_horiz, m_vert; +}; + + +#endif // kpTransformFlipCommand_H diff --git a/commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp b/commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp new file mode 100644 index 0000000..bade764 --- /dev/null +++ b/commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp @@ -0,0 +1,486 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_RESIZE_SCALE_COMMAND 0 +#define DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG 0 + + +#include "kpTransformResizeScaleCommand.h" + +#include "layers/selections/image/kpAbstractImageSelection.h" +#include "environments/commands/kpCommandEnvironment.h" +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "layers/selections/image/kpFreeFormImageSelection.h" +#include "pixmapfx/kpPixmapFX.h" +#include "layers/selections/image/kpRectangularImageSelection.h" +#include "layers/selections/text/kpTextSelection.h" + + +#include +#include +#include +#include +#include +#include + +#include "kpLogCategories.h" + +#include + +//-------------------------------------------------------------------------------- + +kpTransformResizeScaleCommand::kpTransformResizeScaleCommand (bool actOnSelection, + int newWidth, int newHeight, + Type type, + kpCommandEnvironment *environ) + : kpCommand (environ), + m_actOnSelection (actOnSelection), + m_type (type), + m_backgroundColor (environ->backgroundColor ()), + m_oldSelectionPtr (nullptr) +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + m_oldWidth = doc->width (m_actOnSelection); + m_oldHeight = doc->height (m_actOnSelection); + + m_actOnTextSelection = (m_actOnSelection && + doc->textSelection ()); + + resize (newWidth, newHeight); + + // If we have a selection _border_ (but not a floating selection), + // then scale the selection with the document + m_scaleSelectionWithImage = (!m_actOnSelection && + (m_type == Scale || m_type == SmoothScale) && + document ()->selection () && + !document ()->selection ()->hasContent ()); +} + +kpTransformResizeScaleCommand::~kpTransformResizeScaleCommand () +{ + delete m_oldSelectionPtr; +} + + +// public virtual [base kpCommand] +QString kpTransformResizeScaleCommand::name () const +{ + if (m_actOnSelection) + { + if (m_actOnTextSelection) + { + if (m_type == Resize) { + return i18n ("Text: Resize Box"); + } + } + else + { + if (m_type == Scale) { + return i18n ("Selection: Scale"); + } + + if (m_type == SmoothScale) { + return i18n ("Selection: Smooth Scale"); + } + } + } + else + { + switch (m_type) + { + case Resize: + return i18n ("Resize"); + case Scale: + return i18n ("Scale"); + case SmoothScale: + return i18n ("Smooth Scale"); + } + } + + return {}; +} + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpTransformResizeScaleCommand::size () const +{ + return ImageSize (m_oldImage) + + ImageSize (m_oldRightImage) + + ImageSize (m_oldBottomImage) + + SelectionSize (m_oldSelectionPtr); +} + + +// public +int kpTransformResizeScaleCommand::newWidth () const +{ + return m_newWidth; +} + +// public +void kpTransformResizeScaleCommand::setNewWidth (int width) +{ + resize (width, newHeight ()); +} + + +// public +int kpTransformResizeScaleCommand::newHeight () const +{ + return m_newHeight; +} + +// public +void kpTransformResizeScaleCommand::setNewHeight (int height) +{ + resize (newWidth (), height); +} + + +// public +QSize kpTransformResizeScaleCommand::newSize () const +{ + return {newWidth (), newHeight ()}; +} + +// public virtual +void kpTransformResizeScaleCommand::resize (int width, int height) +{ + m_newWidth = width; + m_newHeight = height; + + m_isLosslessScale = ((m_type == Scale) && + (m_newWidth / m_oldWidth * m_oldWidth == m_newWidth) && + (m_newHeight / m_oldHeight * m_oldHeight == m_newHeight)); +} + + +// public +bool kpTransformResizeScaleCommand::scaleSelectionWithImage () const +{ + return m_scaleSelectionWithImage; +} + + +// private +void kpTransformResizeScaleCommand::scaleSelectionRegionWithDocument () +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_COMMAND + qCDebug(kpLogCommands) << "kpTransformResizeScaleCommand::scaleSelectionRegionWithDocument"; +#endif + + Q_ASSERT (m_oldSelectionPtr); + Q_ASSERT (!m_oldSelectionPtr->hasContent ()); + + + const double horizScale = double (m_newWidth) / double (m_oldWidth); + const double vertScale = double (m_newHeight) / double (m_oldHeight); + + const int newX = static_cast (m_oldSelectionPtr->x () * horizScale); + const int newY = static_cast (m_oldSelectionPtr->y () * vertScale); + + + QPolygon currentPoints = m_oldSelectionPtr->calculatePoints (); + currentPoints.translate (-currentPoints.boundingRect ().x (), + -currentPoints.boundingRect ().y ()); + + // TODO: refactor into kpPixmapFX + // TODO: Can we get to size 0x0 accidentally? + QTransform scaleMatrix; + scaleMatrix.scale (horizScale, vertScale); + currentPoints = scaleMatrix.map (currentPoints); + + currentPoints.translate ( + -currentPoints.boundingRect ().x () + newX, + -currentPoints.boundingRect ().y () + newY); + + auto *imageSel = dynamic_cast (m_oldSelectionPtr); + auto *textSel = dynamic_cast (m_oldSelectionPtr); + + if (imageSel) + { + document ()->setSelection ( + kpFreeFormImageSelection (currentPoints, kpImage (), + imageSel->transparency ())); + } + else if (textSel) + { + document ()->setSelection ( + kpTextSelection (currentPoints.boundingRect (), + textSel->textLines (), + textSel->textStyle ())); + } + else { + Q_ASSERT (!"Unknown selection type"); + } + + + environ ()->somethingBelowTheCursorChanged (); +} + + +// public virtual [base kpCommand] +void kpTransformResizeScaleCommand::execute () +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_COMMAND + qCDebug(kpLogCommands) << "kpTransformResizeScaleCommand::execute() type=" + << (int) m_type + << " oldWidth=" << m_oldWidth + << " oldHeight=" << m_oldHeight + << " newWidth=" << m_newWidth + << " newHeight=" << m_newHeight; +#endif + + if (m_oldWidth == m_newWidth && m_oldHeight == m_newHeight) + return; + + if (m_type == Resize) + { + if (m_actOnSelection) + { + if (!m_actOnTextSelection) { + Q_ASSERT (!"kpTransformResizeScaleCommand::execute() resizing sel doesn't make sense"); + } + + QApplication::setOverrideCursor (Qt::WaitCursor); + + kpTextSelection *textSel = textSelection (); + Q_ASSERT (textSel); + + kpTextSelection *newSel = textSel->resized (m_newWidth, m_newHeight); + document ()->setSelection (*newSel); + delete newSel; + + environ ()->somethingBelowTheCursorChanged (); + + QApplication::restoreOverrideCursor (); + } + else + { + QApplication::setOverrideCursor (Qt::WaitCursor); + + + if (m_newWidth < m_oldWidth) + { + m_oldRightImage = document ()->getImageAt ( + QRect (m_newWidth, 0, + m_oldWidth - m_newWidth, m_oldHeight)); + } + + if (m_newHeight < m_oldHeight) + { + m_oldBottomImage = document ()->getImageAt ( + QRect (0, m_newHeight, + m_newWidth, m_oldHeight - m_newHeight)); + } + + document ()->resize (m_newWidth, m_newHeight, m_backgroundColor); + + + QApplication::restoreOverrideCursor (); + } + } + // Scale + else + { + QApplication::setOverrideCursor (Qt::WaitCursor); + + + kpImage oldImage = document ()->image (m_actOnSelection); + + if (!m_isLosslessScale) { + m_oldImage = oldImage; + } + + kpImage newImage = kpPixmapFX::scale (oldImage, m_newWidth, m_newHeight, + m_type == SmoothScale); + + + if (!m_oldSelectionPtr && document ()->selection ()) + { + // Save sel border + m_oldSelectionPtr = document ()->selection ()->clone (); + m_oldSelectionPtr->deleteContent (); + } + + if (m_actOnSelection) + { + if (m_actOnTextSelection) { + Q_ASSERT (!"kpTransformResizeScaleCommand::execute() scaling text sel doesn't make sense"); + } + + Q_ASSERT (m_oldSelectionPtr); + if ( !m_oldSelectionPtr ) { // make coverity happy + return; + } + + QRect newRect = QRect (m_oldSelectionPtr->x (), m_oldSelectionPtr->y (), + newImage.width (), newImage.height ()); + + // Not possible to retain non-rectangular selection borders on scale + // (think about e.g. a 45 deg line as part of the border & 2x scale) + Q_ASSERT (dynamic_cast (m_oldSelectionPtr)); + document ()->setSelection ( + kpRectangularImageSelection (newRect, newImage, + dynamic_cast (m_oldSelectionPtr) + ->transparency ())); + + environ ()->somethingBelowTheCursorChanged (); + } + else + { + document ()->setImage (newImage); + + if (m_scaleSelectionWithImage) + { + scaleSelectionRegionWithDocument (); + } + } + + + QApplication::restoreOverrideCursor (); + } +} + +// public virtual [base kpCommand] +void kpTransformResizeScaleCommand::unexecute () +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_COMMAND + qCDebug(kpLogCommands) << "kpTransformResizeScaleCommand::unexecute() type=" + << m_type; +#endif + + if (m_oldWidth == m_newWidth && m_oldHeight == m_newHeight) { + return; + } + + kpDocument *doc = document (); + Q_ASSERT (doc); + + if (m_type == Resize) + { + if (m_actOnSelection) + { + if (!m_actOnTextSelection) { + Q_ASSERT (!"kpTransformResizeScaleCommand::unexecute() resizing sel doesn't make sense"); + } + + QApplication::setOverrideCursor (Qt::WaitCursor); + + kpTextSelection *textSel = textSelection (); + Q_ASSERT (textSel); + + kpTextSelection *newSel = textSel->resized (m_oldWidth, m_oldHeight); + document ()->setSelection (*newSel); + delete newSel; + + environ ()->somethingBelowTheCursorChanged (); + + QApplication::restoreOverrideCursor (); + } + else + { + QApplication::setOverrideCursor (Qt::WaitCursor); + + + kpImage newImage (m_oldWidth, m_oldHeight, QImage::Format_ARGB32_Premultiplied); + + kpPixmapFX::setPixmapAt (&newImage, QPoint (0, 0), + doc->image ()); + + if (m_newWidth < m_oldWidth) + { + kpPixmapFX::setPixmapAt (&newImage, + QPoint (m_newWidth, 0), + m_oldRightImage); + } + + if (m_newHeight < m_oldHeight) + { + kpPixmapFX::setPixmapAt (&newImage, + QPoint (0, m_newHeight), + m_oldBottomImage); + } + + doc->setImage (newImage); + + + QApplication::restoreOverrideCursor (); + } + } + // Scale + else + { + QApplication::setOverrideCursor (Qt::WaitCursor); + + + kpImage oldImage; + + if (!m_isLosslessScale) { + oldImage = m_oldImage; + } else { + oldImage = kpPixmapFX::scale (doc->image (m_actOnSelection), + m_oldWidth, m_oldHeight); + } + + + if (m_actOnSelection) + { + if (m_actOnTextSelection) { + Q_ASSERT (!"kpTransformResizeScaleCommand::unexecute() scaling text sel doesn't make sense"); + } + + Q_ASSERT (dynamic_cast (m_oldSelectionPtr)); + auto *oldImageSel = dynamic_cast (m_oldSelectionPtr); + + kpAbstractImageSelection *oldSelection = oldImageSel->clone (); + oldSelection->setBaseImage (oldImage); + doc->setSelection (*oldSelection); + delete oldSelection; + + environ ()->somethingBelowTheCursorChanged (); + } + else + { + doc->setImage (oldImage); + + if (m_scaleSelectionWithImage) + { + doc->setSelection (*m_oldSelectionPtr); + + environ ()->somethingBelowTheCursorChanged (); + } + } + + + QApplication::restoreOverrideCursor (); + } +} + diff --git a/commands/imagelib/transforms/kpTransformResizeScaleCommand.h b/commands/imagelib/transforms/kpTransformResizeScaleCommand.h new file mode 100644 index 0000000..23a6394 --- /dev/null +++ b/commands/imagelib/transforms/kpTransformResizeScaleCommand.h @@ -0,0 +1,100 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTransformResizeScaleCommand_H +#define kpTransformResizeScaleCommand_H + + +#include + +#include "imagelib/kpColor.h" +#include "commands/kpCommand.h" +#include "imagelib/kpImage.h" + + +class QSize; + +class kpAbstractSelection; + + +// REFACTOR: Split into multiple classes, each doing a different thing +// e.g. resize, scale and smooth scale. +// REFACTOR: Replace kpToolSelectionResizeScaleCommand with us. +class kpTransformResizeScaleCommand : public kpCommand +{ +public: + enum Type + { + Resize, Scale, SmoothScale + }; + + kpTransformResizeScaleCommand (bool actOnSelection, + int newWidth, int newHeight, + Type type, + kpCommandEnvironment *environ); + ~kpTransformResizeScaleCommand () override; + + QString name () const override; + SizeType size () const override; + +public: + int newWidth () const; + void setNewWidth (int width); + + int newHeight () const; + void setNewHeight (int height); + + QSize newSize () const; + virtual void resize (int width, int height); + +public: + bool scaleSelectionWithImage () const; + +private: + void scaleSelectionRegionWithDocument (); + +public: + void execute () override; + void unexecute () override; + +protected: + bool m_actOnSelection; + int m_newWidth, m_newHeight; + Type m_type; + bool m_isLosslessScale; + bool m_scaleSelectionWithImage; + kpColor m_backgroundColor; + + int m_oldWidth, m_oldHeight; + bool m_actOnTextSelection; + kpImage m_oldImage, m_oldRightImage, m_oldBottomImage; + kpAbstractSelection *m_oldSelectionPtr; +}; + + +#endif // kpTransformResizeScaleCommand_H diff --git a/commands/imagelib/transforms/kpTransformRotateCommand.cpp b/commands/imagelib/transforms/kpTransformRotateCommand.cpp new file mode 100644 index 0000000..28e6258 --- /dev/null +++ b/commands/imagelib/transforms/kpTransformRotateCommand.cpp @@ -0,0 +1,220 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_ROTATE 0 + + +#include "kpTransformRotateCommand.h" + +#include "layers/selections/image/kpAbstractImageSelection.h" +#include "environments/commands/kpCommandEnvironment.h" +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "layers/selections/image/kpFreeFormImageSelection.h" +#include "pixmapfx/kpPixmapFX.h" +#include "layers/selections/image/kpRectangularImageSelection.h" +#include "views/manager/kpViewManager.h" +#include "kpLogCategories.h" + +#include +#include +#include + +#include + +//-------------------------------------------------------------------------------- + +kpTransformRotateCommand::kpTransformRotateCommand (bool actOnSelection, + double angle, + kpCommandEnvironment *environ) + : kpCommand (environ), + m_actOnSelection (actOnSelection), + m_angle (angle), + m_backgroundColor (environ->backgroundColor (actOnSelection)), + m_losslessRotation (kpPixmapFX::isLosslessRotation (angle)), + m_oldSelectionPtr (nullptr) +{ +} + +kpTransformRotateCommand::~kpTransformRotateCommand () +{ + delete m_oldSelectionPtr; +} + + +// public virtual [base kpCommand] +QString kpTransformRotateCommand::name () const +{ + QString opName = i18n ("Rotate"); + + return (m_actOnSelection) ? i18n ("Selection: %1", opName) : opName; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpTransformRotateCommand::size () const +{ + return ImageSize (m_oldImage) + + SelectionSize (m_oldSelectionPtr); +} + + +// public virtual [base kpCommand] +void kpTransformRotateCommand::execute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + + QApplication::setOverrideCursor (Qt::WaitCursor); + + + if (!m_losslessRotation) { + m_oldImage = doc->image (m_actOnSelection); + } + + + kpImage newImage = kpPixmapFX::rotate (doc->image (m_actOnSelection), + m_angle, + m_backgroundColor); + + if (!m_actOnSelection) { + doc->setImage (newImage); + } + else { + kpAbstractImageSelection *sel = doc->imageSelection (); + Q_ASSERT (sel); + + // Save old selection + m_oldSelectionPtr = sel->clone (); + + // Conserve memmory: + // + // 1. If it's a lossless rotation, we don't need to the store old + // image anywhere at all, as we can reconstruct it by rotating in + // reverse. + // 2. If it's not a lossless rotation, "m_oldImage" already holds + // a copy of the old image. In this case, we actually save very + // little with this line (just, the computed transparency mask) since + // kpImage is copy-on-write. + m_oldSelectionPtr->setBaseImage (kpImage ()); + + + // Calculate new top left (so selection rotates about center) + // (the Times2 trickery is used to reduce integer division error without + // resorting to the troublesome world of floating point) + QPoint oldCenterTimes2 (sel->x () * 2 + sel->width (), + sel->y () * 2 + sel->height ()); + QPoint newTopLeftTimes2 (oldCenterTimes2 - QPoint (newImage.width (), newImage.height ())); + QPoint newTopLeft (newTopLeftTimes2.x () / 2, newTopLeftTimes2.y () / 2); + + + // Calculate rotated points + QPolygon currentPoints = sel->calculatePoints (); + currentPoints.translate (-currentPoints.boundingRect ().x (), + -currentPoints.boundingRect ().y ()); + QTransform rotateMatrix = kpPixmapFX::rotateMatrix (doc->image (m_actOnSelection), m_angle); + currentPoints = rotateMatrix.map (currentPoints); + currentPoints.translate (-currentPoints.boundingRect ().x () + newTopLeft.x (), + -currentPoints.boundingRect ().y () + newTopLeft.y ()); + + + if (currentPoints.boundingRect ().width () == newImage.width () && + currentPoints.boundingRect ().height () == newImage.height ()) + { + doc->setSelection ( + kpFreeFormImageSelection ( + currentPoints, newImage, + m_oldSelectionPtr->transparency ())); + } + else + { + // TODO: fix the latter "victim of" problem in kpAbstractImageSelection by + // allowing the border width & height != pixmap width & height + // Or maybe autocrop? + #if DEBUG_KP_TOOL_ROTATE + qCDebug(kpLogCommands) << "kpTransformRotateCommand::execute() currentPoints.boundingRect=" + << currentPoints.boundingRect () + << " newPixmap: w=" << newImage.width () + << " h=" << newImage.height () + << " (victim of rounding error and/or rotated-a-(rectangular)-pixmap-that-was-transparent-in-the-corners-making-sel-uselessly-bigger-than-needs-be)"; + #endif + doc->setSelection ( + kpRectangularImageSelection ( + QRect (newTopLeft.x (), newTopLeft.y (), + newImage.width (), newImage.height ()), + newImage, + m_oldSelectionPtr->transparency ())); + } + + environ ()->somethingBelowTheCursorChanged (); + } + + + QApplication::restoreOverrideCursor (); +} + +// public virtual [base kpCommand] +void kpTransformRotateCommand::unexecute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + + QApplication::setOverrideCursor (Qt::WaitCursor); + + + kpImage oldImage; + + if (!m_losslessRotation) + { + oldImage = m_oldImage; + m_oldImage = kpImage (); + } + else + { + oldImage = kpPixmapFX::rotate (doc->image (m_actOnSelection), + 360 - m_angle, + m_backgroundColor); + } + + + if (!m_actOnSelection) { + doc->setImage (oldImage); + } + else { + m_oldSelectionPtr->setBaseImage (oldImage); + doc->setSelection (*m_oldSelectionPtr); + delete m_oldSelectionPtr; m_oldSelectionPtr = nullptr; + + environ ()->somethingBelowTheCursorChanged (); + } + + + QApplication::restoreOverrideCursor (); +} + diff --git a/commands/imagelib/transforms/kpTransformRotateCommand.h b/commands/imagelib/transforms/kpTransformRotateCommand.h new file mode 100644 index 0000000..93ab104 --- /dev/null +++ b/commands/imagelib/transforms/kpTransformRotateCommand.h @@ -0,0 +1,68 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTransformRotateCommand_H +#define kpTransformRotateCommand_H + + +#include "imagelib/kpColor.h" +#include "commands/kpCommand.h" +#include "imagelib/kpImage.h" + + +class kpAbstractImageSelection; + + +class kpTransformRotateCommand : public kpCommand +{ +public: + kpTransformRotateCommand (bool actOnSelection, + double angle, // 0 <= angle < 360 (clockwise) + kpCommandEnvironment *environ); + ~kpTransformRotateCommand () override; + + QString name () const override; + + SizeType size () const override; + + void execute () override; + void unexecute () override; + +private: + bool m_actOnSelection; + double m_angle; + + kpColor m_backgroundColor; + + bool m_losslessRotation; + kpImage m_oldImage; + kpAbstractImageSelection *m_oldSelectionPtr; +}; + + +#endif // kpTransformRotateCommand_H diff --git a/commands/imagelib/transforms/kpTransformSkewCommand.cpp b/commands/imagelib/transforms/kpTransformSkewCommand.cpp new file mode 100644 index 0000000..e8aeb33 --- /dev/null +++ b/commands/imagelib/transforms/kpTransformSkewCommand.cpp @@ -0,0 +1,195 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SKEW 0 +#define DEBUG_KP_TOOL_SKEW_DIALOG 0 + + +#include "kpTransformSkewCommand.h" + +#include "layers/selections/image/kpAbstractImageSelection.h" +#include "environments/commands/kpCommandEnvironment.h" +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "layers/selections/image/kpFreeFormImageSelection.h" +#include "pixmapfx/kpPixmapFX.h" +#include "layers/selections/image/kpRectangularImageSelection.h" +#include "kpLogCategories.h" + +#include +#include +#include + +// TODO: nasty, should avoid using GUI class in this command class +#include "dialogs/imagelib/transforms/kpTransformSkewDialog.h" + +#include + +//-------------------------------------------------------------------------------- + +kpTransformSkewCommand::kpTransformSkewCommand (bool actOnSelection, + int hangle, int vangle, + kpCommandEnvironment *environ) + : kpCommand (environ), + m_actOnSelection (actOnSelection), + m_hangle (hangle), m_vangle (vangle), + m_backgroundColor (environ->backgroundColor (actOnSelection)), + m_oldSelectionPtr (nullptr) +{ +} + +kpTransformSkewCommand::~kpTransformSkewCommand () +{ + delete m_oldSelectionPtr; +} + + +// public virtual [base kpCommand] +QString kpTransformSkewCommand::name () const +{ + QString opName = i18n ("Skew"); + + return (m_actOnSelection) ? i18n ("Selection: %1", opName) : opName; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpTransformSkewCommand::size () const +{ + return ImageSize (m_oldImage) + + SelectionSize (m_oldSelectionPtr); +} + + +// public virtual [base kpCommand] +void kpTransformSkewCommand::execute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + + QApplication::setOverrideCursor (Qt::WaitCursor); + + + kpImage newImage = kpPixmapFX::skew (doc->image (m_actOnSelection), + kpTransformSkewDialog::horizontalAngleForPixmapFX (m_hangle), + kpTransformSkewDialog::verticalAngleForPixmapFX (m_vangle), + m_backgroundColor); + + if (!m_actOnSelection) + { + m_oldImage = doc->image (m_actOnSelection); + + doc->setImage (newImage); + } + else + { + kpAbstractImageSelection *sel = doc->imageSelection (); + Q_ASSERT (sel); + + // Save old selection + m_oldSelectionPtr = sel->clone (); + + + // Calculate skewed points + QPolygon currentPoints = sel->calculatePoints (); + currentPoints.translate (-currentPoints.boundingRect ().x (), + -currentPoints.boundingRect ().y ()); + QTransform skewMatrix = kpPixmapFX::skewMatrix ( + doc->image (m_actOnSelection), + kpTransformSkewDialog::horizontalAngleForPixmapFX (m_hangle), + kpTransformSkewDialog::verticalAngleForPixmapFX (m_vangle)); + currentPoints = skewMatrix.map (currentPoints); + currentPoints.translate (-currentPoints.boundingRect ().x () + m_oldSelectionPtr->x (), + -currentPoints.boundingRect ().y () + m_oldSelectionPtr->y ()); + + + if (currentPoints.boundingRect ().width () == newImage.width () && + currentPoints.boundingRect ().height () == newImage.height ()) + { + doc->setSelection ( + kpFreeFormImageSelection ( + currentPoints, newImage, + m_oldSelectionPtr->transparency ())); + } + else + { + // TODO: fix the latter "victim of" problem in kpAbstractImageSelection by + // allowing the border width & height != pixmap width & height + // Or maybe autocrop? + #if DEBUG_KP_TOOL_SKEW + qCDebug(kpLogCommands) << "kpTransformSkewCommand::execute() currentPoints.boundingRect=" + << currentPoints.boundingRect () + << " newPixmap: w=" << newImage.width () + << " h=" << newImage.height () + << " (victim of rounding error and/or skewed-a-(rectangular)-pixmap-that-was-transparent-in-the-corners-making-sel-uselessly-bigger-than-needs-be))"; + #endif + doc->setSelection ( + kpRectangularImageSelection ( + QRect (currentPoints.boundingRect ().x (), + currentPoints.boundingRect ().y (), + newImage.width (), + newImage.height ()), + newImage, + m_oldSelectionPtr->transparency ())); + } + + environ ()->somethingBelowTheCursorChanged (); + } + + + QApplication::restoreOverrideCursor (); +} + +// public virtual [base kpCommand] +void kpTransformSkewCommand::unexecute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + + QApplication::setOverrideCursor (Qt::WaitCursor); + + + if (!m_actOnSelection) + { + doc->setImage (m_oldImage); + m_oldImage = kpImage (); + } + else + { + doc->setSelection (*m_oldSelectionPtr); + delete m_oldSelectionPtr; m_oldSelectionPtr = nullptr; + + environ ()->somethingBelowTheCursorChanged (); + } + + + QApplication::restoreOverrideCursor (); +} + diff --git a/commands/imagelib/transforms/kpTransformSkewCommand.h b/commands/imagelib/transforms/kpTransformSkewCommand.h new file mode 100644 index 0000000..cb9cbd0 --- /dev/null +++ b/commands/imagelib/transforms/kpTransformSkewCommand.h @@ -0,0 +1,65 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTransformSkewCommand_H +#define kpTransformSkewCommand_H + + +#include "imagelib/kpColor.h" +#include "imagelib/kpImage.h" +#include "commands/kpCommand.h" + + + + +class kpTransformSkewCommand : public kpCommand +{ +public: + kpTransformSkewCommand (bool actOnSelection, + int hangle, int vangle, + kpCommandEnvironment *environ); + ~kpTransformSkewCommand () override; + + QString name () const override; + + SizeType size () const override; + + void execute () override; + void unexecute () override; + +private: + bool m_actOnSelection; + int m_hangle, m_vangle; + + kpColor m_backgroundColor; + kpImage m_oldImage; + kpAbstractImageSelection *m_oldSelectionPtr; +}; + + +#endif // kpTransformSkewCommand_H diff --git a/commands/kpCommand.cpp b/commands/kpCommand.cpp new file mode 100644 index 0000000..55e3e7c --- /dev/null +++ b/commands/kpCommand.cpp @@ -0,0 +1,83 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_COMMAND_HISTORY 0 + + +#include "kpCommand.h" + +#include "environments/commands/kpCommandEnvironment.h" + + +kpCommand::kpCommand (kpCommandEnvironment *environ) + : m_environ (environ) +{ + Q_ASSERT (environ); +} + +kpCommand::~kpCommand () = default; + + +kpCommandEnvironment *kpCommand::environ () const +{ + return m_environ; +} + + +// protected +kpDocument *kpCommand::document () const +{ + return m_environ->document (); +} + + +// protected +kpAbstractSelection *kpCommand::selection () const +{ + return m_environ->selection (); +} + +// protected +kpAbstractImageSelection *kpCommand::imageSelection () const +{ + return m_environ->imageSelection (); +} + +// protected +kpTextSelection *kpCommand::textSelection () const +{ + return m_environ->textSelection (); +} + + +// protected +kpViewManager *kpCommand::viewManager () const +{ + return m_environ->viewManager (); +} + diff --git a/commands/kpCommand.h b/commands/kpCommand.h new file mode 100644 index 0000000..59a71a5 --- /dev/null +++ b/commands/kpCommand.h @@ -0,0 +1,90 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpCommand_H +#define kpCommand_H + + +#include "kpCommandSize.h" +#undef environ // macro on win32 + + +class QString; + +class kpAbstractImageSelection; +class kpAbstractSelection; +class kpCommandEnvironment; +class kpDocument; +class kpTextSelection; +class kpViewManager; + + +class kpCommand : public kpCommandSize +{ +public: + kpCommand (kpCommandEnvironment *environ); + virtual ~kpCommand (); + +public: + virtual QString name () const = 0; + + // Returns the estimated size in bytes. + // + // You only have to factor in the size of variables that change according + // to the amount of input e.g. pixmap size, text size. There is no need + // to include the size of O(1) variables unless they are huge. + // + // If in doubt, return the largest possible amount of memory that your + // command will take. This is better than making the user unexpectedly + // run out of memory. + // + // Implement this by measuring the size of all of your fields, using + // kpCommandSize. + virtual SizeType size () const = 0; + + virtual void execute () = 0; + virtual void unexecute () = 0; + +protected: + kpCommandEnvironment *environ () const; + + // Commonly used accessors - simply forwards to environ(). + kpDocument *document () const; + + kpAbstractSelection *selection () const; + kpAbstractImageSelection *imageSelection () const; + kpTextSelection *textSelection () const; + + kpViewManager *viewManager () const; + +private: + kpCommandEnvironment * const m_environ; +}; + + +#endif // kpCommand_H diff --git a/commands/kpCommandHistory.cpp b/commands/kpCommandHistory.cpp new file mode 100644 index 0000000..dd44de5 --- /dev/null +++ b/commands/kpCommandHistory.cpp @@ -0,0 +1,131 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_COMMAND_HISTORY 0 + + +#include "kpCommandHistory.h" + +#include "layers/selections/kpAbstractSelection.h" +#include "mainWindow/kpMainWindow.h" +#include "tools/kpTool.h" +#include "commands/tools/selection/kpToolSelectionCreateCommand.h" + + +kpCommandHistory::kpCommandHistory (bool doReadConfig, kpMainWindow *mainWindow) + : kpCommandHistoryBase (doReadConfig, mainWindow->actionCollection ()), + m_mainWindow (mainWindow) +{ +} + +kpCommandHistory::~kpCommandHistory () = default; + + +static bool NextUndoCommandIsCreateBorder (kpCommandHistory *commandHistory) +{ + Q_ASSERT (commandHistory); + + kpCommand *cmd = commandHistory->nextUndoCommand (); + if (!cmd) { + return false; + } + + auto *c = dynamic_cast (cmd); + if (!c) { + return false; + } + + const kpAbstractSelection *sel = c->fromSelection (); + Q_ASSERT (sel); + + return (!sel->hasContent ()); +} + +// public +void kpCommandHistory::addCreateSelectionCommand (kpToolSelectionCreateCommand *cmd, + bool execute) +{ + if (cmd->fromSelection ()->hasContent ()) + { + addCommand (cmd, execute); + return; + } + + if (::NextUndoCommandIsCreateBorder (this)) + { + setNextUndoCommand (cmd); + if (execute) { + cmd->execute (); + } + } + else { + addCommand (cmd, execute); + } +} + +//--------------------------------------------------------------------- + +// public slot virtual [base KCommandHistory] +void kpCommandHistory::undo () +{ +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "kpCommandHistory::undo() CALLED!"; +#endif + if (m_mainWindow && m_mainWindow->toolHasBegunShape ()) + { + #if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "\thas begun shape - cancel draw"; + #endif + m_mainWindow->tool ()->cancelShapeInternal (); + } + else { + kpCommandHistoryBase::undo (); + } +} + +//--------------------------------------------------------------------- + +// public slot virtual [base KCommandHistory] +void kpCommandHistory::redo () +{ + if (m_mainWindow && m_mainWindow->toolHasBegunShape ()) + { + // Not completely obvious but what else can we do? + // + // Ignoring the request would not be intuitive for tools like + // Polygon & Polyline (where it's not always apparent to the user + // that s/he's still drawing a shape even though the mouse isn't + // down). + m_mainWindow->tool ()->cancelShapeInternal (); + } + else { + kpCommandHistoryBase::redo (); + } +} + + diff --git a/commands/kpCommandHistory.h b/commands/kpCommandHistory.h new file mode 100644 index 0000000..e0b55ff --- /dev/null +++ b/commands/kpCommandHistory.h @@ -0,0 +1,105 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpCommandHistory_H +#define kpCommandHistory_H + + +#include "kpCommandHistoryBase.h" + +class kpMainWindow; +class kpToolSelectionCreateCommand; + + +// +// KolourPaint-specific command history functionality. +// +// Intercepts Undo/Redo requests: +// +// If the user is currently drawing a shape, it cancels it. +// Else it passes on the Undo/Redo request to kpCommandHistoryBase. +// +// TODO: This is wrong. It won't work if the Undo action is disabled, +// for instance. Later: What about kpToolText::viewEvent()'s use of +// QEvent::ShortcutOverride? +// +// Maybe the real solution is to call kpCommandHistoryBase::addCommand() +// as _soon_ as the shape starts - not after it ends. But the +// trouble with this solution is that if the user Undoes/cancels +// the shape s/he's currently drawing, it would replace a Redo +// slot in the history. Arguably you shouldn't be able to Redo +// something you never finished drawing. +// +// The solution is to add this functionality to kpCommandHistoryBase. +// +class kpCommandHistory : public kpCommandHistoryBase +{ +Q_OBJECT + +public: + kpCommandHistory (bool doReadConfig, kpMainWindow *mainWindow); + ~kpCommandHistory () override; + +public: + // Same as addCommand(), except that this has a more desirable behavior + // when adding a selection border creation command: If the next undo command + // also creates a selection border, it overwrites that command + // with the given , instead of adding to the undo history. + // + // This helps to reduce the number of consecutive selection border + // creation commands in the history. Exactly one border creation + // command before each "real" selection command is useful as it allows + // users to undo just that "real" operation and then do a different "real" + // operation with the same border (as sometimes, exact borders are difficult + // to recreate). However, multiple consecutive border creation + // commands get annoying since none of them mutate the document, + // so if the user has not done a "real" command with the last selection + // border (i.e. the next undo command), what this method is saying is + // that the user wanted to throw away that border drag anyway. + // + // This special behavior is perfectly safe since border creation commands + // do not mutate the document. + // + // If creates a selection that is not just a border, this + // method has the same effect as addCommand(). + // + // REFACTOR: Why not just override addCommand() and test if it was given a + // kpToolSelectionCreateCommand? + void addCreateSelectionCommand (kpToolSelectionCreateCommand *cmd, + bool execute = true); + +public slots: + void undo () override; + void redo () override; + +protected: + kpMainWindow *m_mainWindow; +}; + + +#endif // kpCommandHistory_H diff --git a/commands/kpCommandHistoryBase.cpp b/commands/kpCommandHistoryBase.cpp new file mode 100644 index 0000000..028037c --- /dev/null +++ b/commands/kpCommandHistoryBase.cpp @@ -0,0 +1,714 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_COMMAND_HISTORY 0 + + +#include "kpCommandHistoryBase.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "kpCommand.h" +#include "kpLogCategories.h" +#include "environments/commands/kpCommandEnvironment.h" +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "mainWindow/kpMainWindow.h" +#include "tools/kpTool.h" + +//--------------------------------------------------------------------- + +static void ClearPointerList(QList &list) +{ + qDeleteAll(list); + list.clear(); +} + +//-------------------------------------------------------------------------------- + +kpCommandHistoryBase::kpCommandHistoryBase (bool doReadConfig, + KActionCollection *ac) +{ + m_actionUndo = new KToolBarPopupAction(QIcon::fromTheme(QStringLiteral("edit-undo")), undoActionText (), this); + ac->addAction (KStandardAction::name (KStandardAction::Undo), m_actionUndo); + ac->setDefaultShortcuts (m_actionUndo, KStandardShortcut::shortcut (KStandardShortcut::Undo)); + connect (m_actionUndo, &KToolBarPopupAction::triggered, this, &kpCommandHistoryBase::undo); + + m_actionRedo = new KToolBarPopupAction(QIcon::fromTheme(QStringLiteral("edit-redo")), redoActionText (), this); + ac->addAction (KStandardAction::name (KStandardAction::Redo), m_actionRedo); + ac->setDefaultShortcuts (m_actionRedo, KStandardShortcut::shortcut (KStandardShortcut::Redo)); + connect (m_actionRedo, &KToolBarPopupAction::triggered, this, &kpCommandHistoryBase::redo ); + + + m_actionUndo->setEnabled (false); + m_actionRedo->setEnabled (false); + + + connect (m_actionUndo->menu(), &QMenu::triggered, + this, &kpCommandHistoryBase::undoUpToNumber); + + connect (m_actionRedo->menu(), &QMenu::triggered, + this, &kpCommandHistoryBase::redoUpToNumber); + + + m_undoMinLimit = 10; + m_undoMaxLimit = 500; + m_undoMaxLimitSizeLimit = 16 * 1048576; + + + m_documentRestoredPosition = 0; + + + if (doReadConfig) { + readConfig (); + } +} + +//-------------------------------------------------------------------------------- + +kpCommandHistoryBase::~kpCommandHistoryBase () +{ + ::ClearPointerList(m_undoCommandList); + ::ClearPointerList(m_redoCommandList); +} + +//-------------------------------------------------------------------------------- + +// public +int kpCommandHistoryBase::undoLimit () const +{ + return undoMinLimit (); +} + +// public +void kpCommandHistoryBase::setUndoLimit (int limit) +{ + setUndoMinLimit (limit); +} + + +// public +int kpCommandHistoryBase::undoMinLimit () const +{ + return m_undoMinLimit; +} + +// public +void kpCommandHistoryBase::setUndoMinLimit (int limit) +{ +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "kpCommandHistoryBase::setUndoMinLimit(" + << limit << ")"; +#endif + + if (limit < 1 || limit > 5000/*"ought to be enough for anybody"*/) + { + qCCritical(kpLogCommands) << "kpCommandHistoryBase::setUndoMinLimit(" + << limit << ")"; + return; + } + + if (limit == m_undoMinLimit) { + return; + } + + m_undoMinLimit = limit; + trimCommandListsUpdateActions (); +} + + +// public +int kpCommandHistoryBase::undoMaxLimit () const +{ + return m_undoMaxLimit; +} + +// public +void kpCommandHistoryBase::setUndoMaxLimit (int limit) +{ +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "kpCommandHistoryBase::setUndoMaxLimit(" + << limit << ")"; +#endif + + if (limit < 1 || limit > 5000/*"ought to be enough for anybody"*/) + { + qCCritical(kpLogCommands) << "kpCommandHistoryBase::setUndoMaxLimit(" + << limit << ")"; + return; + } + + if (limit == m_undoMaxLimit) { + return; + } + + m_undoMaxLimit = limit; + trimCommandListsUpdateActions (); +} + + +// public +kpCommandSize::SizeType kpCommandHistoryBase::undoMaxLimitSizeLimit () const +{ + return m_undoMaxLimitSizeLimit; +} + +// public +void kpCommandHistoryBase::setUndoMaxLimitSizeLimit (kpCommandSize::SizeType sizeLimit) +{ +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "kpCommandHistoryBase::setUndoMaxLimitSizeLimit(" + << sizeLimit << ")"; +#endif + + if (sizeLimit < 0 || + sizeLimit > (500 * 1048576)/*"ought to be enough for anybody"*/) + { + qCCritical(kpLogCommands) << "kpCommandHistoryBase::setUndoMaxLimitSizeLimit(" + << sizeLimit << ")"; + return; + } + + if (sizeLimit == m_undoMaxLimitSizeLimit) { + return; + } + + m_undoMaxLimitSizeLimit = sizeLimit; + trimCommandListsUpdateActions (); +} + + +// public +void kpCommandHistoryBase::readConfig () +{ +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "kpCommandHistoryBase::readConfig()"; +#endif + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupUndoRedo); + + setUndoMinLimit (cfg.readEntry (kpSettingUndoMinLimit, undoMinLimit ())); + setUndoMaxLimit (cfg.readEntry (kpSettingUndoMaxLimit, undoMaxLimit ())); + setUndoMaxLimitSizeLimit ( + cfg.readEntry (kpSettingUndoMaxLimitSizeLimit, + undoMaxLimitSizeLimit ())); + + trimCommandListsUpdateActions (); +} + +// public +void kpCommandHistoryBase::writeConfig () +{ +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "kpCommandHistoryBase::writeConfig()"; +#endif + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupUndoRedo); + + cfg.writeEntry (kpSettingUndoMinLimit, undoMinLimit ()); + cfg.writeEntry (kpSettingUndoMaxLimit, undoMaxLimit ()); + cfg.writeEntry ( + kpSettingUndoMaxLimitSizeLimit, undoMaxLimitSizeLimit ()); + + cfg.sync (); +} + + +// public +void kpCommandHistoryBase::addCommand (kpCommand *command, bool execute) +{ +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "kpCommandHistoryBase::addCommand(" + << command + << ",execute=" << execute << ")" +#endif + + if (execute) { + command->execute (); + } + + m_undoCommandList.push_front (command); + ::ClearPointerList(m_redoCommandList); + +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "\tdocumentRestoredPosition=" << m_documentRestoredPosition; +#endif + if (m_documentRestoredPosition != INT_MAX) + { + if (m_documentRestoredPosition > 0) { + m_documentRestoredPosition = INT_MAX; + } + else { + m_documentRestoredPosition--; + } + #if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "\t\tdocumentRestoredPosition=" << m_documentRestoredPosition; + #endif + } + + trimCommandListsUpdateActions (); +} + +// public +void kpCommandHistoryBase::clear () +{ +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "kpCommandHistoryBase::clear()"; +#endif + + ::ClearPointerList(m_undoCommandList); + ::ClearPointerList(m_redoCommandList); + + m_documentRestoredPosition = 0; + + updateActions (); +} + +//--------------------------------------------------------------------- + +// protected slot +void kpCommandHistoryBase::undoInternal () +{ +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "kpCommandHistoryBase::undoInternal()"; +#endif + + kpCommand *undoCommand = nextUndoCommand (); + if (!undoCommand) { + return; + } + + undoCommand->unexecute (); + + + m_undoCommandList.erase (m_undoCommandList.begin ()); + m_redoCommandList.push_front (undoCommand); + +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "\tdocumentRestoredPosition=" << m_documentRestoredPosition; +#endif + if (m_documentRestoredPosition != INT_MAX) + { + m_documentRestoredPosition++; + if (m_documentRestoredPosition == 0) + emit documentRestored (); + #if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "\t\tdocumentRestoredPosition=" << m_documentRestoredPosition; + #endif + } +} + +//--------------------------------------------------------------------- + +// protected slot +void kpCommandHistoryBase::redoInternal () +{ +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "kpCommandHistoryBase::redoInternal()"; +#endif + + kpCommand *redoCommand = nextRedoCommand (); + if (!redoCommand) { + return; + } + + redoCommand->execute (); + + + m_redoCommandList.erase (m_redoCommandList.begin ()); + m_undoCommandList.push_front (redoCommand); + +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "\tdocumentRestoredPosition=" << m_documentRestoredPosition; +#endif + if (m_documentRestoredPosition != INT_MAX) + { + m_documentRestoredPosition--; + if (m_documentRestoredPosition == 0) { + emit documentRestored (); + } + #if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "\t\tdocumentRestoredPosition=" << m_documentRestoredPosition; + #endif + } +} + +//--------------------------------------------------------------------- + +// public slot virtual +void kpCommandHistoryBase::undo () +{ +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "kpCommandHistoryBase::undo()"; +#endif + + undoInternal (); + trimCommandListsUpdateActions (); +} + +//--------------------------------------------------------------------- + +// public slot virtual +void kpCommandHistoryBase::redo () +{ +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "kpCommandHistoryBase::redo()"; +#endif + + redoInternal (); + trimCommandListsUpdateActions (); +} + +//--------------------------------------------------------------------- + +// public slot virtual +void kpCommandHistoryBase::undoUpToNumber (QAction *which) +{ +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "kpCommandHistoryBase::undoUpToNumber(" << which << ")"; +#endif + + for (int i = 0; + i <= which->data().toInt() && !m_undoCommandList.isEmpty (); + i++) + { + undoInternal (); + } + + trimCommandListsUpdateActions (); +} + +// public slot virtual +void kpCommandHistoryBase::redoUpToNumber (QAction *which) +{ +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "kpCommandHistoryBase::redoUpToNumber(" << which << ")"; +#endif + + for (int i = 0; + i <= which->data().toInt() && !m_redoCommandList.isEmpty (); + i++) + { + redoInternal (); + } + + trimCommandListsUpdateActions (); +} + + +// protected +QString kpCommandHistoryBase::undoActionText () const +{ + kpCommand *undoCommand = nextUndoCommand (); + + return (undoCommand) ? i18n ("&Undo: %1", undoCommand->name ()) : i18n ("&Undo"); +} + +// protected +QString kpCommandHistoryBase::redoActionText () const +{ + kpCommand *redoCommand = nextRedoCommand (); + + return (redoCommand) ? i18n ("&Redo: %1", redoCommand->name ()) : i18n ("&Redo"); +} + + +// protected +QString kpCommandHistoryBase::undoActionToolTip () const +{ + kpCommand *undoCommand = nextUndoCommand (); + + return (undoCommand) ? i18n ("Undo: %1", undoCommand->name ()) : i18n ("Undo"); +} + +// protected +QString kpCommandHistoryBase::redoActionToolTip () const +{ + kpCommand *redoCommand = nextRedoCommand (); + + return (redoCommand) ? i18n ("Redo: %1", redoCommand->name ()) : i18n ("Redo"); +} + + +// protected +void kpCommandHistoryBase::trimCommandListsUpdateActions () +{ +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "kpCommandHistoryBase::trimCommandListsUpdateActions()"; +#endif + + trimCommandLists (); + updateActions (); +} + +//-------------------------------------------------------------------------------- + +// protected +void kpCommandHistoryBase::trimCommandList(QList &commandList) +{ +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "kpCommandHistoryBase::trimCommandList()"; + QTime timer; timer.start (); + + qCDebug(kpLogCommands) << "\tsize=" << commandList.size() + << " undoMinLimit=" << m_undoMinLimit + << " undoMaxLimit=" << m_undoMaxLimit + << " undoMaxLimitSizeLimit=" << m_undoMaxLimitSizeLimit; +#endif + if ( commandList.size() <= m_undoMinLimit ) + { + #if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "\t\tsize under undoMinLimit - done"; + #endif + return; + } + + +#if DEBUG_KP_COMMAND_HISTORY && 0 + qCDebug(kpLogCommands) << "\tsize over undoMinLimit - iterating thru cmds:"; +#endif + + QList ::iterator it = commandList.begin (); + int upto = 0; + + kpCommandSize::SizeType sizeSoFar = 0; + + while (it != commandList.end ()) + { + bool advanceIt = true; + + if (sizeSoFar <= m_undoMaxLimitSizeLimit) + { + sizeSoFar += (*it)->size (); + } + + #if DEBUG_KP_COMMAND_HISTORY && 0 + qCDebug(kpLogCommands) << "\t\t" << upto << ":" + << " name='" << (*it)->name () + << "' size=" << (*it)->size () + << " sizeSoFar=" << sizeSoFar; + #endif + + if (upto >= m_undoMinLimit) + { + if (upto >= m_undoMaxLimit || + sizeSoFar > m_undoMaxLimitSizeLimit) + { + #if DEBUG_KP_COMMAND_HISTORY && 0 + qCDebug(kpLogCommands) << "\t\t\tkill"; + #endif + delete (*it); + it = m_undoCommandList.erase (it); + advanceIt = false; + } + } + + if (advanceIt) { + it++; + } + upto++; + } + +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "\ttook " << timer.elapsed () << "ms"; +#endif +} + +//-------------------------------------------------------------------------------- + +// protected +void kpCommandHistoryBase::trimCommandLists () +{ +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "kpCommandHistoryBase::trimCommandLists()"; +#endif + + trimCommandList(m_undoCommandList); + trimCommandList(m_redoCommandList); + +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "\tdocumentRestoredPosition=" << m_documentRestoredPosition +#endif + if (m_documentRestoredPosition != INT_MAX) + { + #if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "\t\tundoCmdList.size=" << m_undoCommandList.size () + << " redoCmdList.size=" << m_redoCommandList.size (); + #endif + if (m_documentRestoredPosition > static_cast (m_redoCommandList.size ()) || + -m_documentRestoredPosition > static_cast (m_undoCommandList.size ())) + { + #if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "\t\t\tinvalidate documentRestoredPosition"; + #endif + m_documentRestoredPosition = INT_MAX; + } + } +} + + +static void populatePopupMenu (QMenu *popupMenu, + const QString &undoOrRedo, + const QList &commandList) +{ + if (!popupMenu) { + return; + } + + popupMenu->clear (); + + QList ::const_iterator it = commandList.begin (); + int i = 0; + while (i < 10 && it != commandList.end ()) + { + QAction *action = new QAction(i18n ("%1: %2", undoOrRedo, (*it)->name ()), popupMenu); + action->setData(i); + popupMenu->addAction (action); + i++; + it++; + } + + if (it != commandList.end ()) + { + // TODO: maybe have a scrollview show all the items instead, like KOffice in KDE 3 + // LOCOMPAT: should be centered text. + popupMenu->addSection (i18np ("%1 more item", "%1 more items", + commandList.size () - i)); + } +} + + +// protected +void kpCommandHistoryBase::updateActions () +{ +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "kpCommandHistoryBase::updateActions()"; +#endif + + m_actionUndo->setEnabled (static_cast (nextUndoCommand ())); + // Don't want to keep changing toolbar text. + // TODO: As a bad side-effect, the menu doesn't have "Undo: " + // anymore. In any case, the KDE4 KToolBarPopupAction + // sucks in menus as it forces the clicking of a submenu. IMO, + // there should be no submenu in the menu. + //m_actionUndo->setText (undoActionText ()); + + // But in icon mode, a tooltip with context is useful. + m_actionUndo->setToolTip (undoActionToolTip ()); +#if DEBUG_KP_COMMAND_HISTORY + QTime timer; timer.start (); +#endif + populatePopupMenu (m_actionUndo->menu (), + i18n ("Undo"), + m_undoCommandList); +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "\tpopuplatePopupMenu undo=" << timer.elapsed () + << "ms"; +#endif + + m_actionRedo->setEnabled (static_cast (nextRedoCommand ())); + // Don't want to keep changing toolbar text. + // TODO: As a bad side-effect, the menu doesn't have "Undo: " + // anymore. In any case, the KDE4 KToolBarPopupAction + // sucks in menus as it forces the clicking of a submenu. IMO, + // there should be no submenu in the menu. + //m_actionRedo->setText (redoActionText ()); + + // But in icon mode, a tooltip with context is useful. + m_actionRedo->setToolTip (redoActionToolTip ()); +#if DEBUG_KP_COMMAND_HISTORY + timer.restart (); +#endif + populatePopupMenu (m_actionRedo->menu (), + i18n ("Redo"), + m_redoCommandList); +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "\tpopuplatePopupMenu redo=" << timer.elapsed () + << "ms"; +#endif +} + + +// public +kpCommand *kpCommandHistoryBase::nextUndoCommand () const +{ + if (m_undoCommandList.isEmpty ()) { + return nullptr; + } + + return m_undoCommandList.first (); +} + +// public +kpCommand *kpCommandHistoryBase::nextRedoCommand () const +{ + if (m_redoCommandList.isEmpty ()) { + return nullptr; + } + + return m_redoCommandList.first (); +} + + +// public +void kpCommandHistoryBase::setNextUndoCommand (kpCommand *command) +{ +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "kpCommandHistoryBase::setNextUndoCommand("<< command << ")"; +#endif + + if (m_undoCommandList.isEmpty ()) { + return; + } + + delete *m_undoCommandList.begin (); + *m_undoCommandList.begin () = command; + + trimCommandListsUpdateActions (); +} + + +// public slot virtual +void kpCommandHistoryBase::documentSaved () +{ +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "kpCommandHistoryBase::documentSaved()"; +#endif + + m_documentRestoredPosition = 0; +} + + diff --git a/commands/kpCommandHistoryBase.h b/commands/kpCommandHistoryBase.h new file mode 100644 index 0000000..971a75a --- /dev/null +++ b/commands/kpCommandHistoryBase.h @@ -0,0 +1,146 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpCommandHistoryBase_H +#define kpCommandHistoryBase_H + + +#include +#include +#include + + +#include "commands/kpCommandSize.h" + +class QAction; + +class KActionCollection; +class KToolBarPopupAction; + +class kpCommand; + + +// Clone of KCommandHistory with features required by KolourPaint but which +// could also be useful for other apps: +// - nextUndoCommand()/nextRedoCommand() +// - undo/redo history limited by both number and size +// +// Features not required by KolourPaint (e.g. commandExecuted()) are not +// implemented and undo limit == redo limit. So compared to +// KCommandHistory, this is only "almost source compatible". +class kpCommandHistoryBase : public QObject +{ +Q_OBJECT + +public: + kpCommandHistoryBase (bool doReadConfig, KActionCollection *ac); + ~kpCommandHistoryBase () override; + +public: + // (provided for compatibility with KCommandHistory) + int undoLimit () const; + void setUndoLimit (int limit); + + + int undoMinLimit () const; + void setUndoMinLimit (int limit); + + int undoMaxLimit () const; + void setUndoMaxLimit (int limit); + + kpCommandSize::SizeType undoMaxLimitSizeLimit () const; + void setUndoMaxLimitSizeLimit (kpCommandSize::SizeType sizeLimit); + +public: + // Read and write above config + void readConfig (); + void writeConfig (); + +public: + void addCommand (kpCommand *command, bool execute = true); + void clear (); + +protected slots: + // (same as undo() & redo() except they don't call + // trimCommandListsUpdateActions()) + void undoInternal (); + void redoInternal (); + +public slots: + virtual void undo (); + virtual void redo (); + + virtual void undoUpToNumber (QAction *which); + virtual void redoUpToNumber (QAction *which); + +protected: + QString undoActionText () const; + QString redoActionText () const; + + QString undoActionToolTip () const; + QString redoActionToolTip () const; + + void trimCommandListsUpdateActions (); + void trimCommandList(QList &commandList); + void trimCommandLists (); + void updateActions (); + +public: + kpCommand *nextUndoCommand () const; + kpCommand *nextRedoCommand () const; + + void setNextUndoCommand (kpCommand *command); + +public slots: + virtual void documentSaved (); + +signals: + void documentRestored (); + +protected: + KToolBarPopupAction *m_actionUndo, *m_actionRedo; + + // (Front element is the next one) + QList m_undoCommandList; + QList m_redoCommandList; + + int m_undoMinLimit, m_undoMaxLimit; + kpCommandSize::SizeType m_undoMaxLimitSizeLimit; + + // What you have to do to get back to the document's unmodified state: + // * -x: must Undo x times + // * 0: unmodified + // * +x: must Redo x times + // * INT_MAX: can never become unmodified again + // + // ASSUMPTION: will never have INT_MAX commands in any list. + int m_documentRestoredPosition; +}; + + +#endif // kpCommandHistoryBase_H diff --git a/commands/kpCommandSize.cpp b/commands/kpCommandSize.cpp new file mode 100644 index 0000000..74d7ca2 --- /dev/null +++ b/commands/kpCommandSize.cpp @@ -0,0 +1,152 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_COMMAND_SIZE 0 + + +#include "commands/kpCommandSize.h" +#include "layers/selections/kpAbstractSelection.h" + +#include +#include +#include + + +// public static +kpCommandSize::SizeType kpCommandSize::PixmapSize (const QImage &image) +{ + return kpCommandSize::PixmapSize (image.width (), image.height (), image.depth ()); +} + +// public static +kpCommandSize::SizeType kpCommandSize::PixmapSize (const QImage *image) +{ + return (image ? kpCommandSize::PixmapSize (*image) : 0); +} + +// public static +kpCommandSize::SizeType kpCommandSize::PixmapSize (int width, int height, int depth) +{ + // handle 15bpp + int roundedDepth = (depth > 8 ? (depth + 7) / 8 * 8 : depth); + kpCommandSize::SizeType ret = + static_cast (width) * height * roundedDepth / 8; + +#if DEBUG_KP_COMMAND_SIZE && 0 + qCDebug(kpLogCommands) << "kpCommandSize::PixmapSize() w=" << width + << " h=" << height + << " d=" << depth + << " roundedDepth=" << roundedDepth + << " ret=" << ret; +#endif + return ret; +} + + +// public static +kpCommandSize::SizeType kpCommandSize::QImageSize (const QImage &image) +{ + return kpCommandSize::QImageSize (image.width (), image.height (), image.depth ()); +} + +// public static +kpCommandSize::SizeType kpCommandSize::QImageSize (const QImage *image) +{ + return (image ? kpCommandSize::QImageSize (*image) : 0); +} + +// public static +kpCommandSize::SizeType kpCommandSize::QImageSize (int width, int height, int depth) +{ + // handle 15bpp + int roundedDepth = (depth > 8 ? (depth + 7) / 8 * 8 : depth); + kpCommandSize::SizeType ret = + static_cast (width) * height * roundedDepth / 8; + +#if DEBUG_KP_COMMAND_SIZE && 0 + qCDebug(kpLogCommands) << "kpCommandSize::QImageSize() w=" << width + << " h=" << height + << " d=" << depth + << " roundedDepth=" << roundedDepth + << " ret=" << ret; +#endif + + return ret; +} + + +// public static +kpCommandSize::SizeType kpCommandSize::ImageSize (const kpImage &image) +{ + return kpCommandSize::PixmapSize (image); +} + +// public static +kpCommandSize::SizeType kpCommandSize::ImageSize (const kpImage *image) +{ + return kpCommandSize::PixmapSize (image); +} + + +// public static +kpCommandSize::SizeType kpCommandSize::SelectionSize (const kpAbstractSelection &sel) +{ + return sel.size (); +} + +// public static +kpCommandSize::SizeType kpCommandSize::SelectionSize (const kpAbstractSelection *sel) +{ + return (sel ? sel->size () : 0); +} + + +// public static +kpCommandSize::SizeType kpCommandSize::StringSize (const QString &string) +{ +#if DEBUG_KP_COMMAND_SIZE && 1 + qCDebug(kpLogCommands) << "kpCommandSize::StringSize(" << string << ")" + << " len=" << string.length () + << " sizeof(QChar)=" << sizeof (QChar); +#endif + return static_cast (static_cast (string.length ()) * sizeof (QChar)); +} + + +// public static +kpCommandSize::SizeType kpCommandSize::PolygonSize (const QPolygon &points) +{ +#if DEBUG_KP_COMMAND_SIZE && 1 + qCDebug(kpLogCommands) << "kpCommandSize::PolygonSize() points.size=" + << points.size () + << " sizeof(QPoint)=" << sizeof (QPoint); +#endif + + return static_cast (static_cast (points.size ()) * sizeof (QPoint)); +} + diff --git a/commands/kpCommandSize.h b/commands/kpCommandSize.h new file mode 100644 index 0000000..c6594b5 --- /dev/null +++ b/commands/kpCommandSize.h @@ -0,0 +1,87 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpCommandSize_H +#define kpCommandSize_H + + +#include "imagelib/kpImage.h" + + +class QImage; +class QPolygon; +class QString; + +class kpAbstractSelection; + + +// +// Estimates the size of the object being pointed to, in bytes. +// +// This is used by the command history to trim stored commands, once a +// certain amount of memory is used by those commands. +// +class kpCommandSize +{ +public: + // Force 64-bit arithmetic, instead of 32-bit, to prevent overflow + // when determining whether to clip the command history -- we might be + // adding a large number of large sizes. This will eventually help + // KolourPaint support more than 2GB of image data. + // + // For some reason, GCC doesn't warn of accidental casts to smaller types + // (e.g. 32-bit). An easy way to get around this is to change "SizeType" + // to be "double" temporarily and recompile - every time an implicit cast to + // "int" (32-bit) is made, we'll be warned. + // + // TODO: Exhaustively test that we're not accidentally doing intermediate + // calculations using 32-bit in some places (mainly inside + // implementations of kpCommand::size()). + typedef qlonglong SizeType; + + static SizeType PixmapSize (const QImage &image); + static SizeType PixmapSize (const QImage *image); + static SizeType PixmapSize (int width, int height, int depth); + + static SizeType QImageSize (const QImage &image); + static SizeType QImageSize (const QImage *image); + static SizeType QImageSize (int width, int height, int depth); + + static SizeType ImageSize (const kpImage &image); + static SizeType ImageSize (const kpImage *image); + + static SizeType SelectionSize (const kpAbstractSelection &sel); + static SizeType SelectionSize (const kpAbstractSelection *sel); + + static SizeType StringSize (const QString &string); + + static SizeType PolygonSize (const QPolygon &points); +}; + + +#endif // kpCommandSize_H diff --git a/commands/kpMacroCommand.cpp b/commands/kpMacroCommand.cpp new file mode 100644 index 0000000..4221b35 --- /dev/null +++ b/commands/kpMacroCommand.cpp @@ -0,0 +1,133 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_COMMAND_HISTORY 0 + + +#include "commands/kpMacroCommand.h" +#include "views/manager/kpViewManager.h" + +#include + +#include + +//--------------------------------------------------------------------- + +kpMacroCommand::kpMacroCommand (const QString &name, kpCommandEnvironment *environ) + : kpNamedCommand (name, environ) +{ +} + +//--------------------------------------------------------------------- + +kpMacroCommand::~kpMacroCommand () +{ + qDeleteAll(m_commandList); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpMacroCommand::size () const +{ +#if DEBUG_KP_COMMAND_HISTORY && 0 + qCDebug(kpLogCommands) << "kpMacroCommand::size()"; +#endif + SizeType s = 0; + +#if DEBUG_KP_COMMAND_HISTORY && 0 + qCDebug(kpLogCommands) << "\tcalculating:"; +#endif + foreach (kpCommand *cmd, m_commandList) + { + #if DEBUG_KP_COMMAND_HISTORY && 0 + qCDebug(kpLogCommands) << "\t\tcurrentSize=" << s << " + " + << cmd->name () << ".size=" << cmd->size (); + #endif + s += cmd->size (); + } + +#if DEBUG_KP_COMMAND_HISTORY && 0 + qCDebug(kpLogCommands) << "\treturning " << s; +#endif + return s; +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +void kpMacroCommand::execute () +{ +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "kpMacroCommand::execute()"; +#endif + + viewManager()->setQueueUpdates(); + + foreach (kpCommand *command, m_commandList) + { + #if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "\texecuting " << command->name(); + #endif + command->execute(); + } + + viewManager()->restoreQueueUpdates(); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +void kpMacroCommand::unexecute () +{ +#if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "kpMacroCommand::unexecute()"; +#endif + + viewManager()->setQueueUpdates(); + + for (int i = m_commandList.count() - 1; i >= 0; i--) + { + #if DEBUG_KP_COMMAND_HISTORY + qCDebug(kpLogCommands) << "\tunexecuting " << m_commandList[i]->name(); + #endif + m_commandList[i]->unexecute(); + } + + viewManager()->restoreQueueUpdates(); +} + +//--------------------------------------------------------------------- + +// public +void kpMacroCommand::addCommand(kpCommand *command) +{ + m_commandList.append(command); +} + +//--------------------------------------------------------------------- diff --git a/commands/kpMacroCommand.h b/commands/kpMacroCommand.h new file mode 100644 index 0000000..84ca09b --- /dev/null +++ b/commands/kpMacroCommand.h @@ -0,0 +1,66 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpMacroCommand_H +#define kpMacroCommand_H + + +#include "commands/kpNamedCommand.h" + +#include + + +class kpMacroCommand : public kpNamedCommand +{ +public: + kpMacroCommand (const QString &name, kpCommandEnvironment *environ); + ~kpMacroCommand () override; + + + // + // kpCommand Interface + // + + SizeType size () const override; + + void execute () override; + void unexecute () override; + + + // + // Interface + // + + void addCommand (kpCommand *command); + +protected: + QList m_commandList; +}; + + +#endif // kpMacroCommand_H diff --git a/commands/kpNamedCommand.cpp b/commands/kpNamedCommand.cpp new file mode 100644 index 0000000..c903626 --- /dev/null +++ b/commands/kpNamedCommand.cpp @@ -0,0 +1,45 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "commands/kpNamedCommand.h" + +//--------------------------------------------------------------------- + +kpNamedCommand::kpNamedCommand (const QString &name, kpCommandEnvironment *environ) + : kpCommand (environ), + m_name (name) +{ +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +QString kpNamedCommand::name () const +{ + return m_name; +} + +//--------------------------------------------------------------------- diff --git a/commands/kpNamedCommand.h b/commands/kpNamedCommand.h new file mode 100644 index 0000000..183e458 --- /dev/null +++ b/commands/kpNamedCommand.h @@ -0,0 +1,50 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpNamedCommand_H +#define kpNamedCommand_H + + +#include "commands/kpCommand.h" + +#include + + +class kpNamedCommand : public kpCommand +{ +public: + kpNamedCommand (const QString &name, kpCommandEnvironment *environ); + + QString name () const override; + +protected: + QString m_name; +}; + + +#endif // kpNamedCommand_H diff --git a/commands/tools/flow/kpToolFlowCommand.cpp b/commands/tools/flow/kpToolFlowCommand.cpp new file mode 100644 index 0000000..7982735 --- /dev/null +++ b/commands/tools/flow/kpToolFlowCommand.cpp @@ -0,0 +1,140 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_FLOW_COMMAND 0 + + +#include "kpToolFlowCommand.h" + +#include "document/kpDocument.h" +#include "imagelib/kpImage.h" +#include "pixmapfx/kpPixmapFX.h" +#include "tools/kpTool.h" +#include "views/manager/kpViewManager.h" + +#include + + +struct kpToolFlowCommandPrivate +{ + kpImage image; + QRect boundingRect; +}; + + +kpToolFlowCommand::kpToolFlowCommand (const QString &name, kpCommandEnvironment *environ) + : kpNamedCommand (name, environ), + d (new kpToolFlowCommandPrivate ()) +{ + d->image = document ()->image (); +} + +kpToolFlowCommand::~kpToolFlowCommand () +{ + delete d; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolFlowCommand::size () const +{ + return ImageSize (d->image); +} + + +// public virtual [base kpCommand] +void kpToolFlowCommand::execute () +{ + swapOldAndNew (); +} + +// public virtual [base kpCommand] +void kpToolFlowCommand::unexecute () +{ + swapOldAndNew (); +} + + +// private +void kpToolFlowCommand::swapOldAndNew () +{ + if (d->boundingRect.isValid ()) + { + const kpImage oldImage = document ()->getImageAt (d->boundingRect); + + document ()->setImageAt (d->image, d->boundingRect.topLeft ()); + + d->image = oldImage; + } +} + +// public +void kpToolFlowCommand::updateBoundingRect (const QPoint &point) +{ + updateBoundingRect (QRect (point, point)); +} + +// public +void kpToolFlowCommand::updateBoundingRect (const QRect &rect) +{ +#if DEBUG_KP_TOOL_FLOW_COMMAND & 0 + qCDebug(kpLogCommands) << "kpToolFlowCommand::updateBoundingRect() existing=" + << d->boundingRect + << " plus=" + << rect; +#endif + d->boundingRect = d->boundingRect.united (rect); +#if DEBUG_KP_TOOL_FLOW_COMMAND & 0 + qCDebug(kpLogCommands) << "\tresult=" << d->boundingRect; +#endif +} + +// public +void kpToolFlowCommand::finalize () +{ + if (d->boundingRect.isValid ()) + { + // Store only the needed part of doc image. + d->image = kpTool::neededPixmap (d->image, d->boundingRect); + } + else + { + d->image = kpImage (); + } +} + +// public +void kpToolFlowCommand::cancel () +{ + if (d->boundingRect.isValid ()) + { + viewManager ()->setFastUpdates (); + document ()->setImageAt (d->image, d->boundingRect.topLeft ()); + viewManager ()->restoreFastUpdates (); + } +} diff --git a/commands/tools/flow/kpToolFlowCommand.h b/commands/tools/flow/kpToolFlowCommand.h new file mode 100644 index 0000000..f63d74c --- /dev/null +++ b/commands/tools/flow/kpToolFlowCommand.h @@ -0,0 +1,64 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_FLOW_COMMAND_H +#define KP_TOOL_FLOW_COMMAND_H + + +#include "commands/kpNamedCommand.h" + + +class QPoint; +class QRect; + + +class kpToolFlowCommand : public kpNamedCommand +{ +public: + kpToolFlowCommand (const QString &name, kpCommandEnvironment *environ); + ~kpToolFlowCommand () override; + + kpCommandSize::SizeType size () const override; + + void execute () override; + void unexecute () override; + + // interface for kpToolFlowBase + void updateBoundingRect (const QPoint &point); + void updateBoundingRect (const QRect &rect); + void finalize (); + void cancel (); + +private: + void swapOldAndNew (); + + struct kpToolFlowCommandPrivate * const d; +}; + + +#endif // KP_TOOL_FLOW_COMMAND_H diff --git a/commands/tools/kpToolColorPickerCommand.cpp b/commands/tools/kpToolColorPickerCommand.cpp new file mode 100644 index 0000000..5173fd6 --- /dev/null +++ b/commands/tools/kpToolColorPickerCommand.cpp @@ -0,0 +1,81 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_COLOR_PICKER 0 + + +#include "kpToolColorPickerCommand.h" + +#include "environments/commands/kpCommandEnvironment.h" +#include "kpDefs.h" + +#include + + +kpToolColorPickerCommand::kpToolColorPickerCommand ( + int mouseButton, + const kpColor &newColor, + const kpColor &oldColor, + kpCommandEnvironment *environ) + + : kpCommand (environ), + m_mouseButton (mouseButton), + m_newColor (newColor), + m_oldColor (oldColor) +{ +} + +kpToolColorPickerCommand::~kpToolColorPickerCommand () = default; + + +// public virtual [base kpCommand] +QString kpToolColorPickerCommand::name () const +{ + return i18n ("Color Picker"); +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolColorPickerCommand::size () const +{ + return 0; +} + + +// public virtual [base kpCommand] +void kpToolColorPickerCommand::execute () +{ + environ ()->setColor (m_mouseButton, m_newColor); +} + +// public virtual [base kpCommand] +void kpToolColorPickerCommand::unexecute () +{ + environ ()->setColor (m_mouseButton, m_oldColor); +} + diff --git a/commands/tools/kpToolColorPickerCommand.h b/commands/tools/kpToolColorPickerCommand.h new file mode 100644 index 0000000..9dca6aa --- /dev/null +++ b/commands/tools/kpToolColorPickerCommand.h @@ -0,0 +1,59 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolColorPickerCommand_H +#define kpToolColorPickerCommand_H + + +#include "imagelib/kpColor.h" +#include "commands/kpCommand.h" + + +class kpToolColorPickerCommand : public kpCommand +{ +public: + kpToolColorPickerCommand (int mouseButton, + const kpColor &newColor, const kpColor &oldColor, + kpCommandEnvironment *environ); + ~kpToolColorPickerCommand () override; + + QString name () const override; + + SizeType size () const override; + + void execute () override; + void unexecute () override; + +private: + int m_mouseButton; + kpColor m_newColor; + kpColor m_oldColor; +}; + + +#endif // kpToolColorPickerCommand_H diff --git a/commands/tools/kpToolFloodFillCommand.cpp b/commands/tools/kpToolFloodFillCommand.cpp new file mode 100644 index 0000000..bcd54fb --- /dev/null +++ b/commands/tools/kpToolFloodFillCommand.cpp @@ -0,0 +1,169 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_FLOOD_FILL 0 + + +#include "kpToolFloodFillCommand.h" + +#include "imagelib/kpColor.h" +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "imagelib/kpImage.h" +#include "kpLogCategories.h" + +#include + +#include + +//--------------------------------------------------------------------- + +struct kpToolFloodFillCommandPrivate +{ + kpImage oldImage; + bool fillEntireImage{false}; +}; + +//--------------------------------------------------------------------- + +kpToolFloodFillCommand::kpToolFloodFillCommand (int x, int y, + const kpColor &color, int processedColorSimilarity, + kpCommandEnvironment *environ) + + : kpCommand (environ), + kpFloodFill (document ()->imagePointer (), x, y, color, processedColorSimilarity), + d (new kpToolFloodFillCommandPrivate ()) +{ + d->fillEntireImage = false; +} + +//--------------------------------------------------------------------- + +kpToolFloodFillCommand::~kpToolFloodFillCommand () +{ + delete d; +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +QString kpToolFloodFillCommand::name () const +{ + return i18n ("Flood Fill"); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolFloodFillCommand::size () const +{ + return kpFloodFill::size () + ImageSize (d->oldImage); +} + +//--------------------------------------------------------------------- + +// public +void kpToolFloodFillCommand::setFillEntireImage (bool yes) +{ + d->fillEntireImage = yes; +} + +//--------------------------------------------------------------------- + +// protected virtual [base kpCommand] +void kpToolFloodFillCommand::execute () +{ +#if DEBUG_KP_TOOL_FLOOD_FILL && 1 + qCDebug(kpLogCommands) << "kpToolFloodFillCommand::execute() fillEntireImage=" + << d->fillEntireImage; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + + + if (d->fillEntireImage) + { + doc->fill (kpFloodFill::color ()); + } + else + { + QRect rect = kpFloodFill::boundingRect (); + if (rect.isValid ()) + { + QApplication::setOverrideCursor (Qt::WaitCursor); + { + d->oldImage = doc->getImageAt (rect); + + kpFloodFill::fill (); + doc->slotContentsChanged (rect); + } + QApplication::restoreOverrideCursor (); + } + else + { + #if DEBUG_KP_TOOL_FLOOD_FILL && 1 + qCDebug(kpLogCommands) << "\tinvalid boundingRect - must be NOP case"; + #endif + } + } +} + +//--------------------------------------------------------------------- + +// protected virtual [base kpCommand] +void kpToolFloodFillCommand::unexecute () +{ +#if DEBUG_KP_TOOL_FLOOD_FILL && 1 + qCDebug(kpLogCommands) << "kpToolFloodFillCommand::unexecute() fillEntireImage=" + << d->fillEntireImage; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + + + if (d->fillEntireImage) + { + doc->fill (kpFloodFill::colorToChange ()); + } + else + { + QRect rect = kpFloodFill::boundingRect (); + if (rect.isValid ()) + { + doc->setImageAt (d->oldImage, rect.topLeft ()); + + d->oldImage = kpImage (); + + doc->slotContentsChanged (rect); + } + } +} + +//--------------------------------------------------------------------- diff --git a/commands/tools/kpToolFloodFillCommand.h b/commands/tools/kpToolFloodFillCommand.h new file mode 100644 index 0000000..ca558a9 --- /dev/null +++ b/commands/tools/kpToolFloodFillCommand.h @@ -0,0 +1,68 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolFloodFillCommand_H +#define kpToolFloodFillCommand_H + + +#include "commands/kpCommand.h" +#include "imagelib/kpFloodFill.h" + + +class kpColor; +class kpCommandEnvironment; + + +struct kpToolFloodFillCommandPrivate; + +class kpToolFloodFillCommand : public kpCommand, public kpFloodFill +{ +public: + kpToolFloodFillCommand (int x, int y, + const kpColor &color, int processedColorSimilarity, + kpCommandEnvironment *environ); + ~kpToolFloodFillCommand () override; + + QString name () const override; + + kpCommandSize::SizeType size () const override; + + // Optimization hack: filling a fresh, unmodified document does not require + // reading any pixels - just set the whole document to + // . + void setFillEntireImage (bool yes = true); + + void execute () override; + void unexecute () override; + +private: + kpToolFloodFillCommandPrivate * const d; +}; + + +#endif // kpToolFloodFillCommand_H diff --git a/commands/tools/polygonal/kpToolPolygonalCommand.cpp b/commands/tools/polygonal/kpToolPolygonalCommand.cpp new file mode 100644 index 0000000..af771d5 --- /dev/null +++ b/commands/tools/polygonal/kpToolPolygonalCommand.cpp @@ -0,0 +1,123 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_POLYGON 0 + + +#include "kpToolPolygonalCommand.h" + +#include "document/kpDocument.h" +#include "kpDefs.h" +#include "imagelib/kpImage.h" + + +struct kpToolPolygonalCommandPrivate +{ + kpToolPolygonalBase::DrawShapeFunc drawShapeFunc{}; + + QPolygon points; + QRect boundingRect; + + kpColor fcolor; + int penWidth{}; + kpColor bcolor; + + kpImage oldImage; +}; + +kpToolPolygonalCommand::kpToolPolygonalCommand (const QString &name, + kpToolPolygonalBase::DrawShapeFunc drawShapeFunc, + const QPolygon &points, + const QRect &boundingRect, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor, + kpCommandEnvironment *environ) + + : kpNamedCommand (name, environ), + d (new kpToolPolygonalCommandPrivate ()) +{ + d->drawShapeFunc = drawShapeFunc; + + d->points = points; + d->boundingRect = boundingRect; + + d->fcolor = fcolor; + d->penWidth = penWidth; + d->bcolor = bcolor; +} + +kpToolPolygonalCommand::~kpToolPolygonalCommand () +{ + delete d; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolPolygonalCommand::size () const +{ + return PolygonSize (d->points) + + ImageSize (d->oldImage); +} + +// public virtual [base kpCommand] +void kpToolPolygonalCommand::execute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + // Store Undo info. + Q_ASSERT (d->oldImage.isNull ()); + d->oldImage = doc->getImageAt (d->boundingRect); + + // Invoke shape drawing function passed in ctor. + kpImage image = d->oldImage; + + QPolygon pointsTranslated = d->points; + pointsTranslated.translate (-d->boundingRect.x (), -d->boundingRect.y ()); + + (*d->drawShapeFunc) (&image, + pointsTranslated, + d->fcolor, d->penWidth, + d->bcolor, + true/*final shape*/); + + doc->setImageAt (image, d->boundingRect.topLeft ()); +} + +// public virtual [base kpCommand] +void kpToolPolygonalCommand::unexecute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + Q_ASSERT (!d->oldImage.isNull ()); + doc->setImageAt (d->oldImage, d->boundingRect.topLeft ()); + + d->oldImage = kpImage (); +} + diff --git a/commands/tools/polygonal/kpToolPolygonalCommand.h b/commands/tools/polygonal/kpToolPolygonalCommand.h new file mode 100644 index 0000000..7032713 --- /dev/null +++ b/commands/tools/polygonal/kpToolPolygonalCommand.h @@ -0,0 +1,68 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolPolygonalCommand_H +#define kpToolPolygonalCommand_H + + +#include "commands/kpNamedCommand.h" +#include "tools/polygonal/kpToolPolygonalBase.h" + + +class QPolygon; +class QRect; + +class kpColor; + + +// TODO: merge with kpToolRectangularCommand due to code duplication. +class kpToolPolygonalCommand : public kpNamedCommand +{ +public: + // = the bounding rectangle for including . + kpToolPolygonalCommand (const QString &name, + kpToolPolygonalBase::DrawShapeFunc drawShapeFunc, + const QPolygon &points, + const QRect &boundingRect, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor, + kpCommandEnvironment *environ); + ~kpToolPolygonalCommand () override; + + kpCommandSize::SizeType size () const override; + + void execute () override; + void unexecute () override; + +private: + struct kpToolPolygonalCommandPrivate * const d; + kpToolPolygonalCommand &operator= (const kpToolPolygonalCommand &) const; +}; + + +#endif // kpToolPolygonalCommand_H diff --git a/commands/tools/rectangular/kpToolRectangularCommand.cpp b/commands/tools/rectangular/kpToolRectangularCommand.cpp new file mode 100644 index 0000000..7cc2734 --- /dev/null +++ b/commands/tools/rectangular/kpToolRectangularCommand.cpp @@ -0,0 +1,126 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_RECTANGULAR_COMMAND 0 + + +#include "kpToolRectangularCommand.h" + +#include "imagelib/kpColor.h" +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "imagelib/kpPainter.h" +#include "pixmapfx/kpPixmapFX.h" +#include "layers/tempImage/kpTempImage.h" +#include "widgets/toolbars/kpToolToolBar.h" +#include "widgets/toolbars/options/kpToolWidgetFillStyle.h" +#include "widgets/toolbars/options/kpToolWidgetLineWidth.h" +#include "views/kpView.h" +#include "views/manager/kpViewManager.h" +#include "kpLogCategories.h" + + +struct kpToolRectangularCommandPrivate +{ + kpToolRectangularBase::DrawShapeFunc drawShapeFunc{}; + + QRect rect; + + kpColor fcolor; + int penWidth{}; + kpColor bcolor; + + kpImage oldImage; +}; + +kpToolRectangularCommand::kpToolRectangularCommand (const QString &name, + kpToolRectangularBase::DrawShapeFunc drawShapeFunc, + const QRect &rect, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor, + kpCommandEnvironment *environ) + + : kpNamedCommand (name, environ), + d (new kpToolRectangularCommandPrivate ()) +{ + d->drawShapeFunc = drawShapeFunc; + + d->rect = rect; + + d->fcolor = fcolor; + d->penWidth = penWidth; + d->bcolor = bcolor; +} + +kpToolRectangularCommand::~kpToolRectangularCommand () +{ + delete d; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolRectangularCommand::size () const +{ + return ImageSize (d->oldImage); +} + + +// public virtual [base kpCommand] +void kpToolRectangularCommand::execute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + // Store Undo info. + // OPT: For a pure rectangle, can do better if there is no bcolor, by only + // saving 4 pixmaps corresponding to the pixels dirtied by the 4 edges. + Q_ASSERT (d->oldImage.isNull ()); + d->oldImage = doc->getImageAt (d->rect); + + // Invoke shape drawing function passed in ctor. + kpImage image = d->oldImage; + (*d->drawShapeFunc) (&image, + 0, 0, d->rect.width (), d->rect.height (), + d->fcolor, d->penWidth, + d->bcolor); + + doc->setImageAt (image, d->rect.topLeft ()); +} + +// public virtual [base kpCommand] +void kpToolRectangularCommand::unexecute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + Q_ASSERT (!d->oldImage.isNull ()); + doc->setImageAt (d->oldImage, d->rect.topLeft ()); + + d->oldImage = kpImage (); +} + diff --git a/commands/tools/rectangular/kpToolRectangularCommand.h b/commands/tools/rectangular/kpToolRectangularCommand.h new file mode 100644 index 0000000..5406478 --- /dev/null +++ b/commands/tools/rectangular/kpToolRectangularCommand.h @@ -0,0 +1,62 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_RECTANGULAR_COMMAND_H +#define KP_TOOL_RECTANGULAR_COMMAND_H + + +#include "commands/kpNamedCommand.h" +#include "tools/rectangular/kpToolRectangularBase.h" + + +class kpColor; + + +class kpToolRectangularCommand : public kpNamedCommand +{ +public: + kpToolRectangularCommand (const QString &name, + kpToolRectangularBase::DrawShapeFunc drawShapeFunc, + const QRect &rect, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor, + kpCommandEnvironment *environ); + ~kpToolRectangularCommand () override; + + kpCommandSize::SizeType size () const override; + + void execute () override; + void unexecute () override; + +private: + struct kpToolRectangularCommandPrivate * const d; + kpToolRectangularCommand &operator= (const kpToolRectangularCommand &) const; +}; + + +#endif // KP_TOOL_RECTANGULAR_COMMAND_H diff --git a/commands/tools/selection/kpAbstractSelectionContentCommand.cpp b/commands/tools/selection/kpAbstractSelectionContentCommand.cpp new file mode 100644 index 0000000..333d183 --- /dev/null +++ b/commands/tools/selection/kpAbstractSelectionContentCommand.cpp @@ -0,0 +1,69 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "kpAbstractSelectionContentCommand.h" + +#include "layers/selections/kpAbstractSelection.h" + + +struct kpAbstractSelectionContentCommandPrivate +{ + const kpAbstractSelection *orgSelBorder; +}; + +kpAbstractSelectionContentCommand::kpAbstractSelectionContentCommand ( + const kpAbstractSelection &originalSelBorder, + const QString &name, + kpCommandEnvironment *environ) + : kpNamedCommand (name, environ), + d (new kpAbstractSelectionContentCommandPrivate ()) +{ + Q_ASSERT (!originalSelBorder.hasContent ()); + + d->orgSelBorder = originalSelBorder.clone (); +} + +kpAbstractSelectionContentCommand::~kpAbstractSelectionContentCommand () +{ + delete d->orgSelBorder; + delete d; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpAbstractSelectionContentCommand::size () const +{ + return d->orgSelBorder->size (); +} + + +// public +const kpAbstractSelection *kpAbstractSelectionContentCommand::originalSelection () const +{ + return d->orgSelBorder; +} diff --git a/commands/tools/selection/kpAbstractSelectionContentCommand.h b/commands/tools/selection/kpAbstractSelectionContentCommand.h new file mode 100644 index 0000000..c81889d --- /dev/null +++ b/commands/tools/selection/kpAbstractSelectionContentCommand.h @@ -0,0 +1,71 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpAbstractSelectionContentCommand_H +#define kpAbstractSelectionContentCommand_H + + +#include "commands/kpNamedCommand.h" + + +// Converts a selection border to a selection with content. +// This must be executed before any manipulations can be made +// to a selection. +// +// Its construction and execution always follows that of a +// kpToolSelectionCreateCommand, which must be given a selection with +// no content. +// +// It's always the first subcommand of a kpMacroCommand, with the following +// subcommands being whatever the selection operation is (e.g. movement, +// resizing). +class kpAbstractSelectionContentCommand : public kpNamedCommand +{ +// LOREFACTOR: Pull up more methods into here? Looking at the code, not +// much could be dragged up without unnecessarily complicated +// abstraction. +public: + // must be a border i.e. have no content. + kpAbstractSelectionContentCommand ( + const kpAbstractSelection &originalSelBorder, + const QString &name, + kpCommandEnvironment *environ); + ~kpAbstractSelectionContentCommand () override; + + kpCommandSize::SizeType size () const override; + + // Note: Returned pointer is only valid for as long as this command is + // alive. + const kpAbstractSelection *originalSelection () const; + +private: + struct kpAbstractSelectionContentCommandPrivate * const d; +}; + + +#endif // kpAbstractSelectionContentCommand_H diff --git a/commands/tools/selection/kpToolImageSelectionTransparencyCommand.cpp b/commands/tools/selection/kpToolImageSelectionTransparencyCommand.cpp new file mode 100644 index 0000000..2ef4595 --- /dev/null +++ b/commands/tools/selection/kpToolImageSelectionTransparencyCommand.cpp @@ -0,0 +1,96 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SELECTION 0 + + +#include "kpToolImageSelectionTransparencyCommand.h" + +#include "environments/commands/kpCommandEnvironment.h" +#include "kpDefs.h" +#include "generic/kpSetOverrideCursorSaver.h" +#include "kpLogCategories.h" +#include "layers/selections/image/kpAbstractImageSelection.h" + +#include + +//-------------------------------------------------------------------------------- + +kpToolImageSelectionTransparencyCommand::kpToolImageSelectionTransparencyCommand ( + const QString &name, + const kpImageSelectionTransparency &st, + const kpImageSelectionTransparency &oldST, + kpCommandEnvironment *environ) + : kpNamedCommand (name, environ), + m_st (st), + m_oldST (oldST) +{ +} + +kpToolImageSelectionTransparencyCommand::~kpToolImageSelectionTransparencyCommand () = default; + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolImageSelectionTransparencyCommand::size () const +{ + return 0; +} + + +// public virtual [base kpCommand] +void kpToolImageSelectionTransparencyCommand::execute () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + qCDebug(kpLogCommands) << "kpToolImageSelectionTransparencyCommand::execute()"; +#endif + + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + environ ()->setImageSelectionTransparency (m_st, true/*force colour change*/); + + if (imageSelection ()) { + imageSelection ()->setTransparency (m_st); + } +} + +// public virtual [base kpCommand] +void kpToolImageSelectionTransparencyCommand::unexecute () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + qCDebug(kpLogCommands) << "kpToolImageSelectionTransparencyCommand::unexecute()"; +#endif + + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + environ ()->setImageSelectionTransparency (m_oldST, true/*force colour change*/); + + if (imageSelection ()) { + imageSelection ()->setTransparency (m_oldST); + } +} + diff --git a/commands/tools/selection/kpToolImageSelectionTransparencyCommand.h b/commands/tools/selection/kpToolImageSelectionTransparencyCommand.h new file mode 100644 index 0000000..a4f2d20 --- /dev/null +++ b/commands/tools/selection/kpToolImageSelectionTransparencyCommand.h @@ -0,0 +1,56 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolImageSelectionTransparencyCommand_H +#define kpToolImageSelectionTransparencyCommand_H + + +#include "commands/kpNamedCommand.h" +#include "layers/selections/image/kpImageSelectionTransparency.h" + + +class kpToolImageSelectionTransparencyCommand : public kpNamedCommand +{ +public: + kpToolImageSelectionTransparencyCommand (const QString &name, + const kpImageSelectionTransparency &st, + const kpImageSelectionTransparency &oldST, + kpCommandEnvironment *environ); + ~kpToolImageSelectionTransparencyCommand () override; + + kpCommandSize::SizeType size () const override; + + void execute () override; + void unexecute () override; + +private: + kpImageSelectionTransparency m_st, m_oldST; +}; + + +#endif // kpToolImageSelectionTransparencyCommand_H diff --git a/commands/tools/selection/kpToolSelectionCreateCommand.cpp b/commands/tools/selection/kpToolSelectionCreateCommand.cpp new file mode 100644 index 0000000..241fd95 --- /dev/null +++ b/commands/tools/selection/kpToolSelectionCreateCommand.cpp @@ -0,0 +1,161 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SELECTION 0 + + +#include "commands/tools/selection/kpToolSelectionCreateCommand.h" + +#include "layers/selections/kpAbstractSelection.h" +#include "layers/selections/image/kpAbstractImageSelection.h" +#include "environments/commands/kpCommandEnvironment.h" +#include "commands/kpCommandHistory.h" +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "layers/selections/text/kpTextSelection.h" +#include "widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.h" +#include "views/kpView.h" +#include "views/manager/kpViewManager.h" + +#include "kpLogCategories.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +kpToolSelectionCreateCommand::kpToolSelectionCreateCommand (const QString &name, + const kpAbstractSelection &fromSelection, + kpCommandEnvironment *environ) + : kpNamedCommand (name, environ), + m_fromSelection (nullptr), + m_textRow (0), m_textCol (0) +{ + setFromSelection (fromSelection); +} + +kpToolSelectionCreateCommand::~kpToolSelectionCreateCommand () +{ + delete m_fromSelection; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolSelectionCreateCommand::size () const +{ + return SelectionSize (m_fromSelection); +} + + +// public +const kpAbstractSelection *kpToolSelectionCreateCommand::fromSelection () const +{ + return m_fromSelection; +} + +// public +void kpToolSelectionCreateCommand::setFromSelection (const kpAbstractSelection &fromSelection) +{ + delete m_fromSelection; + m_fromSelection = fromSelection.clone (); +} + +// public virtual [base kpCommand] +void kpToolSelectionCreateCommand::execute () +{ +#if DEBUG_KP_TOOL_SELECTION + qCDebug(kpLogCommands) << "kpToolSelectionCreateCommand::execute()"; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + + if (m_fromSelection) + { + #if DEBUG_KP_TOOL_SELECTION + qCDebug(kpLogCommands) << "\tusing fromSelection"; + qCDebug(kpLogCommands) << "\t\thave sel=" << doc->selection () << endl; + #endif + kpAbstractImageSelection *imageSel = + dynamic_cast (m_fromSelection); + kpTextSelection *textSel = + dynamic_cast (m_fromSelection); + if (imageSel) + { + if (imageSel->transparency () != environ ()->imageSelectionTransparency ()) { + environ ()->setImageSelectionTransparency (imageSel->transparency ()); + } + } + else if (textSel) + { + if (textSel->textStyle () != environ ()->textStyle ()) { + environ ()->setTextStyle (textSel->textStyle ()); + } + } + else { + Q_ASSERT (!"Unknown selection type"); + } + + viewManager ()->setTextCursorPosition (m_textRow, m_textCol); + doc->setSelection (*m_fromSelection); + + environ ()->somethingBelowTheCursorChanged (); + } +} + +// public virtual [base kpCommand] +void kpToolSelectionCreateCommand::unexecute () +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + if (!doc->selection ()) + { + // Was just a border that got deselected? + if (m_fromSelection && !m_fromSelection->hasContent ()) { + return; + } + + Q_ASSERT (!"kpToolSelectionCreateCommand::unexecute() without sel region"); + return; + } + + m_textRow = viewManager ()->textCursorRow (); + m_textCol = viewManager ()->textCursorCol (); + + doc->selectionDelete (); + + environ ()->somethingBelowTheCursorChanged (); +} + diff --git a/commands/tools/selection/kpToolSelectionCreateCommand.h b/commands/tools/selection/kpToolSelectionCreateCommand.h new file mode 100644 index 0000000..7556b10 --- /dev/null +++ b/commands/tools/selection/kpToolSelectionCreateCommand.h @@ -0,0 +1,62 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolSelectionCreateCommand_H +#define kpToolSelectionCreateCommand_H + + +#include "commands/kpNamedCommand.h" + + +class kpAbstractSelection; + + +class kpToolSelectionCreateCommand : public kpNamedCommand +{ +public: + // (if fromSelection doesn't have a pixmap, it will only recreate the region) + kpToolSelectionCreateCommand (const QString &name, const kpAbstractSelection &fromSelection, + kpCommandEnvironment *environ); + ~kpToolSelectionCreateCommand () override; + + kpCommandSize::SizeType size () const override; + + const kpAbstractSelection *fromSelection () const; + void setFromSelection (const kpAbstractSelection &fromSelection); + + void execute () override; + void unexecute () override; + +private: + kpAbstractSelection *m_fromSelection; + + int m_textRow, m_textCol; +}; + + +#endif // kpToolSelectionCreateCommand_H diff --git a/commands/tools/selection/kpToolSelectionDestroyCommand.cpp b/commands/tools/selection/kpToolSelectionDestroyCommand.cpp new file mode 100644 index 0000000..2be17d8 --- /dev/null +++ b/commands/tools/selection/kpToolSelectionDestroyCommand.cpp @@ -0,0 +1,176 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SELECTION 0 + + +#include "kpToolSelectionDestroyCommand.h" +#include "kpLogCategories.h" +#include "layers/selections/kpAbstractSelection.h" +#include "layers/selections/image/kpAbstractImageSelection.h" +#include "environments/commands/kpCommandEnvironment.h" +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "layers/selections/text/kpTextSelection.h" +#include "views/manager/kpViewManager.h" + +//--------------------------------------------------------------------- + +kpToolSelectionDestroyCommand::kpToolSelectionDestroyCommand (const QString &name, + bool pushOntoDocument, + kpCommandEnvironment *environ) + : kpNamedCommand (name, environ), + m_pushOntoDocument (pushOntoDocument), + m_oldSelectionPtr (nullptr), + m_textRow(0), m_textCol(0) +{ +} + +//--------------------------------------------------------------------- + +kpToolSelectionDestroyCommand::~kpToolSelectionDestroyCommand () +{ + delete m_oldSelectionPtr; +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolSelectionDestroyCommand::size () const +{ + return ImageSize (m_oldDocImage) + + SelectionSize (m_oldSelectionPtr); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +void kpToolSelectionDestroyCommand::execute () +{ +#if DEBUG_KP_TOOL_SELECTION + qCDebug(kpLogCommands) << "kpToolSelectionDestroyCommand::execute () CALLED"; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + Q_ASSERT (doc->selection ()); + + m_textRow = viewManager ()->textCursorRow (); + m_textCol = viewManager ()->textCursorCol (); + + Q_ASSERT (!m_oldSelectionPtr); + m_oldSelectionPtr = doc->selection ()->clone (); + + if (m_pushOntoDocument) + { + m_oldDocImage = doc->getImageAt (doc->selection ()->boundingRect ()); + doc->selectionPushOntoDocument (); + } + else { + doc->selectionDelete (); + } + + environ ()->somethingBelowTheCursorChanged (); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +void kpToolSelectionDestroyCommand::unexecute () +{ +#if DEBUG_KP_TOOL_SELECTION + qCDebug(kpLogCommands) << "kpToolSelectionDestroyCommand::unexecute () CALLED"; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + + if (doc->selection ()) + { + // not error because it's possible that the user dragged out a new + // region (without pulling image), and then CTRL+Z + #if DEBUG_KP_TOOL_SELECTION + qCDebug(kpLogCommands) << "kpToolSelectionDestroyCommand::unexecute() already has sel region"; + #endif + + if (doc->selection ()->hasContent ()) + { + Q_ASSERT (!"kpToolSelectionDestroyCommand::unexecute() already has sel content"); + return; + } + } + + Q_ASSERT (m_oldSelectionPtr); + + if (m_pushOntoDocument) + { + #if DEBUG_KP_TOOL_SELECTION + qCDebug(kpLogCommands) << "\tunpush oldDocImage onto doc first"; + #endif + doc->setImageAt (m_oldDocImage, m_oldSelectionPtr->topLeft ()); + } + +#if DEBUG_KP_TOOL_SELECTION + qCDebug(kpLogCommands) << "\tsetting selection to: rect=" << m_oldSelectionPtr->boundingRect () + << " hasContent=" << m_oldSelectionPtr->hasContent (); +#endif + kpAbstractImageSelection *imageSel = + dynamic_cast (m_oldSelectionPtr); + kpTextSelection *textSel = + dynamic_cast (m_oldSelectionPtr); + if (imageSel) + { + if (imageSel->transparency () != environ ()->imageSelectionTransparency ()) { + environ ()->setImageSelectionTransparency (imageSel->transparency ()); + } + if (dynamic_cast (doc->selection())) { + doc->selectionPushOntoDocument(); + } + } + else if (textSel) + { + if (textSel->textStyle () != environ ()->textStyle ()) { + environ ()->setTextStyle (textSel->textStyle ()); + } + if (dynamic_cast (doc->selection())) { + doc->selectionPushOntoDocument(); + } + } + else { + Q_ASSERT (!"Unknown selection type"); + } + + viewManager ()->setTextCursorPosition (m_textRow, m_textCol); + doc->setSelection (*m_oldSelectionPtr); + + environ ()->somethingBelowTheCursorChanged (); + + delete m_oldSelectionPtr; + m_oldSelectionPtr = nullptr; +} + diff --git a/commands/tools/selection/kpToolSelectionDestroyCommand.h b/commands/tools/selection/kpToolSelectionDestroyCommand.h new file mode 100644 index 0000000..0639945 --- /dev/null +++ b/commands/tools/selection/kpToolSelectionDestroyCommand.h @@ -0,0 +1,61 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolSelectionDestroyCommand_H +#define kpToolSelectionDestroyCommand_H + + +#include "imagelib/kpImage.h" +#include "commands/kpNamedCommand.h" + + +class kpAbstractSelection; + + +class kpToolSelectionDestroyCommand : public kpNamedCommand +{ +public: + kpToolSelectionDestroyCommand (const QString &name, bool pushOntoDocument, + kpCommandEnvironment *environ); + ~kpToolSelectionDestroyCommand () override; + + kpCommandSize::SizeType size () const override; + + void execute () override; + void unexecute () override; + +private: + bool m_pushOntoDocument; + kpImage m_oldDocImage; + kpAbstractSelection *m_oldSelectionPtr; + + int m_textRow, m_textCol; +}; + + +#endif // kpToolSelectionDestroyCommand_H diff --git a/commands/tools/selection/kpToolSelectionMoveCommand.cpp b/commands/tools/selection/kpToolSelectionMoveCommand.cpp new file mode 100644 index 0000000..00904f3 --- /dev/null +++ b/commands/tools/selection/kpToolSelectionMoveCommand.cpp @@ -0,0 +1,219 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SELECTION 0 + + +#include "kpToolSelectionMoveCommand.h" + +#include "layers/selections/kpAbstractSelection.h" +#include "environments/commands/kpCommandEnvironment.h" +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "tools/kpTool.h" +#include "widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.h" +#include "views/manager/kpViewManager.h" + +#include "kpLogCategories.h" + +//-------------------------------------------------------------------------------- + +kpToolSelectionMoveCommand::kpToolSelectionMoveCommand (const QString &name, + kpCommandEnvironment *environ) + : kpNamedCommand (name, environ) +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + Q_ASSERT (doc->selection ()); + + m_startPoint = m_endPoint = doc->selection ()->topLeft (); +} + +kpToolSelectionMoveCommand::~kpToolSelectionMoveCommand () = default; + + +// public +kpAbstractSelection *kpToolSelectionMoveCommand::originalSelectionClone () const +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + Q_ASSERT (doc->selection ()); + + kpAbstractSelection *selection = doc->selection ()->clone (); + selection->moveTo (m_startPoint); + + return selection; +} + + +// public virtual [base kpComand] +kpCommandSize::SizeType kpToolSelectionMoveCommand::size () const +{ + return ImageSize (m_oldDocumentImage) + + PolygonSize (m_copyOntoDocumentPoints); +} + + +// public virtual [base kpCommand] +void kpToolSelectionMoveCommand::execute () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + qCDebug(kpLogCommands) << "kpToolSelectionMoveCommand::execute()"; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + + kpAbstractSelection *sel = doc->selection (); + // Must have content before it can be moved. + Q_ASSERT (sel && sel->hasContent ()); + + kpViewManager *vm = viewManager (); + Q_ASSERT (vm); + + vm->setQueueUpdates (); + { + for (const auto &p : m_copyOntoDocumentPoints) + { + sel->moveTo (p); + doc->selectionCopyOntoDocument (); + } + + sel->moveTo (m_endPoint); + + environ ()->somethingBelowTheCursorChanged (); + } + vm->restoreQueueUpdates (); +} + +// public virtual [base kpCommand] +void kpToolSelectionMoveCommand::unexecute () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + qCDebug(kpLogCommands) << "kpToolSelectionMoveCommand::unexecute()"; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + + kpAbstractSelection *sel = doc->selection (); + // Must have content before it can be un-moved. + Q_ASSERT (sel && sel->hasContent ()); + + kpViewManager *vm = viewManager (); + Q_ASSERT (vm); + + vm->setQueueUpdates (); + + if (!m_oldDocumentImage.isNull ()) { + doc->setImageAt (m_oldDocumentImage, m_documentBoundingRect.topLeft ()); + } + +#if DEBUG_KP_TOOL_SELECTION && 1 + qCDebug(kpLogCommands) << "\tmove to startPoint=" << m_startPoint; +#endif + sel->moveTo (m_startPoint); + + environ ()->somethingBelowTheCursorChanged (); + + vm->restoreQueueUpdates (); +} + +// public +void kpToolSelectionMoveCommand::moveTo (const QPoint &point, bool moveLater) +{ +#if DEBUG_KP_TOOL_SELECTION && 0 + qCDebug(kpLogCommands) << "kpToolSelectionMoveCommand::moveTo" << point + << " moveLater=" << moveLater; +#endif + + if (!moveLater) + { + kpDocument *doc = document (); + Q_ASSERT (doc); + + kpAbstractSelection *sel = doc->selection (); + // Must have content before it can be moved. + Q_ASSERT (sel && sel->hasContent ()); + + if (point == sel->topLeft ()) { + return; + } + + sel->moveTo (point); + } + + m_endPoint = point; +} + +// public +void kpToolSelectionMoveCommand::moveTo (int x, int y, bool moveLater) +{ + moveTo (QPoint (x, y), moveLater); +} + +// public +void kpToolSelectionMoveCommand::copyOntoDocument () +{ +#if DEBUG_KP_TOOL_SELECTION + qCDebug(kpLogCommands) << "kpToolSelectionMoveCommand::copyOntoDocument()"; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + + kpAbstractSelection *sel = doc->selection (); + // Must have content before we allow it be stamped onto the document, + // to be consistent with the requirement on other selection operations. + Q_ASSERT (sel && sel->hasContent ()); + + if (m_oldDocumentImage.isNull ()) { + m_oldDocumentImage = doc->image (); + } + + QRect selBoundingRect = sel->boundingRect (); + m_documentBoundingRect = m_documentBoundingRect.united (selBoundingRect); + + doc->selectionCopyOntoDocument (); + + m_copyOntoDocumentPoints.putPoints (m_copyOntoDocumentPoints.count (), + 1, + selBoundingRect.x (), + selBoundingRect.y ()); +} + +// public +void kpToolSelectionMoveCommand::finalize () +{ + if (!m_oldDocumentImage.isNull () && !m_documentBoundingRect.isNull ()) + { + m_oldDocumentImage = kpTool::neededPixmap (m_oldDocumentImage, + m_documentBoundingRect); + } +} + diff --git a/commands/tools/selection/kpToolSelectionMoveCommand.h b/commands/tools/selection/kpToolSelectionMoveCommand.h new file mode 100644 index 0000000..c6e61f0 --- /dev/null +++ b/commands/tools/selection/kpToolSelectionMoveCommand.h @@ -0,0 +1,74 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolSelectionMoveCommand_H +#define kpToolSelectionMoveCommand_H + + +#include +#include +#include + +#include "imagelib/kpImage.h" +#include "commands/kpNamedCommand.h" + + +class kpAbstractSelection; + + +class kpToolSelectionMoveCommand : public kpNamedCommand +{ +public: + kpToolSelectionMoveCommand (const QString &name, kpCommandEnvironment *environ); + ~kpToolSelectionMoveCommand () override; + + kpAbstractSelection *originalSelectionClone () const; + + kpCommandSize::SizeType size () const override; + + void execute () override; + void unexecute () override; + + void moveTo (const QPoint &point, bool moveLater = false); + void moveTo (int x, int y, bool moveLater = false); + void copyOntoDocument (); + void finalize (); + +private: + QPoint m_startPoint, m_endPoint; + + kpImage m_oldDocumentImage; + + // area of document affected (not the bounding rect of the sel) + QRect m_documentBoundingRect; + + QPolygon m_copyOntoDocumentPoints; +}; + + +#endif // kpToolSelectionMoveCommand_H diff --git a/commands/tools/selection/kpToolSelectionPullFromDocumentCommand.cpp b/commands/tools/selection/kpToolSelectionPullFromDocumentCommand.cpp new file mode 100644 index 0000000..bd48644 --- /dev/null +++ b/commands/tools/selection/kpToolSelectionPullFromDocumentCommand.cpp @@ -0,0 +1,140 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SELECTION 0 + + +#include "kpToolSelectionPullFromDocumentCommand.h" + +#include "layers/selections/image/kpAbstractImageSelection.h" +#include "environments/commands/kpCommandEnvironment.h" +#include "document/kpDocument.h" +#include "views/manager/kpViewManager.h" +#include "kpLogCategories.h" + + +kpToolSelectionPullFromDocumentCommand::kpToolSelectionPullFromDocumentCommand ( + const kpAbstractImageSelection &originalSelBorder, + const kpColor &backgroundColor, + const QString &name, + kpCommandEnvironment *environ) + : kpAbstractSelectionContentCommand (originalSelBorder, name, environ), + m_backgroundColor (backgroundColor) +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + qCDebug(kpLogCommands) << "kpToolSelectionPullFromDocumentCommand::() environ=" + << environ; +#endif +} + +kpToolSelectionPullFromDocumentCommand::~kpToolSelectionPullFromDocumentCommand () = default; + + +// public virtual [base kpCommand] +void kpToolSelectionPullFromDocumentCommand::execute () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + qCDebug(kpLogCommands) << "kpToolSelectionPullFromDocumentCommand::execute()"; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + + kpViewManager *vm = viewManager (); + Q_ASSERT (vm); + + vm->setQueueUpdates (); + { + // + // Recreate border + // + + // The previously executed command is required to have been a + // kpToolSelectionCreateCommand, which must have been given an image + // selection with no content. + // + // However, there is a tricky case. Suppose we are called for the first + // time, where the above precondition holds. We would add content + // to the selection as expected. But the user then undoes (CTRL+Z) the + // operation, calling unexecute(). There is now no content again. + // Since selection is only a border, the user can freely deselect it + // and/or select another region without changing the command history + // or document modified state. Therefore, if they now call us again + // by redoing (CTRL+Shift+Z), there is potentially no selection at all + // or it is at an arbitrary location. + // + // This assertion covers all 3 possibilities: + // + // 1. First call: image selection with no content + // 2. Later calls: + // a) no image selection (due to deselection) + // b) image selection with no content, at an arbitrary location + Q_ASSERT (!imageSelection () || !imageSelection ()->hasContent ()); + + const auto *originalImageSel = dynamic_cast + (originalSelection ()); + + if (originalImageSel->transparency () != + environ ()->imageSelectionTransparency ()) + { + environ ()->setImageSelectionTransparency (originalImageSel->transparency ()); + } + + doc->setSelection (*originalSelection ()); + + + // + // Add content + // + + doc->imageSelectionPullFromDocument (m_backgroundColor); + } + vm->restoreQueueUpdates (); +} + +// public virtual [base kpCommand] +void kpToolSelectionPullFromDocumentCommand::unexecute () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + qCDebug(kpLogCommands) << "kpToolSelectionPullFromDocumentCommand::unexecute()"; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + // Must have selection image content. + Q_ASSERT (doc->imageSelection () && doc->imageSelection ()->hasContent ()); + + + // We can have faith that this is the state of the selection after + // execute(), rather than after the user tried to throw us off by + // simply selecting another region as to do that, a destroy command + // must have been used. + doc->selectionCopyOntoDocument (false/*use opaque pixmap*/); + doc->imageSelection ()->deleteContent (); +} + diff --git a/commands/tools/selection/kpToolSelectionPullFromDocumentCommand.h b/commands/tools/selection/kpToolSelectionPullFromDocumentCommand.h new file mode 100644 index 0000000..203dcbf --- /dev/null +++ b/commands/tools/selection/kpToolSelectionPullFromDocumentCommand.h @@ -0,0 +1,59 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolSelectionPullFromDocumentCommand_H +#define kpToolSelectionPullFromDocumentCommand_H + + +#include "kpAbstractSelectionContentCommand.h" +#include "imagelib/kpColor.h" + + +class kpAbstractImageSelection; + + +class kpToolSelectionPullFromDocumentCommand : + public kpAbstractSelectionContentCommand +{ +public: + kpToolSelectionPullFromDocumentCommand ( + const kpAbstractImageSelection &originalSelBorder, + const kpColor &backgroundColor, + const QString &name, + kpCommandEnvironment *environ); + ~kpToolSelectionPullFromDocumentCommand () override; + + void execute () override; + void unexecute () override; + +private: + kpColor m_backgroundColor; +}; + + +#endif // kpToolSelectionPullFromDocumentCommand_H diff --git a/commands/tools/selection/kpToolSelectionResizeScaleCommand.cpp b/commands/tools/selection/kpToolSelectionResizeScaleCommand.cpp new file mode 100644 index 0000000..da772a1 --- /dev/null +++ b/commands/tools/selection/kpToolSelectionResizeScaleCommand.cpp @@ -0,0 +1,257 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SELECTION 0 + + +#include "kpToolSelectionResizeScaleCommand.h" + +#include "layers/selections/kpAbstractSelection.h" +#include "layers/selections/image/kpAbstractImageSelection.h" +#include "environments/commands/kpCommandEnvironment.h" +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "layers/selections/image/kpRectangularImageSelection.h" +#include "layers/selections/text/kpTextSelection.h" +#include "kpLogCategories.h" + +#include +#include +#include + +#include + +//-------------------------------------------------------------------------------- + +kpToolSelectionResizeScaleCommand::kpToolSelectionResizeScaleCommand ( + kpCommandEnvironment *environ) + : kpNamedCommand (environ->textSelection () ? + i18n ("Text: Resize Box") : + i18n ("Selection: Smooth Scale"), + environ), + m_smoothScaleTimer (new QTimer (this)) +{ + m_originalSelectionPtr = selection ()->clone (); + + m_newTopLeft = selection ()->topLeft (); + m_newWidth = selection ()->width (); + m_newHeight = selection ()->height (); + + m_smoothScaleTimer->setSingleShot (true); + connect (m_smoothScaleTimer, &QTimer::timeout, this, [this]{resizeScaleAndMove(false);}); +} + +kpToolSelectionResizeScaleCommand::~kpToolSelectionResizeScaleCommand () +{ + delete m_originalSelectionPtr; +} + + +// public virtual +kpCommandSize::SizeType kpToolSelectionResizeScaleCommand::size () const +{ + return SelectionSize (m_originalSelectionPtr); +} + + +// public +const kpAbstractSelection *kpToolSelectionResizeScaleCommand::originalSelection () const +{ + return m_originalSelectionPtr; +} + + +// public +QPoint kpToolSelectionResizeScaleCommand::topLeft () const +{ + return m_newTopLeft; +} + +// public +void kpToolSelectionResizeScaleCommand::moveTo (const QPoint &point) +{ + if (point == m_newTopLeft) { + return; + } + + m_newTopLeft = point; + selection ()->moveTo (m_newTopLeft); +} + + +// public +int kpToolSelectionResizeScaleCommand::width () const +{ + return m_newWidth; +} + +// public +int kpToolSelectionResizeScaleCommand::height () const +{ + return m_newHeight; +} + +// public +void kpToolSelectionResizeScaleCommand::resize (int width, int height, + bool delayed) +{ + if (width == m_newWidth && height == m_newHeight) { + return; + } + + m_newWidth = width; + m_newHeight = height; + + resizeScaleAndMove (delayed); +} + + +// public +void kpToolSelectionResizeScaleCommand::resizeAndMoveTo (int width, int height, + const QPoint &point, + bool delayed) +{ + if (width == m_newWidth && height == m_newHeight && + point == m_newTopLeft) + { + return; + } + + m_newWidth = width; + m_newHeight = height; + m_newTopLeft = point; + + resizeScaleAndMove (delayed); +} + + +// protected +void kpToolSelectionResizeScaleCommand::killSmoothScaleTimer () +{ + m_smoothScaleTimer->stop (); +} + + +// protected +void kpToolSelectionResizeScaleCommand::resizeScaleAndMove (bool delayed) +{ +#if DEBUG_KP_TOOL_SELECTION + qCDebug(kpLogCommands) << "kpToolSelectionResizeScaleCommand::resizeScaleAndMove(delayed=" + << delayed << ")"; +#endif + + killSmoothScaleTimer (); + + kpAbstractSelection *newSelPtr = nullptr; + + if (textSelection ()) + { + Q_ASSERT (dynamic_cast (m_originalSelectionPtr)); + auto *orgTextSel = dynamic_cast (m_originalSelectionPtr); + + newSelPtr = orgTextSel->resized (m_newWidth, m_newHeight); + } + else + { + Q_ASSERT (dynamic_cast (m_originalSelectionPtr)); + auto *imageSel = dynamic_cast (m_originalSelectionPtr); + + newSelPtr = new kpRectangularImageSelection ( + QRect (imageSel->x (), + imageSel->y (), + m_newWidth, + m_newHeight), + kpPixmapFX::scale (imageSel->baseImage (), + m_newWidth, m_newHeight, + !delayed/*if not delayed, smooth*/), + imageSel->transparency ()); + + if (delayed) + { + // Call self (once) with delayed==false in 200ms + m_smoothScaleTimer->start (200/*ms*/); + } + } + + Q_ASSERT (newSelPtr); + newSelPtr->moveTo (m_newTopLeft); + + document ()->setSelection (*newSelPtr); + + delete newSelPtr; +} + + +// public +void kpToolSelectionResizeScaleCommand::finalize () +{ +#if DEBUG_KP_TOOL_SELECTION + qCDebug(kpLogCommands) << "kpToolSelectionResizeScaleCommand::finalize()" + << " smoothScaleTimer->isActive=" + << m_smoothScaleTimer->isActive (); +#endif + + // Make sure the selection contains the final image and the timer won't + // fire afterwards. + if (m_smoothScaleTimer->isActive ()) + { + resizeScaleAndMove (); + Q_ASSERT (!m_smoothScaleTimer->isActive ()); + } +} + + +// public virtual [base kpToolResizeScaleCommand] +void kpToolSelectionResizeScaleCommand::execute () +{ + QApplication::setOverrideCursor (Qt::WaitCursor); + + killSmoothScaleTimer (); + + resizeScaleAndMove (); + + environ ()->somethingBelowTheCursorChanged (); + + QApplication::restoreOverrideCursor (); +} + +// public virtual [base kpToolResizeScaleCommand] +void kpToolSelectionResizeScaleCommand::unexecute () +{ + QApplication::setOverrideCursor (Qt::WaitCursor); + + killSmoothScaleTimer (); + + document ()->setSelection (*m_originalSelectionPtr); + + environ ()->somethingBelowTheCursorChanged (); + + QApplication::restoreOverrideCursor (); +} + + diff --git a/commands/tools/selection/kpToolSelectionResizeScaleCommand.h b/commands/tools/selection/kpToolSelectionResizeScaleCommand.h new file mode 100644 index 0000000..426b768 --- /dev/null +++ b/commands/tools/selection/kpToolSelectionResizeScaleCommand.h @@ -0,0 +1,104 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolSelectionResizeScaleCommand_H +#define kpToolSelectionResizeScaleCommand_H + + +#include +#include + +#include "commands/kpNamedCommand.h" + + +class QTimer; + +class kpAbstractSelection; + + +// You could subclass kpToolResizeScaleCommand and/or +// kpToolSelectionMoveCommand instead if want a disaster. +// This is different to kpToolResizeScaleCommand in that: +// +// 1. This only works for selections. +// 2. This is designed for the size and position to change several times +// before execute(). +// +// REFACTOR: Later: I take that all back. We should merge with +// kpToolResizeScaleCommand to reduce code duplication. +class kpToolSelectionResizeScaleCommand : public QObject, + public kpNamedCommand +{ +Q_OBJECT + +public: + kpToolSelectionResizeScaleCommand (kpCommandEnvironment *environ); + ~kpToolSelectionResizeScaleCommand () override; + + kpCommandSize::SizeType size () const override; + +public: + const kpAbstractSelection *originalSelection () const; + + QPoint topLeft () const; + void moveTo (const QPoint &point); + + int width () const; + int height () const; + void resize (int width, int height, bool delayed = false); + + // (equivalent to resize() followed by moveTo() but faster) + void resizeAndMoveTo (int width, int height, const QPoint &point, + bool delayed = false); + +protected: + void killSmoothScaleTimer (); + + // If , does a fast, low-quality scale and then calls itself + // with unset for a smooth scale, a short time later. + // If acting on a text box, is ignored. + void resizeScaleAndMove (bool delayed = false); + +public: + void finalize (); + +public: + void execute () override; + void unexecute () override; + +protected: + kpAbstractSelection *m_originalSelectionPtr; + + QPoint m_newTopLeft; + int m_newWidth, m_newHeight; + + QTimer *m_smoothScaleTimer; +}; + + +#endif // kpToolSelectionResizeScaleCommand_H diff --git a/commands/tools/selection/text/kpToolTextBackspaceCommand.cpp b/commands/tools/selection/text/kpToolTextBackspaceCommand.cpp new file mode 100644 index 0000000..03b3cc1 --- /dev/null +++ b/commands/tools/selection/text/kpToolTextBackspaceCommand.cpp @@ -0,0 +1,152 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_TEXT 0 + + +#include "kpToolTextBackspaceCommand.h" + +#include "layers/selections/text/kpTextSelection.h" +#include "views/manager/kpViewManager.h" + +#include + + +kpToolTextBackspaceCommand::kpToolTextBackspaceCommand (const QString &name, + int row, int col, Action action, + kpCommandEnvironment *environ) + : kpNamedCommand (name, environ), + m_row (row), m_col (col), + m_numBackspaces (0) +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + if (action == AddBackspaceNow) { + addBackspace (); + } +} + +kpToolTextBackspaceCommand::~kpToolTextBackspaceCommand () = default; + + +// public +void kpToolTextBackspaceCommand::addBackspace () +{ + QList textLines = textSelection ()->textLines (); + + if (m_col > 0) + { + m_deletedText.prepend (textLines [m_row][m_col - 1]); + + textLines [m_row] = textLines [m_row].left (m_col - 1) + + textLines [m_row].mid (m_col); + m_col--; + } + else + { + if (m_row > 0) + { + int newCursorRow = m_row - 1; + int newCursorCol = textLines [newCursorRow].length (); + + m_deletedText.prepend ('\n'); + + textLines [newCursorRow] += textLines [m_row]; + + textLines.erase (textLines.begin () + m_row); + + m_row = newCursorRow; + m_col = newCursorCol; + } + } + + textSelection ()->setTextLines (textLines); + + viewManager ()->setTextCursorPosition (m_row, m_col); + + m_numBackspaces++; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolTextBackspaceCommand::size () const +{ + return static_cast + (static_cast (m_deletedText.length ()) * sizeof (QChar)); +} + + +// public virtual [base kpCommand] +void kpToolTextBackspaceCommand::execute () +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + m_deletedText.clear (); + int oldNumBackspaces = m_numBackspaces; + m_numBackspaces = 0; + + for (int i = 0; i < oldNumBackspaces; i++) { + addBackspace (); + } +} + +// public virtual [base kpCommand] +void kpToolTextBackspaceCommand::unexecute () +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + QList textLines = textSelection ()->textLines (); + + for (auto && i : m_deletedText) + { + if (i == '\n') + { + const QString rightHalf = textLines [m_row].mid (m_col); + + textLines [m_row].truncate (m_col); + textLines.insert (textLines.begin () + m_row + 1, rightHalf); + + m_row++; + m_col = 0; + } + else + { + const QString leftHalf = textLines [m_row].left (m_col); + const QString rightHalf = textLines [m_row].mid (m_col); + + textLines [m_row] = leftHalf + i + rightHalf; + m_col++; + } + } + + m_deletedText.clear (); + + textSelection ()->setTextLines (textLines); + + viewManager ()->setTextCursorPosition (m_row, m_col); +} + diff --git a/commands/tools/selection/text/kpToolTextBackspaceCommand.h b/commands/tools/selection/text/kpToolTextBackspaceCommand.h new file mode 100644 index 0000000..e728b40 --- /dev/null +++ b/commands/tools/selection/text/kpToolTextBackspaceCommand.h @@ -0,0 +1,64 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_TEXT_BACKSPACE_COMMAND_H +#define KP_TOOL_TEXT_BACKSPACE_COMMAND_H + + +#include "commands/kpNamedCommand.h" + + +class kpToolTextBackspaceCommand : public kpNamedCommand +{ +public: + enum Action + { + DontAddBackspaceYet, + AddBackspaceNow + }; + + kpToolTextBackspaceCommand (const QString &name, + int row, int col, Action action, + kpCommandEnvironment *environ); + ~kpToolTextBackspaceCommand () override; + + void addBackspace (); + + kpCommandSize::SizeType size () const override; + + void execute () override; + void unexecute () override; + +protected: + int m_row, m_col; + int m_numBackspaces; + QString m_deletedText; +}; + + +#endif // KP_TOOL_TEXT_BACKSPACE_COMMAND_H diff --git a/commands/tools/selection/text/kpToolTextChangeStyleCommand.cpp b/commands/tools/selection/text/kpToolTextChangeStyleCommand.cpp new file mode 100644 index 0000000..1e9a73a --- /dev/null +++ b/commands/tools/selection/text/kpToolTextChangeStyleCommand.cpp @@ -0,0 +1,96 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_TEXT 0 + + +#include "kpToolTextChangeStyleCommand.h" + +#include "environments/commands/kpCommandEnvironment.h" +#include "layers/selections/text/kpTextSelection.h" + +#include "kpLogCategories.h" + + +kpToolTextChangeStyleCommand::kpToolTextChangeStyleCommand (const QString &name, + const kpTextStyle &newTextStyle, const kpTextStyle &oldTextStyle, + kpCommandEnvironment *environ) + : kpNamedCommand (name, environ), + m_newTextStyle (newTextStyle), + m_oldTextStyle (oldTextStyle) +{ +} + +kpToolTextChangeStyleCommand::~kpToolTextChangeStyleCommand () = default; + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolTextChangeStyleCommand::size () const +{ + return 0; +} + + +// public virtual [base kpCommand] +void kpToolTextChangeStyleCommand::execute () +{ +#if DEBUG_KP_TOOL_TEXT && 1 + qCDebug(kpLogCommands) << "kpToolTextChangeStyleCommand::execute()" + << " font=" << m_newTextStyle.fontFamily () + << " fontSize=" << m_newTextStyle.fontSize () + << " isBold=" << m_newTextStyle.isBold () + << " isItalic=" << m_newTextStyle.isItalic () + << " isUnderline=" << m_newTextStyle.isUnderline () + << " isStrikeThru=" << m_newTextStyle.isStrikeThru (); +#endif + + environ ()->setTextStyle (m_newTextStyle); + + if (textSelection ()) { + textSelection ()->setTextStyle (m_newTextStyle); + } +} + +// public virtual [base kpCommand] +void kpToolTextChangeStyleCommand::unexecute () +{ +#if DEBUG_KP_TOOL_TEXT && 1 + qCDebug(kpLogCommands) << "kpToolTextChangeStyleCommand::unexecute()" + << " font=" << m_newTextStyle.fontFamily () + << " fontSize=" << m_newTextStyle.fontSize () + << " isBold=" << m_newTextStyle.isBold () + << " isItalic=" << m_newTextStyle.isItalic () + << " isUnderline=" << m_newTextStyle.isUnderline () + << " isStrikeThru=" << m_newTextStyle.isStrikeThru (); +#endif + + environ ()->setTextStyle (m_oldTextStyle); + + if (textSelection ()) + textSelection ()->setTextStyle (m_oldTextStyle); +} + diff --git a/commands/tools/selection/text/kpToolTextChangeStyleCommand.h b/commands/tools/selection/text/kpToolTextChangeStyleCommand.h new file mode 100644 index 0000000..b1ad7ac --- /dev/null +++ b/commands/tools/selection/text/kpToolTextChangeStyleCommand.h @@ -0,0 +1,55 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_TEXT_CHANGE_STYLE_COMMAND_H +#define KP_TOOL_TEXT_CHANGE_STYLE_COMMAND_H + + +#include "commands/kpNamedCommand.h" +#include "layers/selections/text/kpTextStyle.h" + + +class kpToolTextChangeStyleCommand : public kpNamedCommand +{ +public: + kpToolTextChangeStyleCommand (const QString &name, + const kpTextStyle &newTextStyle, const kpTextStyle &oldTextStyle, + kpCommandEnvironment *environ); + ~kpToolTextChangeStyleCommand () override; + + kpCommandSize::SizeType size () const override; + + void execute () override; + void unexecute () override; + +protected: + kpTextStyle m_newTextStyle, m_oldTextStyle; +}; + + +#endif // KP_TOOL_TEXT_CHANGE_STYLE_COMMAND_H diff --git a/commands/tools/selection/text/kpToolTextDeleteCommand.cpp b/commands/tools/selection/text/kpToolTextDeleteCommand.cpp new file mode 100644 index 0000000..459657c --- /dev/null +++ b/commands/tools/selection/text/kpToolTextDeleteCommand.cpp @@ -0,0 +1,140 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_TEXT 0 + + +#include "kpToolTextDeleteCommand.h" + +#include "layers/selections/text/kpTextSelection.h" +#include "views/manager/kpViewManager.h" + +#include + + +kpToolTextDeleteCommand::kpToolTextDeleteCommand (const QString &name, + int row, int col, Action action, + kpCommandEnvironment *environ) + : kpNamedCommand (name, environ), + m_row (row), m_col (col), + m_numDeletes (0) +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + if (action == AddDeleteNow) { + addDelete (); + } +} + +kpToolTextDeleteCommand::~kpToolTextDeleteCommand () = default; + + +// public +void kpToolTextDeleteCommand::addDelete () +{ + QList textLines = textSelection ()->textLines (); + + if (m_col < static_cast (textLines [m_row].length ())) + { + m_deletedText.prepend (textLines [m_row][m_col]); + + textLines [m_row] = textLines [m_row].left (m_col) + + textLines [m_row].mid (m_col + 1); + } + else + { + if (m_row < static_cast (textLines.size () - 1)) + { + m_deletedText.prepend ('\n'); + + textLines [m_row] += textLines [m_row + 1]; + textLines.erase (textLines.begin () + m_row + 1); + } + } + + textSelection ()->setTextLines (textLines); + + viewManager ()->setTextCursorPosition (m_row, m_col); + + m_numDeletes++; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolTextDeleteCommand::size () const +{ + return static_cast + (static_cast (m_deletedText.length ()) * sizeof (QChar)); +} + + +// public virtual [base kpCommand] +void kpToolTextDeleteCommand::execute () +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + m_deletedText.clear (); + int oldNumDeletes = m_numDeletes; + m_numDeletes = 0; + + for (int i = 0; i < oldNumDeletes; i++) { + addDelete (); + } +} + +// public virtual [base kpCommand] +void kpToolTextDeleteCommand::unexecute () +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + QList textLines = textSelection ()->textLines (); + + for (auto && i : m_deletedText) + { + if (i == '\n') + { + const QString rightHalf = textLines [m_row].mid (m_col); + + textLines [m_row].truncate (m_col); + textLines.insert (textLines.begin () + m_row + 1, rightHalf); + } + else + { + const QString leftHalf = textLines [m_row].left (m_col); + const QString rightHalf = textLines [m_row].mid (m_col); + + textLines [m_row] = leftHalf + i + rightHalf; + } + } + + m_deletedText.clear (); + + textSelection ()->setTextLines (textLines); + + viewManager ()->setTextCursorPosition (m_row, m_col); +} + diff --git a/commands/tools/selection/text/kpToolTextDeleteCommand.h b/commands/tools/selection/text/kpToolTextDeleteCommand.h new file mode 100644 index 0000000..c1d8cdd --- /dev/null +++ b/commands/tools/selection/text/kpToolTextDeleteCommand.h @@ -0,0 +1,64 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_TEXT_DELETE_COMMAND_H +#define KP_TOOL_TEXT_DELETE_COMMAND_H + + +#include "commands/kpNamedCommand.h" + + +class kpToolTextDeleteCommand : public kpNamedCommand +{ +public: + enum Action + { + DontAddDeleteYet, + AddDeleteNow + }; + + kpToolTextDeleteCommand (const QString &name, + int row, int col, Action action, + kpCommandEnvironment *environ); + ~kpToolTextDeleteCommand () override; + + void addDelete (); + + kpCommandSize::SizeType size () const override; + + void execute () override; + void unexecute () override; + +protected: + int m_row, m_col; + int m_numDeletes; + QString m_deletedText; +}; + + +#endif // KP_TOOL_TEXT_DELETE_COMMAND_H diff --git a/commands/tools/selection/text/kpToolTextEnterCommand.cpp b/commands/tools/selection/text/kpToolTextEnterCommand.cpp new file mode 100644 index 0000000..c341c77 --- /dev/null +++ b/commands/tools/selection/text/kpToolTextEnterCommand.cpp @@ -0,0 +1,126 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_TEXT 0 + + +#include "kpToolTextEnterCommand.h" + +#include "layers/selections/text/kpTextSelection.h" +#include "views/manager/kpViewManager.h" + +#include + + +kpToolTextEnterCommand::kpToolTextEnterCommand (const QString &name, + int row, int col, Action action, + kpCommandEnvironment *environ) + : kpNamedCommand (name, environ), + m_row (row), m_col (col), + m_numEnters (0) +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + if (action == AddEnterNow) { + addEnter (); + } +} + +kpToolTextEnterCommand::~kpToolTextEnterCommand () = default; + + +// public +void kpToolTextEnterCommand::addEnter () +{ + QList textLines = textSelection ()->textLines (); + + const QString rightHalf = textLines [m_row].mid (m_col); + + textLines [m_row].truncate (m_col); + textLines.insert (textLines.begin () + m_row + 1, rightHalf); + + textSelection ()->setTextLines (textLines); + + m_row++; + m_col = 0; + + viewManager ()->setTextCursorPosition (m_row, m_col); + + m_numEnters++; +} + + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolTextEnterCommand::size () const +{ + return 0; +} + + +// public virtual [base kpCommand] +void kpToolTextEnterCommand::execute () +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + int oldNumEnters = m_numEnters; + m_numEnters = 0; + + for (int i = 0; i < oldNumEnters; i++) { + addEnter (); + } +} + +// public virtual [base kpCommand] +void kpToolTextEnterCommand::unexecute () +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + QList textLines = textSelection ()->textLines (); + + for (int i = 0; i < m_numEnters; i++) + { + Q_ASSERT (m_col == 0); + + if (m_row <= 0) { + break; + } + + int newRow = m_row - 1; + int newCol = textLines [newRow].length (); + + textLines [newRow] += textLines [m_row]; + + textLines.erase (textLines.begin () + m_row); + + m_row = newRow; + m_col = newCol; + } + + textSelection ()->setTextLines (textLines); + + viewManager ()->setTextCursorPosition (m_row, m_col); +} + diff --git a/commands/tools/selection/text/kpToolTextEnterCommand.h b/commands/tools/selection/text/kpToolTextEnterCommand.h new file mode 100644 index 0000000..036e852 --- /dev/null +++ b/commands/tools/selection/text/kpToolTextEnterCommand.h @@ -0,0 +1,63 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_TEXT_ENTER_COMMAND_H +#define KP_TOOL_TEXT_ENTER_COMMAND_H + + +#include "commands/kpNamedCommand.h" + + +class kpToolTextEnterCommand : public kpNamedCommand +{ +public: + enum Action + { + DontAddEnterYet, + AddEnterNow + }; + + kpToolTextEnterCommand (const QString &name, + int row, int col, Action action, + kpCommandEnvironment *environ); + ~kpToolTextEnterCommand () override; + + void addEnter (); + + kpCommandSize::SizeType size () const override; + + void execute () override; + void unexecute () override; + +protected: + int m_row, m_col; + int m_numEnters; +}; + + +#endif // KP_TOOL_TEXT_ENTER_COMMAND_H diff --git a/commands/tools/selection/text/kpToolTextGiveContentCommand.cpp b/commands/tools/selection/text/kpToolTextGiveContentCommand.cpp new file mode 100644 index 0000000..7ee0c8f --- /dev/null +++ b/commands/tools/selection/text/kpToolTextGiveContentCommand.cpp @@ -0,0 +1,151 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SELECTION 0 + + +#include "kpToolTextGiveContentCommand.h" + +#include "environments/commands/kpCommandEnvironment.h" +#include "document/kpDocument.h" +#include "layers/selections/text/kpTextSelection.h" +#include "views/manager/kpViewManager.h" +#include "kpLogCategories.h" + + +kpToolTextGiveContentCommand::kpToolTextGiveContentCommand ( + const kpTextSelection &originalSelBorder, + const QString &name, + kpCommandEnvironment *environ) + : kpAbstractSelectionContentCommand (originalSelBorder, name, environ) +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + qCDebug(kpLogCommands) << "kpToolTextGiveContentCommand::() environ=" + << environ +#endif +} + +kpToolTextGiveContentCommand::~kpToolTextGiveContentCommand () = default; + + +// public virtual [base kpCommand] +void kpToolTextGiveContentCommand::execute () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + qCDebug(kpLogCommands) << "kpToolTextGiveContentCommand::execute()"; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + + kpViewManager *vm = viewManager (); + Q_ASSERT (vm); + + // See API Doc for kpViewManager::textCursorRow() & textCursorCol(). + Q_ASSERT (vm->textCursorRow () == 0 && vm->textCursorCol () == 0); + + vm->setQueueUpdates (); + { + // + // Recreate border + // + + // The previously executed command is required to have been a + // kpToolSelectionCreateCommand, which must have been given a text + // selection with no content. + // + // However, there is a tricky case. Suppose we are called for the first + // time, where the above precondition holds. We would add content + // to the selection as expected. But the user then undoes (CTRL+Z) the + // operation, calling unexecute(). There is now no content again. + // Since selection is only a border, the user can freely deselect it + // and/or select another region without changing the command history + // or document modified state. Therefore, if they now call us again + // by redoing (CTRL+Shift+Z), there is potentially no selection at all + // or it is at an arbitrary location. + // + // This assertion covers all 3 possibilities: + // + // 1. First call: text selection with no content + // 2. Later calls: + // a) no text selection (due to deselection) + // b) text selection with no content, at an arbitrary location + Q_ASSERT (!textSelection () || !textSelection ()->hasContent ()); + + const auto *originalTextSel = dynamic_cast + (originalSelection ()); + + if (originalTextSel->textStyle () != environ ()->textStyle ()) { + environ ()->setTextStyle (originalTextSel->textStyle ()); + } + + doc->setSelection (*originalSelection ()); + + + // + // Add Content + // + + QList listOfOneEmptyString; + listOfOneEmptyString.append (QString ()); + textSelection ()->setTextLines (listOfOneEmptyString); + } + vm->restoreQueueUpdates (); + + // This should not have changed from the start of the method. + Q_ASSERT (vm->textCursorRow () == 0 && vm->textCursorCol () == 0); +} + +// public virtual [base kpCommand] +void kpToolTextGiveContentCommand::unexecute () +{ +#if DEBUG_KP_TOOL_SELECTION && 1 + qCDebug(kpLogCommands) << "kpToolTextGiveContentCommand::unexecute()"; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + // Must have selection text content. + Q_ASSERT (doc->textSelection () && doc->textSelection ()->hasContent ()); + + kpViewManager *vm = viewManager (); + Q_ASSERT (vm); + // All the commands after us have been unexecuted, so we must be back + // to the state we were after our execute(). + Q_ASSERT (vm->textCursorRow () == 0 && vm->textCursorCol () == 0); + + // We can have faith that this is the state of the selection after + // execute(), rather than after the user tried to throw us off by + // simply selecting another region as to do that, a destroy command + // must have been used. + doc->textSelection ()->deleteContent (); + + // This should not have changed from the start of the method. + Q_ASSERT (vm->textCursorRow () == 0 && vm->textCursorCol () == 0); +} + diff --git a/commands/tools/selection/text/kpToolTextGiveContentCommand.h b/commands/tools/selection/text/kpToolTextGiveContentCommand.h new file mode 100644 index 0000000..6a7a2f1 --- /dev/null +++ b/commands/tools/selection/text/kpToolTextGiveContentCommand.h @@ -0,0 +1,59 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolTextGiveContentCommand_H +#define kpToolTextGiveContentCommand_H + + +#include "commands/tools/selection/kpAbstractSelectionContentCommand.h" + + +class kpTextSelection; + + +// Converts a text border (no text lines) to a text selection with 1 empty +// text line. This must be executed before any manipulations can be made +// to a text selection. +// +// Text analog of kpToolSelectionPullFromDocumentCommand. +class kpToolTextGiveContentCommand : + public kpAbstractSelectionContentCommand +{ +public: + kpToolTextGiveContentCommand ( + const kpTextSelection &originalSelBorder, + const QString &name, + kpCommandEnvironment *environ); + ~kpToolTextGiveContentCommand () override; + + void execute () override; + void unexecute () override; +}; + + +#endif // kpToolTextGiveContentCommand_H diff --git a/commands/tools/selection/text/kpToolTextInsertCommand.cpp b/commands/tools/selection/text/kpToolTextInsertCommand.cpp new file mode 100644 index 0000000..7db6e13 --- /dev/null +++ b/commands/tools/selection/text/kpToolTextInsertCommand.cpp @@ -0,0 +1,110 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_TEXT 0 + + +#include "kpToolTextInsertCommand.h" + +#include "layers/selections/text/kpTextSelection.h" +#include "views/manager/kpViewManager.h" + +#include + +//--------------------------------------------------------------------- + +kpToolTextInsertCommand::kpToolTextInsertCommand (const QString &name, + int row, int col, const QString& newText, + kpCommandEnvironment *environ) + : kpNamedCommand (name, environ), + m_row (row), m_col (col) +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + addText (newText); +} + +//--------------------------------------------------------------------- + +// public +void kpToolTextInsertCommand::addText (const QString &moreText) +{ + if (moreText.isEmpty ()) { + return; + } + + QList textLines = textSelection ()->textLines (); + const QString leftHalf = textLines [m_row].left (m_col); + const QString rightHalf = textLines [m_row].mid (m_col); + textLines [m_row] = leftHalf + moreText + rightHalf; + textSelection ()->setTextLines (textLines); + + m_newText += moreText; + m_col += moreText.length (); + + viewManager ()->setTextCursorPosition (m_row, m_col); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +kpCommandSize::SizeType kpToolTextInsertCommand::size () const +{ + return static_cast + (static_cast (m_newText.length ()) * sizeof (QChar)); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +void kpToolTextInsertCommand::execute () +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + QString text = m_newText; + m_newText.clear (); + addText (text); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +void kpToolTextInsertCommand::unexecute () +{ + viewManager ()->setTextCursorPosition (m_row, m_col); + + QList textLines = textSelection ()->textLines (); + const QString leftHalf = textLines [m_row].left (m_col - m_newText.length ()); + const QString rightHalf = textLines [m_row].mid (m_col); + textLines [m_row] = leftHalf + rightHalf; + textSelection ()->setTextLines (textLines); + + m_col -= m_newText.length (); + + viewManager ()->setTextCursorPosition (m_row, m_col); +} + +//--------------------------------------------------------------------- diff --git a/commands/tools/selection/text/kpToolTextInsertCommand.h b/commands/tools/selection/text/kpToolTextInsertCommand.h new file mode 100644 index 0000000..aaf7956 --- /dev/null +++ b/commands/tools/selection/text/kpToolTextInsertCommand.h @@ -0,0 +1,56 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TOOL_TEXT_INSERT_COMMAND_H +#define KP_TOOL_TEXT_INSERT_COMMAND_H + + +#include "commands/kpNamedCommand.h" + + +class kpToolTextInsertCommand : public kpNamedCommand +{ +public: + kpToolTextInsertCommand (const QString &name, + int row, int col, const QString& newText, + kpCommandEnvironment *environ); + + void addText (const QString &moreText); + + kpCommandSize::SizeType size () const override; + + void execute () override; + void unexecute () override; + +protected: + int m_row, m_col; + QString m_newText; +}; + + +#endif // KP_TOOL_TEXT_INSERT_COMMAND_H diff --git a/cursors/kpCursorLightCross.cpp b/cursors/kpCursorLightCross.cpp new file mode 100644 index 0000000..915ea0c --- /dev/null +++ b/cursors/kpCursorLightCross.cpp @@ -0,0 +1,131 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_CURSOR_LIGHT_CROSS 0 + + +#include "kpCursorLightCross.h" + +#include "kpLogCategories.h" + +#include +#include + + +enum PixelValue +{ + White, Black, Transparent +}; + +static void setPixel (unsigned char *colorBitmap, + unsigned char *maskBitmap, + int width, + int y, int x, enum PixelValue pv) +{ + const int ColorBlack = 1; + const int ColorWhite = 0; + + const int MaskOpaque = 1; + const int MaskTransparent = 0; + + int colorValue, maskValue; + + switch (pv) + { + case White: + colorValue = ColorWhite; + maskValue = MaskOpaque; + break; + + case Black: + colorValue = ColorBlack; + maskValue = MaskOpaque; + break; + + case Transparent: + colorValue = ColorWhite; + maskValue = MaskTransparent; + break; + } + + if (colorValue) { + colorBitmap [y * (width / 8) + (x / 8)] |= (1 << (x % 8)); + } + + if (maskValue) { + maskBitmap [y * (width / 8) + (x / 8)] |= (1 << (x % 8)); + } +} + + +const QCursor *kpCursorLightCrossCreate () +{ +#if DEBUG_KP_CURSOR_LIGHT_CROSS + qCDebug(kpLogMisc) << "kpCursorLightCrossCreate() "; +#endif + + const int side = 24; + const int byteSize = (side * side) / 8; + auto *colorBitmap = new unsigned char [byteSize]; + auto *maskBitmap = new unsigned char [byteSize]; + + memset (colorBitmap, 0, byteSize); + memset (maskBitmap, 0, byteSize); + + const int oddSide = side - 1; + const int strokeLen = oddSide * 3 / 8; + + for (int i = 0; i < strokeLen; i++) + { + const enum PixelValue pv = (i % 2) ? Black : White; + + #define X_(val) (val) + #define Y_(val) (val) + #define DRAW(y,x) setPixel (colorBitmap, maskBitmap, side, (y), (x), pv) + // horizontal + DRAW (Y_(side / 2), X_(1 + i)); + DRAW (Y_(side / 2), X_(side - 1 - i)); + + // vertical + DRAW (Y_(1 + i), X_(side / 2)); + DRAW (Y_(side - 1 - i), X_(side / 2)); + #undef DRAW + #undef Y_ + #undef X_ + } + + const QSize size (side, side); + QCursor *cursor = new QCursor ( + QBitmap::fromData (size, colorBitmap, QImage::Format_MonoLSB), + QBitmap::fromData (size, maskBitmap, QImage::Format_MonoLSB)); + + delete [] maskBitmap; + delete [] colorBitmap; + + return cursor; +} + diff --git a/cursors/kpCursorLightCross.h b/cursors/kpCursorLightCross.h new file mode 100644 index 0000000..941e825 --- /dev/null +++ b/cursors/kpCursorLightCross.h @@ -0,0 +1,39 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_CURSOR_LIGHT_CROSS_H +#define KP_CURSOR_LIGHT_CROSS_H + + +class QCursor; + + +const QCursor *kpCursorLightCrossCreate (); + + +#endif // KP_CURSOR_LIGHT_CROSS_H diff --git a/cursors/kpCursorProvider.cpp b/cursors/kpCursorProvider.cpp new file mode 100644 index 0000000..fdc3d51 --- /dev/null +++ b/cursors/kpCursorProvider.cpp @@ -0,0 +1,48 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "kpCursorProvider.h" + +#include "kpCursorLightCross.h" + +#include + + +static const QCursor *TheLightCursor = nullptr; + + +// public static +QCursor kpCursorProvider::lightCross () +{ + // TODO: don't leak (although it's cleaned up on exit by OS anyway) + if (!::TheLightCursor) { + ::TheLightCursor = kpCursorLightCrossCreate (); + } + + return *::TheLightCursor; +} diff --git a/cursors/kpCursorProvider.h b/cursors/kpCursorProvider.h new file mode 100644 index 0000000..9714261 --- /dev/null +++ b/cursors/kpCursorProvider.h @@ -0,0 +1,43 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_CURSOR_PROVIDER_H +#define KP_CURSOR_PROVIDER_H + + +class QCursor; + + +class kpCursorProvider +{ +public: + static QCursor lightCross (); +}; + + +#endif // KP_CURSOR_PROVIDER_H diff --git a/dialogs/imagelib/effects/kpEffectsDialog.cpp b/dialogs/imagelib/effects/kpEffectsDialog.cpp new file mode 100644 index 0000000..926581b --- /dev/null +++ b/dialogs/imagelib/effects/kpEffectsDialog.cpp @@ -0,0 +1,372 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECTS_DIALOG 0 + + +#include "kpEffectsDialog.h" + +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "widgets/imagelib/effects/kpEffectBalanceWidget.h" +#include "widgets/imagelib/effects/kpEffectBlurSharpenWidget.h" +#include "widgets/imagelib/effects/kpEffectEmbossWidget.h" +#include "widgets/imagelib/effects/kpEffectFlattenWidget.h" +#include "widgets/imagelib/effects/kpEffectHSVWidget.h" +#include "widgets/imagelib/effects/kpEffectInvertWidget.h" +#include "widgets/imagelib/effects/kpEffectReduceColorsWidget.h" +#include "widgets/imagelib/effects/kpEffectToneEnhanceWidget.h" +#include "pixmapfx/kpPixmapFX.h" +#include "environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.h" + +#include +#include "kpLogCategories.h" +#include + +#include +#include +#include +#include +#include +#include + + +// protected static +int kpEffectsDialog::s_lastWidth = 640; +int kpEffectsDialog::s_lastHeight = 620; + + +kpEffectsDialog::kpEffectsDialog (bool actOnSelection, + kpTransformDialogEnvironment *_env, + QWidget *parent, + int defaultSelectedEffect) + : kpTransformPreviewDialog (kpTransformPreviewDialog::Preview, + true/*reserve top row*/, + QString()/*caption*/, + QString()/*afterActionText (no Dimensions Group Box)*/, + actOnSelection, + _env, + parent), + m_delayedUpdateTimer (new QTimer (this)), + m_effectsComboBox (nullptr), + m_settingsGroupBox (nullptr), + m_settingsLayout (nullptr), + m_effectWidget (nullptr) +{ +#if DEBUG_KP_EFFECTS_DIALOG + qCDebug(kpLogDialogs) << "kpEffectsDialog::kpEffectsDialog()"; +#endif + const bool e = updatesEnabled (); + setUpdatesEnabled (false); + + + if (actOnSelection) { + setWindowTitle (i18nc ("@title:window", "More Image Effects (Selection)")); + } + else { + setWindowTitle (i18nc ("@title:window", "More Image Effects")); + } + + + m_delayedUpdateTimer->setSingleShot (true); + connect (m_delayedUpdateTimer, &QTimer::timeout, + this, &kpEffectsDialog::slotUpdateWithWaitCursor); + + + QWidget *effectContainer = new QWidget (mainWidget ()); + + auto *containerLayout = new QHBoxLayout (effectContainer); + containerLayout->setContentsMargins(0, 0, 0, 0); + + QLabel *label = new QLabel (i18n ("&Effect:"), effectContainer); + + m_effectsComboBox = new QComboBox (effectContainer); + // Keep in alphabetical order. + // TODO: What about translations? + // sync: order in selectEffect(). + m_effectsComboBox->addItem (i18n ("Balance")); + m_effectsComboBox->addItem (i18n ("Emboss")); + m_effectsComboBox->addItem (i18n ("Flatten")); + m_effectsComboBox->addItem (i18n ("Histogram Equalizer")); + m_effectsComboBox->addItem (i18n ("Hue, Saturation, Value")); + m_effectsComboBox->addItem (i18n ("Invert")); + m_effectsComboBox->addItem (i18n ("Reduce Colors")); + m_effectsComboBox->addItem (i18n ("Soften & Sharpen")); + + containerLayout->addWidget (label); + containerLayout->addWidget (m_effectsComboBox, 1); + + label->setBuddy (m_effectsComboBox); + + addCustomWidgetToFront (effectContainer); + + + m_settingsGroupBox = new QGroupBox (mainWidget ()); + m_settingsLayout = new QVBoxLayout ( m_settingsGroupBox ); + addCustomWidgetToBack (m_settingsGroupBox); + + + connect (m_effectsComboBox, static_cast(&QComboBox::activated), + this, &kpEffectsDialog::selectEffect); + + + selectEffect (defaultSelectedEffect); + + + resize (s_lastWidth, s_lastHeight); + + +#if DEBUG_KP_EFFECTS_DIALOG + qCDebug(kpLogDialogs) << "about to setUpdatesEnabled()"; +#endif + // OPT: The preview pixmap gets recalculated here and then possibly + // again when QResizeEvent fires, when the dialog is shown. + setUpdatesEnabled (e); +#if DEBUG_KP_EFFECTS_DIALOG + qCDebug(kpLogDialogs) << endl + << endl + << endl; +#endif +} + +kpEffectsDialog::~kpEffectsDialog () +{ + s_lastWidth = width (); + s_lastHeight = height (); +} + + +// public virtual [base kpTransformPreviewDialog] +bool kpEffectsDialog::isNoOp () const +{ + if (!m_effectWidget) { + return true; + } + + return m_effectWidget->isNoOp (); +} + +// public +kpEffectCommandBase *kpEffectsDialog::createCommand () const +{ + if (!m_effectWidget) { + return nullptr; + } + + return m_effectWidget->createCommand (m_environ->commandEnvironment ()); +} + + +// protected virtual [base kpTransformPreviewDialog] +QSize kpEffectsDialog::newDimensions () const +{ + kpDocument *doc = document (); + if (!doc) { + return {}; + } + + return {doc->width (m_actOnSelection), doc->height (m_actOnSelection)}; +} + +// protected virtual [base kpTransformPreviewDialog] +QImage kpEffectsDialog::transformPixmap (const QImage &pixmap, + int targetWidth, int targetHeight) const +{ + QImage pixmapWithEffect; + + if (m_effectWidget && !m_effectWidget->isNoOp ()) { + pixmapWithEffect = m_effectWidget->applyEffect (pixmap); + } + else { + pixmapWithEffect = pixmap; + } + + return kpPixmapFX::scale (pixmapWithEffect, targetWidth, targetHeight); +} + + +// public +int kpEffectsDialog::selectedEffect () const +{ + return m_effectsComboBox->currentIndex (); +} + +// public slot +void kpEffectsDialog::selectEffect (int which) +{ +#if DEBUG_KP_EFFECTS_DIALOG + qCDebug(kpLogDialogs) << "kpEffectsDialog::selectEffect(" << which << ")"; +#endif + + if (which < 0 || + which >= m_effectsComboBox->count ()) + { + return; + } + + if (which != m_effectsComboBox->currentIndex ()) { + m_effectsComboBox->setCurrentIndex (which); + } + + + delete m_effectWidget; + m_effectWidget = nullptr; + + + m_settingsGroupBox->setWindowTitle(QString()); + +#define CREATE_EFFECT_WIDGET(name) \ + m_effectWidget = new name (m_actOnSelection, m_settingsGroupBox) + // sync: order in constructor. + switch (which) + { + case 0: + CREATE_EFFECT_WIDGET (kpEffectBalanceWidget); + break; + + case 1: + CREATE_EFFECT_WIDGET (kpEffectEmbossWidget); + break; + + case 2: + CREATE_EFFECT_WIDGET (kpEffectFlattenWidget); + break; + + case 3: + CREATE_EFFECT_WIDGET (kpEffectToneEnhanceWidget); + break; + + case 4: + CREATE_EFFECT_WIDGET (kpEffectHSVWidget); + break; + + case 5: + CREATE_EFFECT_WIDGET (kpEffectInvertWidget); + break; + + case 6: + CREATE_EFFECT_WIDGET (kpEffectReduceColorsWidget); + break; + + case 7: + CREATE_EFFECT_WIDGET (kpEffectBlurSharpenWidget); + break; + } +#undef CREATE_EFFECT_WIDGET + + + if (m_effectWidget) + { + const bool e = updatesEnabled (); + setUpdatesEnabled (false); + + #if DEBUG_KP_EFFECTS_DIALOG + qCDebug(kpLogDialogs) << "widget exists for effect #"; + #endif + m_settingsGroupBox->setTitle (m_effectWidget->caption ()); + + // Show widget. + // + // Don't resize the whole dialog when doing this. + // This seems to work magically without any extra code with Qt4. + #if DEBUG_KP_EFFECTS_DIALOG + qCDebug(kpLogDialogs) << "addWidget"; + #endif + m_settingsLayout->addWidget (m_effectWidget); + #if DEBUG_KP_EFFECTS_DIALOG + qCDebug(kpLogDialogs) << "show widget"; + #endif + m_effectWidget->show (); + + connect (m_effectWidget, &kpEffectWidgetBase::settingsChangedNoWaitCursor, + this, &kpEffectsDialog::slotUpdate); + connect (m_effectWidget, &kpEffectWidgetBase::settingsChanged, + this, &kpEffectsDialog::slotUpdateWithWaitCursor); + connect (m_effectWidget, &kpEffectWidgetBase::settingsChangedDelayed, + this, &kpEffectsDialog::slotDelayedUpdate); + + #if DEBUG_KP_EFFECTS_DIALOG + qCDebug(kpLogDialogs) << "about to setUpdatesEnabled()"; + #endif + setUpdatesEnabled (e); + } + + +#if DEBUG_KP_EFFECTS_DIALOG + qCDebug(kpLogDialogs) << "done" + << endl + << endl + << endl; +#endif +} + + +// protected slot virtual [base kpTransformPreviewDialog] +void kpEffectsDialog::slotUpdate () +{ +#if DEBUG_KP_EFFECTS_DIALOG + qCDebug(kpLogDialogs) << "kpEffectsDialog::slotUpdate()" + << " timerActive=" << m_delayedUpdateTimer->isActive () + << endl; +#endif + + m_delayedUpdateTimer->stop (); + + kpTransformPreviewDialog::slotUpdate (); +} + +// protected slot virtual [base kpTransformPreviewDialog] +void kpEffectsDialog::slotUpdateWithWaitCursor () +{ +#if DEBUG_KP_EFFECTS_DIALOG + qCDebug(kpLogDialogs) << "kpEffectsDialog::slotUpdateWithWaitCursor()" + << " timerActive=" << m_delayedUpdateTimer->isActive () + << endl; +#endif + + m_delayedUpdateTimer->stop (); + + kpTransformPreviewDialog::slotUpdateWithWaitCursor (); +} + + +// protected slot +void kpEffectsDialog::slotDelayedUpdate () +{ +#if DEBUG_KP_EFFECTS_DIALOG + qCDebug(kpLogDialogs) << "kpEffectsDialog::slotDelayedUpdate()" + << " timerActive=" << m_delayedUpdateTimer->isActive () + << endl; +#endif + m_delayedUpdateTimer->stop (); + + // (single shot) + m_delayedUpdateTimer->start (400/*ms*/); +} + + diff --git a/dialogs/imagelib/effects/kpEffectsDialog.h b/dialogs/imagelib/effects/kpEffectsDialog.h new file mode 100644 index 0000000..5b29290 --- /dev/null +++ b/dialogs/imagelib/effects/kpEffectsDialog.h @@ -0,0 +1,91 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_EFFECTS_DIALOG_H +#define KP_EFFECTS_DIALOG_H + + +#include "dialogs/imagelib/transforms/kpTransformPreviewDialog.h" + + +class QComboBox; +class QGroupBox; +class QImage; +class QTimer; +class QVBoxLayout; + +class kpEffectCommandBase; +class kpEffectWidgetBase; + + +class kpEffectsDialog : public kpTransformPreviewDialog +{ +Q_OBJECT + +public: + // Specifying is more efficient than leaving it + // as 0 and then calling selectEffect() afterwards. + kpEffectsDialog (bool actOnSelection, + kpTransformDialogEnvironment *_env, + QWidget *parent, + int defaultSelectedEffect = 0); + ~kpEffectsDialog () override; + + bool isNoOp () const override; + kpEffectCommandBase *createCommand () const; + +protected: + QSize newDimensions () const override; + QImage transformPixmap (const QImage &pixmap, + int targetWidth, int targetHeight) const override; + +public: + int selectedEffect () const; +public slots: + void selectEffect (int which); + +protected slots: + void slotUpdate () override; + void slotUpdateWithWaitCursor () override; + + void slotDelayedUpdate (); + +protected: + static int s_lastWidth, s_lastHeight; + + QTimer *m_delayedUpdateTimer; + + QComboBox *m_effectsComboBox; + QGroupBox *m_settingsGroupBox; + QVBoxLayout *m_settingsLayout; + + kpEffectWidgetBase *m_effectWidget; +}; + + +#endif // KP_EFFECTS_DIALOG_H diff --git a/dialogs/imagelib/kpDocumentMetaInfoDialog.cpp b/dialogs/imagelib/kpDocumentMetaInfoDialog.cpp new file mode 100644 index 0000000..a475ea0 --- /dev/null +++ b/dialogs/imagelib/kpDocumentMetaInfoDialog.cpp @@ -0,0 +1,753 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_DOCUMENT_META_INFO_DIALOG 0 + + +#include "kpDocumentMetaInfoDialog.h" + +#include "kpDefs.h" +#include "imagelib/kpDocumentMetaInfo.h" + +#include +#include +#include "kpLogCategories.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +struct kpDocumentMetaInfoDialogPrivate +{ + const kpDocumentMetaInfo *originalMetaInfoPtr; + + QDoubleSpinBox *horizDpiInput, *vertDpiInput; + QSpinBox *horizOffsetInput, *vertOffsetInput; + + QTableWidget *fieldsTableWidget; + QPushButton *fieldsAddRowButton, *fieldsDeleteRowButton, *fieldsResetButton; +}; + + +// (shared by all dialogs, across all main windows, in a KolourPaint instance) +static int LastWidth = -1, LastHeight = -1; + + +// sync: You must keep DpiMinStep = 10 ^ (-DpiPrecision). +// +// You can increase the precision to reduce the chance of inadvertently changing +// the resolution when converting from kpDocumentMetaInfo's "dots per meter" +// to our "dots per inch" and back. It would be bad if simply going into this +// dialog and pressing OK changed the resolution (it's unlikely but I still think +// it might happen with the current precision). +// TODO: On a related note, for many particular resolutions, if the user enters +// one of them into the UI, presses OK and then comes back to the dialog, +// s/he is presented with a different resolution to the one typed in. +// Maybe make DotsPerMeter[XY] be of type "double" instead of "int" to +// solve this problem? +// +// Of course, if you increase the precision too much, the minimum step will +// become so small that it will start experiencing floating point inaccuracies +// esp. since we use it for the "DpiUnspecified" hack. +static const int DpiPrecision = 3; +static const double DpiMinStep = 0.001; + +static const double DpiLegalMin = + kpDocumentMetaInfo::MinDotsPerMeter / KP_INCHES_PER_METER; + +// Out of range represents unspecified DPI. +static const double DpiUnspecified = ::DpiLegalMin - ::DpiMinStep; + +static const double DpiInputMin = ::DpiUnspecified; +static const double DpiInputMax = + kpDocumentMetaInfo::MaxDotsPerMeter / KP_INCHES_PER_METER; + +// The increment the DPI spinboxes jump by when they're clicked. +// +// We make this relatively big since people don't usually just increase their +// DPIs by 1 or so -- they are usually changing from say, 72, to 96. +// +// Obviously, making it equal to DpiMinStep is too slow a UI. Therefore, with +// our big setting, the user will still have to manually change the value in +// the spinbox, using the keyboard, after all their clicking to ensure it is +// exactly the value they want. +static const double DpiInputStep = 10; + + +// TODO: Halve groupbox layout margins in every other file since it doesn't +// seem to be need in Qt4. +kpDocumentMetaInfoDialog::kpDocumentMetaInfoDialog ( + const kpDocumentMetaInfo *docMetaInfo, + QWidget *parent) + + : QDialog (parent), + d (new kpDocumentMetaInfoDialogPrivate ()) +{ + d->originalMetaInfoPtr = docMetaInfo; + + + setWindowTitle (i18nc ("@title:window", "Document Properties")); + auto * buttons = new QDialogButtonBox (QDialogButtonBox::Ok | + QDialogButtonBox::Cancel, this); + + connect (buttons, &QDialogButtonBox::accepted, this, &kpDocumentMetaInfoDialog::accept); + connect (buttons, &QDialogButtonBox::rejected, this, &kpDocumentMetaInfoDialog::reject); + + auto *baseWidget = new QWidget (this); + + auto *dialogLayout = new QVBoxLayout (this); + dialogLayout->addWidget (baseWidget); + dialogLayout->addWidget (buttons); + + + // + // DPI Group Box + // + + Q_ASSERT (::DpiInputMin < ::DpiInputMax); + + auto *dpiGroupBox = new QGroupBox(i18n("Dots &Per Inch (DPI)"), baseWidget); + + d->horizDpiInput = new QDoubleSpinBox(dpiGroupBox); + d->horizDpiInput->setRange(::DpiInputMin, ::DpiInputMax); + d->horizDpiInput->setValue(0.0); + d->horizDpiInput->setSingleStep(::DpiInputStep); + d->horizDpiInput->setDecimals(::DpiPrecision); + d->horizDpiInput->setSpecialValueText(i18n("Unspecified")); + + auto *dpiXLabel = new QLabel ( + i18nc ("Horizontal DPI 'x' Vertical DPI", " x "), dpiGroupBox); + dpiXLabel->setAlignment (Qt::AlignCenter); + + d->vertDpiInput = new QDoubleSpinBox(dpiGroupBox); + d->vertDpiInput->setRange(::DpiInputMin, ::DpiInputMax); + d->vertDpiInput->setValue(0.0); + d->vertDpiInput->setSingleStep(::DpiInputStep); + d->vertDpiInput->setDecimals(::DpiPrecision); + d->vertDpiInput->setSpecialValueText(i18n("Unspecified")); + + + auto *dpiLay = new QGridLayout(dpiGroupBox); + + dpiLay->addWidget(new QLabel(i18n("Horizontal:")), 0, 0, Qt::AlignHCenter); + dpiLay->addWidget(d->horizDpiInput, 1, 0); + dpiLay->addWidget(dpiXLabel, 0, 1); + dpiLay->addWidget(new QLabel(i18n("Vertical:")), 0, 2, Qt::AlignHCenter); + dpiLay->addWidget(d->vertDpiInput, 1, 2); + + dpiLay->setRowStretch(2, 1); + + + dpiGroupBox->setWhatsThis ( + i18n ( + "" + "

Dots Per Inch (DPI) specifies the number of pixels" + " of the image that should be printed inside one inch (2.54cm).

" + + "

The higher the image's DPI, the smaller the printed image." + " Note that your printer is unlikely to produce high" + " quality prints if you increase this to more than 300 or 600 DPI," + " depending on the printer.

" + + "

If you would like to print the image so that it is the same" + " size as it is displayed on the screen, set the image's DPI" + " values to be the same as the screen's.

" + + // TODO: This is currently not true! + // See "96dpi" TODO in kpMainWindow::sendPixmapToPrinter(). + // This also why we don't try to report the current screen DPI + // for the above paragraph. + "

If either DPI value is Unspecified, the image will also" + " be printed to be the same size as on the screen.

" + + "

Not all image formats support DPI values. If the format you" + " save in does not support them, they will not be saved.

" + "
" + )); + + + // + // Offset Group Box + // + + auto *offsetGroupBox = new QGroupBox(i18n ("O&ffset"), baseWidget); + + d->horizOffsetInput = new QSpinBox; + d->horizOffsetInput->setRange(kpDocumentMetaInfo::MinOffset, kpDocumentMetaInfo::MaxOffset); + + d->vertOffsetInput = new QSpinBox; + d->vertOffsetInput->setRange(kpDocumentMetaInfo::MinOffset, kpDocumentMetaInfo::MaxOffset); + + auto *offsetLay = new QGridLayout(offsetGroupBox); + + offsetLay->addWidget(new QLabel(i18n("Horizontal:")), 0, 0, Qt::AlignHCenter); + offsetLay->addWidget(d->horizOffsetInput, 1, 0); + offsetLay->addWidget(new QLabel(i18n("Vertical:")), 0, 1, Qt::AlignHCenter); + offsetLay->addWidget(d->vertOffsetInput, 1, 1); + + offsetLay->setRowStretch (2, 1); + + + offsetGroupBox->setWhatsThis ( + i18n ( + "" + "

The Offset is the relative position where this image" + " should be placed, compared to other images.

" + + "

Not all image formats support the Offset feature." + " If the format you save in does not support it, the values" + " specified here will not be saved.

" + "
" + )); + + + // + // Fields Group Box + // + + + auto *fieldsGroupBox = new QGroupBox (i18n ("&Text Fields"), + baseWidget); + + d->fieldsTableWidget = new QTableWidget (fieldsGroupBox); + d->fieldsTableWidget->setEditTriggers(QAbstractItemView::AllEditTriggers); + + connect (d->fieldsTableWidget, &QTableWidget::currentCellChanged, + this, &kpDocumentMetaInfoDialog::slotFieldsCurrentCellChanged); + + connect (d->fieldsTableWidget, &QTableWidget::itemChanged, + this, &kpDocumentMetaInfoDialog::slotFieldsItemChanged); + + d->fieldsAddRowButton = new QPushButton (i18n ("&Add Row"), + fieldsGroupBox); + connect (d->fieldsAddRowButton, &QPushButton::clicked, + this, &kpDocumentMetaInfoDialog::slotFieldsAddRowButtonClicked); + + d->fieldsDeleteRowButton = new QPushButton (i18n ("&Delete Row"), + fieldsGroupBox); + connect (d->fieldsDeleteRowButton, &QPushButton::clicked, + this, &kpDocumentMetaInfoDialog::slotFieldsDeleteRowButtonClicked); + + d->fieldsResetButton = new QPushButton (i18n ("&Reset"), + fieldsGroupBox); + connect (d->fieldsResetButton, &QPushButton::clicked, + this, &kpDocumentMetaInfoDialog::setUIToOriginalMetaInfo); + + auto *fieldsButtonsLayout = new QHBoxLayout (); + fieldsButtonsLayout->addWidget (d->fieldsAddRowButton); + fieldsButtonsLayout->addWidget (d->fieldsDeleteRowButton); + fieldsButtonsLayout->addStretch (); + fieldsButtonsLayout->addWidget (d->fieldsResetButton); + + auto *fieldsLayout = new QVBoxLayout (fieldsGroupBox); + + fieldsLayout->addWidget (d->fieldsTableWidget); + fieldsLayout->addLayout (fieldsButtonsLayout); + + + fieldsGroupBox->setWhatsThis ( + i18n ( + "" + "

Text Fields provide extra information about the image." + " This is probably a comment area that you can freely write any text in.

" + + "

However, this is format-specific so the fields could theoretically be" + " computer-interpreted data - that you should not modify -" + " but this is unlikely.

" + + "

Not all image formats support Text Fields. If the format" + " you save in does not support them, they will not be saved.

" + "
" + )); + + + // + // Global Layout + // + auto *baseLayout = new QGridLayout (baseWidget); + baseLayout->setContentsMargins(0, 0, 0, 0); + + // Col 0 + baseLayout->addWidget (dpiGroupBox, 0, 0); + baseLayout->addWidget (offsetGroupBox, 1, 0); + + // Col 1 + baseLayout->addWidget (fieldsGroupBox, 0, 1, 2/*row span*/, 1/*col span*/); + baseLayout->setColumnStretch (1, 1/*stretch*/); + + + // + // Remaining UI Setup + // + + + setUIToOriginalMetaInfo (); + + + if (::LastWidth > 0 && ::LastHeight > 0) { + resize (::LastWidth, ::LastHeight); + } +} + +//--------------------------------------------------------------------- + +kpDocumentMetaInfoDialog::~kpDocumentMetaInfoDialog () +{ + ::LastWidth = width (); + ::LastHeight = height (); + + delete d; +} + +//--------------------------------------------------------------------- +// private + +void kpDocumentMetaInfoDialog::editCell (int r, int c) +{ + d->fieldsTableWidget->setCurrentCell (r, c); + d->fieldsTableWidget->editItem (d->fieldsTableWidget->item (r, c)); +} + +//--------------------------------------------------------------------- +// private slot + +void kpDocumentMetaInfoDialog::setUIToOriginalMetaInfo () +{ + // Set DPI spinboxes. + d->horizDpiInput->setValue (d->originalMetaInfoPtr->dotsPerMeterX () / + KP_INCHES_PER_METER); + d->vertDpiInput->setValue (d->originalMetaInfoPtr->dotsPerMeterY () / + KP_INCHES_PER_METER); + + + // Set Offset spinboxes. + d->horizOffsetInput->setValue (d->originalMetaInfoPtr->offset ().x ()); + d->vertOffsetInput->setValue (d->originalMetaInfoPtr->offset ().y ()); + + + // Set Text Fields. + // + // Block itemChanged() signal as slotFieldsItemChanged() should not get called + // when rows are half-created. + const bool b = d->fieldsTableWidget->blockSignals (true); + { + d->fieldsTableWidget->clear (); + + d->fieldsTableWidget->setRowCount (d->originalMetaInfoPtr->textKeys ().size ()); + d->fieldsTableWidget->setColumnCount (2); + + QStringList fieldsHeader; + fieldsHeader << i18n ("Key") << i18n ("Value"); + d->fieldsTableWidget->setHorizontalHeaderLabels (fieldsHeader); + + int row = 0; + for (const auto &key : d->originalMetaInfoPtr->textKeys ()) + { + d->fieldsTableWidget->setItem (row, 0/*1st col*/, + new QTableWidgetItem (key)); + d->fieldsTableWidget->setItem (row, 1/*2nd col*/, + new QTableWidgetItem (d->originalMetaInfoPtr->text (key))); + + row++; + } + + fieldsAppendEmptyRow (); + } + d->fieldsTableWidget->blockSignals (b); + + + editCell (0/*row*/, 0/*col*/); + + + enableFieldsDeleteRowButtonIfShould (); +} + +//--------------------------------------------------------------------- +// public + +bool kpDocumentMetaInfoDialog::isNoOp () const +{ + return (metaInfo () == *d->originalMetaInfoPtr); +} + +//--------------------------------------------------------------------- + +// public +kpDocumentMetaInfo kpDocumentMetaInfoDialog::originalMetaInfo () const +{ + return *d->originalMetaInfoPtr; +} + +// public +kpDocumentMetaInfo kpDocumentMetaInfoDialog::metaInfo ( + QString *errorMessage) const +{ + if (errorMessage) + { + // No errors to start with. + *errorMessage = QString (); + } + + + kpDocumentMetaInfo ret; + + + if (d->horizDpiInput->value () < ::DpiLegalMin) { + ret.setDotsPerMeterX (0/*unspecified*/); + } + else { + ret.setDotsPerMeterX (qRound (d->horizDpiInput->value () * KP_INCHES_PER_METER)); + } + + if (d->vertDpiInput->value () < ::DpiLegalMin) { + ret.setDotsPerMeterY (0/*unspecified*/); + } + else { + ret.setDotsPerMeterY (qRound (d->vertDpiInput->value () * KP_INCHES_PER_METER)); + } + + + ret.setOffset (QPoint (d->horizOffsetInput->value (), + d->vertOffsetInput->value ())); + + + for (int r = 0; r < d->fieldsTableWidget->rowCount (); r++) + { + const QString key = d->fieldsTableWidget->item (r, 0)->text (); + const QString value = d->fieldsTableWidget->item (r, 1)->text (); + + // Empty key? + if (key.isEmpty ()) + { + // Empty value too? + if (value.isEmpty ()) + { + // Ignore empty row. + continue; + } + // Value without a key? + + + if (errorMessage) + { + *errorMessage = + ki18n ("The text value \"%1\" on line %2 requires a key.") + .subs (value).subs (r + 1/*count from 1*/).toString (); + + // Print only 1 error message per method invocation. + errorMessage = nullptr; + } + + // Ignore. + continue; + + } + + // Duplicate key? + if (ret.textKeys ().contains (key)) + { + if (errorMessage) + { + int q; + for (q = 0; q < r; q++) + { + if (d->fieldsTableWidget->item (q, 0)->text () == key) { + break; + } + } + Q_ASSERT (q != r); + + *errorMessage = + ki18n ("All text keys must be unique. The text key \"%1\"" + " on lines %2 and %3 are identical.") + .subs (key) + .subs (q + 1/*count from 1*/) + .subs (r + 1/*count from 1*/) + .toString (); + + // Print only 1 error message per method invocation. + errorMessage = nullptr; + } + + // Ignore this duplicate - keep the first value of the key. + continue; + } + + ret.setText (key, value); + + } // for (r = 0; r < table widget rows; r++) { + + + return ret; +} + + +// private +void kpDocumentMetaInfoDialog::fieldsUpdateVerticalHeader () +{ + QStringList vertLabels; + for (int r = 1; r <= d->fieldsTableWidget->rowCount (); r++) { + vertLabels << QString::number (r); + } + + d->fieldsTableWidget->setVerticalHeaderLabels (vertLabels); +} + + +// private +void kpDocumentMetaInfoDialog::fieldsAddEmptyRow (int atRow) +{ + // Block itemChanged() signal as slotFieldsItemChanged() should not get called + // when rows are half-created. + const bool b = d->fieldsTableWidget->blockSignals (true); + { + d->fieldsTableWidget->insertRow (atRow); + + d->fieldsTableWidget->setItem (atRow, 0, new QTableWidgetItem (QString ())); + d->fieldsTableWidget->setItem (atRow, 1, new QTableWidgetItem (QString ())); + } + d->fieldsTableWidget->blockSignals (b); + + // Hack around Qt's failure to redraw these sometimes. + fieldsUpdateVerticalHeader (); + + enableFieldsDeleteRowButtonIfShould (); +} + +// private +void kpDocumentMetaInfoDialog::fieldsAppendEmptyRow () +{ + fieldsAddEmptyRow (d->fieldsTableWidget->rowCount ()); +} + + +// private +bool kpDocumentMetaInfoDialog::isFieldsRowDeleteable (int row) const +{ + // Can't delete no row and can't delete last (always blank) row, which + // is used to make it easy for the user to add rows without pressing + // the "Add" button explicitly. + return (row >= 0 && row < d->fieldsTableWidget->rowCount () - 1); +} + +// private +void kpDocumentMetaInfoDialog::fieldsDeleteRow (int r) +{ +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + qCDebug(kpLogDialogs) << "kpDocumentMetaInfoDialog::fieldsDeleteRow(" + << "row=" << r << ")" + << " currentRow=" << d->fieldsTableWidget->currentRow (); +#endif + + Q_ASSERT (isFieldsRowDeleteable (r)); + + if (r == d->fieldsTableWidget->currentRow ()) + { + // Assertion follows from previous assertion. + const int newRow = r + 1; + #if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + qCDebug(kpLogDialogs) << "\tnewRow=" << newRow; + #endif + Q_ASSERT (newRow < d->fieldsTableWidget->rowCount ()); + + int newCol = d->fieldsTableWidget->currentColumn (); + #if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + qCDebug(kpLogDialogs) << "\tnewCol=" << newCol; + #endif + if (newCol != 0 && newCol != 1) + { + newCol = 0; + #if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + qCDebug(kpLogDialogs) << "\t\tcorrecting to " << newCol; + #endif + } + + // WARNING: You must call this _before_ deleting the row. Else, you'll + // trigger a Qt bug where if the editor is active on the row to + // be deleted, it might crash. To reproduce, move this line to + // after removeRow() (and subtract 1 from newRow) and + // press the "Delete Row" button several times in succession + // very quickly. + // + // TODO: This usually results in a redraw error if the scrollbar scrolls + // after deleting the 2nd last row. Qt bug. + editCell (newRow, newCol); + } + + + d->fieldsTableWidget->removeRow (r); + + + fieldsUpdateVerticalHeader (); + + enableFieldsDeleteRowButtonIfShould (); +} + + +// private +void kpDocumentMetaInfoDialog::enableFieldsDeleteRowButtonIfShould () +{ +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + qCDebug(kpLogDialogs) << "kpDocumentMetaInfoDialog::enableFieldsDeleteRowButtonIfShould()"; +#endif + + const int r = d->fieldsTableWidget->currentRow (); +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + qCDebug(kpLogDialogs) << "\tr=" << r; +#endif + + d->fieldsDeleteRowButton->setEnabled (isFieldsRowDeleteable (r)); +} + + +// private slot +void kpDocumentMetaInfoDialog::slotFieldsCurrentCellChanged (int row, int col, + int oldRow, int oldCol) +{ +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + qCDebug(kpLogDialogs) << "kpDocumentMetaInfoDialog::slotFieldsCurrentCellChanged(" + << "row=" << row << ",col=" << col + << ",oldRow=" << oldRow << ",oldCol=" << oldCol + << ")" << endl; +#endif + + (void) row; + (void) col; + (void) oldRow; + (void) oldCol; + + enableFieldsDeleteRowButtonIfShould (); +} + +//--------------------------------------------------------------------- +// private slot + +void kpDocumentMetaInfoDialog::slotFieldsItemChanged (QTableWidgetItem *it) +{ +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + qCDebug(kpLogDialogs) << "kpDocumentMetaInfoDialog::slotFieldsItemChanged(" + << "item=" << it << ") rows=" << d->fieldsTableWidget->rowCount (); +#endif + + const int r = d->fieldsTableWidget->row (it); +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + qCDebug(kpLogDialogs) << "\tr=" << r; +#endif + Q_ASSERT (r >= 0 && r < d->fieldsTableWidget->rowCount ()); + + const QString key = d->fieldsTableWidget->item (r, 0)->text (); +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + qCDebug(kpLogDialogs) << " key='" << key << "'"; +#endif + + const QString value = d->fieldsTableWidget->item (r, 1)->text (); +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + qCDebug(kpLogDialogs) << " value='" << value << "'"; +#endif + + // At the last row? + if (r == d->fieldsTableWidget->rowCount () - 1) + { + // Typed some text? + if (!key.isEmpty () || !value.isEmpty ()) + { + // LOTODO: If we're called due to the cell's text being finalized + // as a result of the user pressing the "Add Row" button, + // should this really append a row since + // slotFieldsAddRowButtonClicked() button is going to add + // another one? That's two rows when the user only clicked + // that button once! + fieldsAppendEmptyRow (); + } + } +} + +//--------------------------------------------------------------------- +// private slot + +void kpDocumentMetaInfoDialog::slotFieldsAddRowButtonClicked () +{ +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + qCDebug(kpLogDialogs) << "kpDocumentMetaInfoDialog::slotFieldsAddRowButtonClicked()"; +#endif + + const int r = d->fieldsTableWidget->currentRow (); +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + qCDebug(kpLogDialogs) << "\tr=" << r; +#endif + + // (if no row is selected, r = -1) + fieldsAddEmptyRow (r + 1); + + // Edit the key of this new row (column 0). + // No one edits the value first (column 1). + editCell ((r + 1)/*row*/, 0/*col*/); +} + +//--------------------------------------------------------------------- + +// private slot +void kpDocumentMetaInfoDialog::slotFieldsDeleteRowButtonClicked () +{ +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + qCDebug(kpLogDialogs) << "kpDocumentMetaInfoDialog::slotFieldsDeleteRowButtonClicked()"; +#endif + + const int r = d->fieldsTableWidget->currentRow (); +#if DEBUG_KP_DOCUMENT_META_INFO_DIALOG + qCDebug(kpLogDialogs) << "\tr=" << r; +#endif + + Q_ASSERT (isFieldsRowDeleteable (r)); + fieldsDeleteRow (r); +} + + +// private slot virtual [base QDialog] +void kpDocumentMetaInfoDialog::accept () +{ + // Validate text fields. + QString errorMessage; + (void) metaInfo (&errorMessage); + if (!errorMessage.isEmpty ()) + { + KMessageBox::sorry (this, errorMessage, i18nc ("@title:window", "Invalid Text Fields")); + return; + } + + QDialog::accept (); +} + + diff --git a/dialogs/imagelib/kpDocumentMetaInfoDialog.h b/dialogs/imagelib/kpDocumentMetaInfoDialog.h new file mode 100644 index 0000000..1b73e33 --- /dev/null +++ b/dialogs/imagelib/kpDocumentMetaInfoDialog.h @@ -0,0 +1,114 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpDocumentMetaInfoDialog_H +#define kpDocumentMetaInfoDialog_H + + +#include + + +class QTableWidgetItem; + +class kpDocumentMetaInfo; + + +// Dialog for editing document meta information (see kpDocumentMetaInfo). +// It contains: +// +// 1. DPI spinboxes +// 2. Offset spinboxes +// 3. Text Fields +// +// The Text Fields widget always keeps an empty key-value row at the bottom. +// If text is entered in this row, a new one is created. This allows users +// to add new rows without pressing any pushbuttons. +class kpDocumentMetaInfoDialog : public QDialog +{ +Q_OBJECT + +public: + kpDocumentMetaInfoDialog (const kpDocumentMetaInfo *docMetaInfo, + QWidget *parent); + ~kpDocumentMetaInfoDialog () override; + +public: + bool isNoOp () const; + + kpDocumentMetaInfo originalMetaInfo () const; + + // Returns the meta information gathered from all the UI. + // + // If there is any invalid data in the UI (e.g. duplicate text field + // keys), the returned meta information will still be valid but will not + // contain the complete contents of the UI (this description is + // deliberately vague so that you don't use the return value in such a + // situation). In this situation, if is set, a non-empty, + // translated error message will be returned through it. + // + // If all data in the UI is valid and is set, an empty + // string will be returned through it. + // + // If QDialog::exec() succeeded, all data in the UI was valid so the + // returned meta information will be complete and correct. + // + // This is a slow method as it recalculates the meta information each + // time it's called. + kpDocumentMetaInfo metaInfo (QString *errorMessage = nullptr) const; + +private: + void editCell (int r, int c); + void fieldsUpdateVerticalHeader (); + + void fieldsAddEmptyRow (int atRow); + void fieldsAppendEmptyRow (); + + bool isFieldsRowDeleteable (int row) const; + void fieldsDeleteRow (int r); + + void enableFieldsDeleteRowButtonIfShould (); + +private slots: + void setUIToOriginalMetaInfo (); + void slotFieldsCurrentCellChanged (int row, int col, int oldRow, int oldCol); + + // Allows the user to add a row without pressing any pushbuttons: + // Appends a new, blank row when text has been added to the last row. + void slotFieldsItemChanged (QTableWidgetItem *item); + + void slotFieldsAddRowButtonClicked (); + void slotFieldsDeleteRowButtonClicked (); + + void accept () override; + +private: + struct kpDocumentMetaInfoDialogPrivate * const d; +}; + + +#endif // kpDocumentMetaInfoDialog_H diff --git a/dialogs/imagelib/transforms/kpTransformPreviewDialog.cpp b/dialogs/imagelib/transforms/kpTransformPreviewDialog.cpp new file mode 100644 index 0000000..fa5e458 --- /dev/null +++ b/dialogs/imagelib/transforms/kpTransformPreviewDialog.cpp @@ -0,0 +1,467 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TRANSFORM_PREVIEW_DIALOG 0 + + +#include "dialogs/imagelib/transforms/kpTransformPreviewDialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kpLogCategories.h" +#include + +#include "layers/selections/image/kpAbstractImageSelection.h" +#include "imagelib/kpColor.h" +#include "document/kpDocument.h" +#include "pixmapfx/kpPixmapFX.h" +#include "generic/widgets/kpResizeSignallingLabel.h" +#include "environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.h" + + +kpTransformPreviewDialog::kpTransformPreviewDialog (Features features, + bool reserveTopRow, + const QString &caption, + const QString &afterActionText, + bool actOnSelection, + kpTransformDialogEnvironment *_env, + QWidget *parent) + : QDialog (parent), + m_afterActionText (afterActionText), + m_actOnSelection (actOnSelection), + m_dimensionsGroupBox (nullptr), + m_afterTransformDimensionsLabel (nullptr), + m_previewGroupBox (nullptr), + m_previewPixmapLabel (nullptr), + m_gridLayout (nullptr), + m_environ (_env) +{ + setWindowTitle (caption); + QDialogButtonBox *buttons = new QDialogButtonBox (QDialogButtonBox::Ok | + QDialogButtonBox::Cancel, this); + connect (buttons, &QDialogButtonBox::accepted, this, &kpTransformPreviewDialog::accept); + connect (buttons, &QDialogButtonBox::rejected, this, &kpTransformPreviewDialog::reject); + + QWidget *baseWidget = new QWidget (this); + m_mainWidget = baseWidget; + + auto *dialogLayout = new QVBoxLayout (this); + dialogLayout->addWidget (baseWidget); + dialogLayout->addWidget (buttons); + + + if (document ()) + { + m_oldWidth = document ()->width (actOnSelection); + m_oldHeight = document ()->height (actOnSelection); + } + else + { + m_oldWidth = m_oldHeight = 1; + } + + + if (features & Dimensions) { + createDimensionsGroupBox (); + } + + if (features & Preview) { + createPreviewGroupBox (); + } + + + m_gridLayout = new QGridLayout (baseWidget ); + m_gridLayout->setContentsMargins(0, 0, 0, 0); + m_gridNumRows = reserveTopRow ? 1 : 0; + if (m_dimensionsGroupBox || m_previewGroupBox) + { + if (m_dimensionsGroupBox && m_previewGroupBox) + { + m_gridLayout->addWidget (m_dimensionsGroupBox, m_gridNumRows, 0); + m_gridLayout->addWidget (m_previewGroupBox, m_gridNumRows, 1); + + m_gridLayout->setColumnStretch (1, 1); + } + else if (m_dimensionsGroupBox) + { + m_gridLayout->addWidget (m_dimensionsGroupBox, + m_gridNumRows, 0, 1, 2); + } + else if (m_previewGroupBox) + { + m_gridLayout->addWidget (m_previewGroupBox, + m_gridNumRows, 0, 1, 2); + } + + m_gridLayout->setRowStretch (m_gridNumRows, 1); + m_gridNumRows++; + } +} + +kpTransformPreviewDialog::~kpTransformPreviewDialog () = default; + + +// private +void kpTransformPreviewDialog::createDimensionsGroupBox () +{ + m_dimensionsGroupBox = new QGroupBox (i18n ("Dimensions"), mainWidget ()); + + auto *originalLabel = new QLabel (i18n ("Original:"), m_dimensionsGroupBox); + QString originalDimensions; + if (document ()) + { + originalDimensions = i18n ("%1 x %2", + m_oldWidth, + m_oldHeight); + + // Stop the Dimensions Group Box from resizing so often + const QString minimumLengthString (QStringLiteral("100000 x 100000")); + const int padLength = minimumLengthString.length (); + for (int i = originalDimensions.length (); i < padLength; i++) { + originalDimensions += ' '; + } + } + auto *originalDimensionsLabel = new QLabel (originalDimensions, m_dimensionsGroupBox); + + auto *afterTransformLabel = new QLabel (m_afterActionText, m_dimensionsGroupBox); + m_afterTransformDimensionsLabel = new QLabel (m_dimensionsGroupBox); + + + auto *dimensionsLayout = new QGridLayout (m_dimensionsGroupBox ); + dimensionsLayout->addWidget (originalLabel, 0, 0, Qt::AlignBottom); + dimensionsLayout->addWidget (originalDimensionsLabel, 0, 1, Qt::AlignBottom); + dimensionsLayout->addWidget (afterTransformLabel, 1, 0, Qt::AlignTop); + dimensionsLayout->addWidget (m_afterTransformDimensionsLabel, 1, 1, Qt::AlignTop); +} + +// private +void kpTransformPreviewDialog::createPreviewGroupBox () +{ + m_previewGroupBox = new QGroupBox (i18n ("Preview"), mainWidget ()); + + m_previewPixmapLabel = new kpResizeSignallingLabel (m_previewGroupBox); + m_previewPixmapLabel->setMinimumSize (150, 110); + connect (m_previewPixmapLabel, &kpResizeSignallingLabel::resized, + this, &kpTransformPreviewDialog::updatePreview); + + QPushButton *updatePushButton = new QPushButton (i18n ("&Update"), + m_previewGroupBox); + connect (updatePushButton, &QPushButton::clicked, + this, &kpTransformPreviewDialog::slotUpdateWithWaitCursor); + + + auto *previewLayout = new QVBoxLayout (m_previewGroupBox); + previewLayout->addWidget (m_previewPixmapLabel, 1/*stretch*/); + previewLayout->addWidget (updatePushButton, 0/*stretch*/, Qt::AlignHCenter); +} + + +// protected +kpDocument *kpTransformPreviewDialog::document () const +{ + return m_environ->document (); +} + +// protected +QWidget *kpTransformPreviewDialog::mainWidget () const +{ + return m_mainWidget; +} + +// protected +void kpTransformPreviewDialog::addCustomWidgetToFront (QWidget *w) +{ + m_gridLayout->addWidget (w, 0, 0, 1, 2); +} + +// protected +void kpTransformPreviewDialog::addCustomWidget (QWidget *w) +{ + m_gridLayout->addWidget (w, m_gridNumRows, 0, 1, 2); + m_gridNumRows++; +} + + +// public override [base QWidget] +void kpTransformPreviewDialog::setUpdatesEnabled (bool enable) +{ + QDialog::setUpdatesEnabled (enable); + + if (enable) { + slotUpdateWithWaitCursor (); + } +} + + +// private +void kpTransformPreviewDialog::updateDimensions () +{ + if (!m_dimensionsGroupBox) { + return; + } + + kpDocument *doc = document (); + if (!doc) { + return; + } + + if (!updatesEnabled ()) + { + #if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG + qCDebug(kpLogDialogs) << "updates not enabled - aborting"; + #endif + return; + } + + QSize newDim = newDimensions (); +#if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG + qCDebug(kpLogDialogs) << "kpTransformPreviewDialog::updateDimensions(): newDim=" << newDim; +#endif + + QString newDimString = i18n ("%1 x %2", + newDim.width (), + newDim.height ()); + m_afterTransformDimensionsLabel->setText (newDimString); +} + + +// public static +double kpTransformPreviewDialog::aspectScale (int newWidth, int newHeight, + int oldWidth, int oldHeight) +{ + double widthScale = double (newWidth) / double (oldWidth); + double heightScale = double (newHeight) / double (oldHeight); + + // Keeps aspect ratio + return qMin (widthScale, heightScale); +} + +// public static +int kpTransformPreviewDialog::scaleDimension (int dimension, double scale, int min, int max) +{ + return qMax (min, + qMin (max, + qRound (dimension * scale))); +} + + +// private +void kpTransformPreviewDialog::updateShrunkenDocumentPixmap () +{ +#if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG + qCDebug(kpLogDialogs) << "kpTransformPreviewDialog::updateShrunkenDocumentPixmap()" + << " shrunkenDocPixmap.size=" + << m_shrunkenDocumentPixmap.size () + << " previewPixmapLabelSizeWhenUpdatedPixmap=" + << m_previewPixmapLabelSizeWhenUpdatedPixmap + << " previewPixmapLabel.size=" + << m_previewPixmapLabel->size () + << endl; +#endif + + if (!m_previewGroupBox) { + return; + } + + + kpDocument *doc = document (); + Q_ASSERT (doc && !doc->image ().isNull ()); + + if (m_shrunkenDocumentPixmap.isNull () || + m_previewPixmapLabel->size () != m_previewPixmapLabelSizeWhenUpdatedPixmap) + { + #if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG + qCDebug(kpLogDialogs) << "\tupdating shrunkenDocPixmap"; + #endif + + // TODO: Why the need to keep aspect ratio here? + // Isn't scaling the skewed result maintaining aspect enough? + double keepsAspectScale = aspectScale (m_previewPixmapLabel->width (), + m_previewPixmapLabel->height (), + m_oldWidth, + m_oldHeight); + + kpImage image; + + if (m_actOnSelection) + { + kpAbstractImageSelection *sel = doc->imageSelection ()->clone (); + if (!sel->hasContent ()) { + sel->setBaseImage (doc->getSelectedBaseImage ()); + } + + image = sel->transparentImage (); + delete sel; + } + else + { + image = doc->image (); + } + + m_shrunkenDocumentPixmap = kpPixmapFX::scale ( + image, + scaleDimension (m_oldWidth, + keepsAspectScale, + 1, m_previewPixmapLabel->width ()), + scaleDimension (m_oldHeight, + keepsAspectScale, + 1, m_previewPixmapLabel->height ())); + + m_previewPixmapLabelSizeWhenUpdatedPixmap = m_previewPixmapLabel->size (); + } +} + + +// private +void kpTransformPreviewDialog::updatePreview () +{ +#if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG + qCDebug(kpLogDialogs) << "kpTransformPreviewDialog::updatePreview()"; +#endif + + if (!m_previewGroupBox) { + return; + } + + + kpDocument *doc = document (); + if (!doc) { + return; + } + + + if (!updatesEnabled ()) + { + #if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG + qCDebug(kpLogDialogs) << "updates not enabled - aborting"; + #endif + return; + } + + + updateShrunkenDocumentPixmap (); + + if (!m_shrunkenDocumentPixmap.isNull ()) + { + QSize newDim = newDimensions (); + double keepsAspectScale = aspectScale (m_previewPixmapLabel->width (), + m_previewPixmapLabel->height (), + newDim.width (), + newDim.height ()); + + int targetWidth = scaleDimension (newDim.width (), + keepsAspectScale, + 1, // min + m_previewPixmapLabel->width ()); // max + int targetHeight = scaleDimension (newDim.height (), + keepsAspectScale, + 1, // min + m_previewPixmapLabel->height ()); // max + + // TODO: Some effects work directly on QImage; so could cache the + // QImage so that transformPixmap() is faster + QImage transformedShrunkenDocumentPixmap = + transformPixmap (m_shrunkenDocumentPixmap, targetWidth, targetHeight); + + QImage previewPixmap (m_previewPixmapLabel->width (), + m_previewPixmapLabel->height (), QImage::Format_ARGB32_Premultiplied); + previewPixmap.fill(QColor(Qt::transparent).rgba()); + kpPixmapFX::setPixmapAt (&previewPixmap, + (previewPixmap.width () - transformedShrunkenDocumentPixmap.width ()) / 2, + (previewPixmap.height () - transformedShrunkenDocumentPixmap.height ()) / 2, + transformedShrunkenDocumentPixmap); + +#if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG + qCDebug(kpLogDialogs) << "kpTransformPreviewDialog::updatePreview ():" + << " shrunkenDocumentPixmap: w=" + << m_shrunkenDocumentPixmap.width () + << " h=" + << m_shrunkenDocumentPixmap.height () + << " previewPixmapLabel: w=" + << m_previewPixmapLabel->width () + << " h=" + << m_previewPixmapLabel->height () + << " transformedShrunkenDocumentPixmap: w=" + << transformedShrunkenDocumentPixmap.width () + << " h=" + << transformedShrunkenDocumentPixmap.height () + << " previewPixmap: w=" + << previewPixmap.width () + << " h=" + << previewPixmap.height () + << endl; +#endif + + m_previewPixmapLabel->setPixmap (QPixmap::fromImage(previewPixmap)); + + // immediate update esp. for expensive previews + m_previewPixmapLabel->repaint (); + +#if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG + qCDebug(kpLogDialogs) << "\tafter QLabel::setPixmap() previewPixmapLabel: w=" + << m_previewPixmapLabel->width () + << " h=" + << m_previewPixmapLabel->height () + << endl; +#endif + } +} + + +// protected slot virtual +void kpTransformPreviewDialog::slotUpdate () +{ +#if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG + qCDebug(kpLogDialogs) << "kpTransformPreviewDialog::slotUpdate()"; +#endif + updateDimensions (); + updatePreview (); +} + +// protected slot virtual +void kpTransformPreviewDialog::slotUpdateWithWaitCursor () +{ +#if DEBUG_KP_TRANSFORM_PREVIEW_DIALOG + qCDebug(kpLogDialogs) << "kpTransformPreviewDialog::slotUpdateWithWaitCursor()"; +#endif + + QApplication::setOverrideCursor (Qt::WaitCursor); + + slotUpdate (); + + QApplication::restoreOverrideCursor (); +} + + diff --git a/dialogs/imagelib/transforms/kpTransformPreviewDialog.h b/dialogs/imagelib/transforms/kpTransformPreviewDialog.h new file mode 100644 index 0000000..a52d326 --- /dev/null +++ b/dialogs/imagelib/transforms/kpTransformPreviewDialog.h @@ -0,0 +1,145 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTransformPreviewDialog_H +#define kpTransformPreviewDialog_H + + +#include +#include + + +class QLabel; +class QGridLayout; +class QGroupBox; + +class kpDocument; +class kpResizeSignallingLabel; +class kpTransformDialogEnvironment; + + +class kpTransformPreviewDialog : public QDialog +{ +Q_OBJECT + +public: + enum Features + { + Dimensions = 1, Preview = 2, + AllFeatures = Dimensions | Preview + }; + + // You must call slotUpdate() in your constructor + kpTransformPreviewDialog (Features features, + bool reserveTopRow, + // e.g. "Skew (Image|Selection)" + const QString &caption, + // (in the Dimensions Group Box) e.g. "After Skew:" + const QString &afterActionText, + bool actOnSelection, + kpTransformDialogEnvironment *_env, + QWidget *parent); + ~kpTransformPreviewDialog () override; + +private: + void createDimensionsGroupBox (); + void createPreviewGroupBox (); + +public: + virtual bool isNoOp () const = 0; + +protected: + kpDocument *document () const; + + QWidget *mainWidget () const; + + // All widgets must have mainWidget() as their parent + void addCustomWidgetToFront (QWidget *w); // see in ctor + void addCustomWidget (QWidget *w); + void addCustomWidgetToBack (QWidget *w) + { + addCustomWidget (w); + } + + virtual QSize newDimensions () const = 0; + virtual QImage transformPixmap (const QImage &pixmap, + int targetWidth, int targetHeight) const = 0; + +public: + // Use to avoid excessive, expensive preview pixmap label recalcuations, + // during init and widget relayouts. + // + // Setting to true automatically calls slotUpdateWithWaitCursor(). + // + // WARNING: This overrides a non-virtual method in QWidget. + void setUpdatesEnabled (bool enable); + +private: + void updateDimensions (); + +public: + static double aspectScale (int newWidth, int newHeight, + int oldWidth, int oldHeight); + static int scaleDimension (int dimension, double scale, int min, int max); + +private: + void updateShrunkenDocumentPixmap (); + +protected slots: + void updatePreview (); + + // Call this whenever a value (e.g. an angle) changes + // and the Dimensions & Preview need to be updated + virtual void slotUpdate (); + + virtual void slotUpdateWithWaitCursor (); + +protected: + // REFACTOR: Use d-ptr + QString m_afterActionText; + bool m_actOnSelection; + + int m_oldWidth, m_oldHeight; + + QWidget *m_mainWidget; + QGroupBox *m_dimensionsGroupBox; + QLabel *m_afterTransformDimensionsLabel; + + QGroupBox *m_previewGroupBox; + kpResizeSignallingLabel *m_previewPixmapLabel; + QSize m_previewPixmapLabelSizeWhenUpdatedPixmap; + QImage m_shrunkenDocumentPixmap; + + QGridLayout *m_gridLayout; + int m_gridNumRows; + + kpTransformDialogEnvironment *m_environ; +}; + + +#endif // kpTransformPreviewDialog_H diff --git a/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp b/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp new file mode 100644 index 0000000..5c3caac --- /dev/null +++ b/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp @@ -0,0 +1,840 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2011 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG 0 + + +#include "kpTransformResizeScaleDialog.h" + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "kpLogCategories.h" +#include + +#include "layers/selections/kpAbstractSelection.h" +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "layers/selections/text/kpTextSelection.h" +#include "tools/kpTool.h" +#include "environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.h" + +//--------------------------------------------------------------------- + +#define kpSettingResizeScaleLastKeepAspect "Resize Scale - Last Keep Aspect" +#define kpSettingResizeScaleScaleType "Resize Scale - ScaleType" + +//--------------------------------------------------------------------- + +#define SET_VALUE_WITHOUT_SIGNAL_EMISSION(knuminput_instance,value) \ +{ \ + knuminput_instance->blockSignals (true); \ + knuminput_instance->setValue (value); \ + knuminput_instance->blockSignals (false); \ +} + +#define IGNORE_KEEP_ASPECT_RATIO(cmd) \ +{ \ + m_ignoreKeepAspectRatio++; \ + cmd; \ + m_ignoreKeepAspectRatio--; \ +} + +//--------------------------------------------------------------------- + +kpTransformResizeScaleDialog::kpTransformResizeScaleDialog ( + kpTransformDialogEnvironment *_env, QWidget *parent) + : QDialog (parent), + m_environ (_env), + m_ignoreKeepAspectRatio (0), + m_lastType(kpTransformResizeScaleCommand::Resize) +{ + setWindowTitle (i18nc ("@title:window", "Resize / Scale")); + QDialogButtonBox *buttons = new QDialogButtonBox (QDialogButtonBox::Ok | + QDialogButtonBox::Cancel, this); + connect (buttons, &QDialogButtonBox::accepted, this, &kpTransformResizeScaleDialog::accept); + connect (buttons, &QDialogButtonBox::rejected, this, &kpTransformResizeScaleDialog::reject); + + QWidget *baseWidget = new QWidget (this); + + auto *dialogLayout = new QVBoxLayout (this); + dialogLayout->addWidget (baseWidget); + dialogLayout->addWidget (buttons); + + QWidget *actOnBox = createActOnBox(baseWidget); + QGroupBox *operationGroupBox = createOperationGroupBox(baseWidget); + QGroupBox *dimensionsGroupBox = createDimensionsGroupBox(baseWidget); + + auto *baseLayout = new QVBoxLayout (baseWidget); + baseLayout->setContentsMargins(0, 0, 0, 0); + baseLayout->addWidget(actOnBox); + baseLayout->addWidget(operationGroupBox); + baseLayout->addWidget(dimensionsGroupBox); + + KConfigGroup cfg(KSharedConfig::openConfig(), kpSettingsGroupGeneral); + setKeepAspectRatio(cfg.readEntry(kpSettingResizeScaleLastKeepAspect, false)); + m_lastType = static_cast + (cfg.readEntry(kpSettingResizeScaleScaleType, + static_cast(kpTransformResizeScaleCommand::Resize))); + + slotActOnChanged (); + + m_newWidthInput->setFocus (); + + //enableButtonOk (!isNoOp ()); +} + +//--------------------------------------------------------------------- +// private + +kpDocument *kpTransformResizeScaleDialog::document () const +{ + return m_environ->document (); +} + +//--------------------------------------------------------------------- +// private + +kpAbstractSelection *kpTransformResizeScaleDialog::selection () const +{ + Q_ASSERT (document ()); + return document ()->selection (); +} + +//--------------------------------------------------------------------- +// private + +kpTextSelection *kpTransformResizeScaleDialog::textSelection () const +{ + Q_ASSERT (document ()); + return document ()->textSelection (); +} + +//--------------------------------------------------------------------- +// private + +QWidget *kpTransformResizeScaleDialog::createActOnBox(QWidget *baseWidget) +{ + QWidget *actOnBox = new QWidget (baseWidget); + + + auto *actOnLabel = new QLabel (i18n ("Ac&t on:"), actOnBox); + m_actOnCombo = new QComboBox (actOnBox); + + + actOnLabel->setBuddy (m_actOnCombo); + + m_actOnCombo->insertItem (Image, i18n ("Entire Image")); + if (selection ()) + { + QString selName = i18n ("Selection"); + + if (textSelection ()) { + selName = i18n ("Text Box"); + } + + m_actOnCombo->insertItem (Selection, selName); + m_actOnCombo->setCurrentIndex (Selection); + } + else + { + actOnLabel->setEnabled (false); + m_actOnCombo->setEnabled (false); + } + + auto *lay = new QHBoxLayout (actOnBox); + lay->setContentsMargins(0, 0, 0, 0); + lay->addWidget (actOnLabel); + lay->addWidget (m_actOnCombo, 1); + + connect (m_actOnCombo, static_cast(&QComboBox::activated), + this, &kpTransformResizeScaleDialog::slotActOnChanged); + + return actOnBox; +} + +//--------------------------------------------------------------------- + +static void toolButtonSetLook (QToolButton *button, + const QString &iconName, + const QString &name) +{ + QPixmap icon; + const QString qrcPath = QStringLiteral(":/icons/") + iconName; + if (!icon.load (qrcPath)) { + qWarning() << qrcPath << "not found"; + } else { + button->setIconSize (QSize (icon.width (), icon.height ())); + button->setIcon (icon); + } + + button->setToolButtonStyle (Qt::ToolButtonTextUnderIcon); + button->setText (name); + button->setFocusPolicy (Qt::StrongFocus); + button->setCheckable (true); +} + +//--------------------------------------------------------------------- +// private + +QGroupBox *kpTransformResizeScaleDialog::createOperationGroupBox (QWidget *baseWidget) +{ + QGroupBox *operationGroupBox = new QGroupBox (i18n ("Operation"), baseWidget); + operationGroupBox->setWhatsThis( + i18n ("" + "
    " + "
  • Resize: The size of the picture will be" + " increased" + " by creating new areas to the right and/or bottom" + " (filled in with the background color) or" + " decreased by cutting" + " it at the right and/or bottom.
  • " + + "
  • Scale: The picture will be expanded" + " by duplicating pixels or squashed by dropping pixels.
  • " + + "
  • Smooth Scale: This is the same as" + " Scale except that it blends neighboring" + " pixels to produce a smoother looking picture.
  • " + "
" + "
")); + + m_resizeButton = new QToolButton (operationGroupBox); + toolButtonSetLook (m_resizeButton, + QStringLiteral ("resize"), + i18n ("&Resize")); + + m_scaleButton = new QToolButton (operationGroupBox); + toolButtonSetLook (m_scaleButton, + QStringLiteral ("scale"), + i18n ("&Scale")); + + m_smoothScaleButton = new QToolButton (operationGroupBox); + toolButtonSetLook (m_smoothScaleButton, + QStringLiteral ("smooth_scale"), + i18n ("S&mooth Scale")); + + auto *resizeScaleButtonGroup = new QButtonGroup (baseWidget); + resizeScaleButtonGroup->addButton (m_resizeButton); + resizeScaleButtonGroup->addButton (m_scaleButton); + resizeScaleButtonGroup->addButton (m_smoothScaleButton); + + + auto *operationLayout = new QGridLayout (operationGroupBox ); + operationLayout->addWidget (m_resizeButton, 0, 0, Qt::AlignCenter); + operationLayout->addWidget (m_scaleButton, 0, 1, Qt::AlignCenter); + operationLayout->addWidget (m_smoothScaleButton, 0, 2, Qt::AlignCenter); + + connect (m_resizeButton, &QToolButton::toggled, + this, &kpTransformResizeScaleDialog::slotTypeChanged); + connect (m_scaleButton, &QToolButton::toggled, + this, &kpTransformResizeScaleDialog::slotTypeChanged); + connect (m_smoothScaleButton, &QToolButton::toggled, + this, &kpTransformResizeScaleDialog::slotTypeChanged); + + return operationGroupBox; +} + +//--------------------------------------------------------------------- +// private + +QGroupBox *kpTransformResizeScaleDialog::createDimensionsGroupBox(QWidget *baseWidget) +{ + QGroupBox *dimensionsGroupBox = new QGroupBox (i18n ("Dimensions"), baseWidget); + + auto *widthLabel = new QLabel (i18n ("Width:"), dimensionsGroupBox); + widthLabel->setAlignment (widthLabel->alignment () | Qt::AlignHCenter); + auto *heightLabel = new QLabel (i18n ("Height:"), dimensionsGroupBox); + heightLabel->setAlignment (heightLabel->alignment () | Qt::AlignHCenter); + + auto *originalLabel = new QLabel (i18n ("Original:"), dimensionsGroupBox); + m_originalWidthInput = new QSpinBox; + m_originalWidthInput->setRange(1, INT_MAX); + m_originalWidthInput->setValue(document()->width(static_cast (selection()))); + auto *xLabel0 = new QLabel (i18n ("x"), dimensionsGroupBox); + m_originalHeightInput = new QSpinBox; + m_originalHeightInput->setRange(1, INT_MAX); + m_originalHeightInput->setValue(document()->height(static_cast (selection()))); + + auto *newLabel = new QLabel (i18n ("&New:"), dimensionsGroupBox); + m_newWidthInput = new QSpinBox; + m_newWidthInput->setRange(1, INT_MAX); + auto *xLabel1 = new QLabel (i18n ("x"), dimensionsGroupBox); + m_newHeightInput = new QSpinBox; + m_newHeightInput->setRange(1, INT_MAX); + + auto *percentLabel = new QLabel (i18n ("&Percent:"), dimensionsGroupBox); + m_percentWidthInput = new QDoubleSpinBox; + m_percentWidthInput->setRange(0.01, 1000000); + m_percentWidthInput->setValue(100); + m_percentWidthInput->setSingleStep(1); + m_percentWidthInput->setDecimals(2); + m_percentWidthInput->setSuffix(i18n("%")); + + auto *xLabel2 = new QLabel (i18n ("x"), dimensionsGroupBox); + + m_percentHeightInput = new QDoubleSpinBox; + m_percentHeightInput->setRange(0.01, 1000000); + m_percentHeightInput->setValue(100); + m_percentHeightInput->setSingleStep(1); + m_percentHeightInput->setDecimals(2); + m_percentHeightInput->setSuffix(i18n("%")); + + m_keepAspectRatioCheckBox = new QCheckBox (i18n ("Keep &aspect ratio"), + dimensionsGroupBox); + + + m_originalWidthInput->setEnabled (false); + m_originalHeightInput->setEnabled (false); + originalLabel->setBuddy (m_originalWidthInput); + newLabel->setBuddy (m_newWidthInput); + m_percentWidthInput->setValue (100); + m_percentHeightInput->setValue (100); + percentLabel->setBuddy (m_percentWidthInput); + + + auto *dimensionsLayout = new QGridLayout (dimensionsGroupBox); + dimensionsLayout->setColumnStretch (1/*column*/, 1); + dimensionsLayout->setColumnStretch (3/*column*/, 1); + + + dimensionsLayout->addWidget (widthLabel, 0, 1); + dimensionsLayout->addWidget (heightLabel, 0, 3); + + dimensionsLayout->addWidget (originalLabel, 1, 0); + dimensionsLayout->addWidget (m_originalWidthInput, 1, 1); + dimensionsLayout->addWidget (xLabel0, 1, 2); + dimensionsLayout->addWidget (m_originalHeightInput, 1, 3); + + dimensionsLayout->addWidget (newLabel, 2, 0); + dimensionsLayout->addWidget (m_newWidthInput, 2, 1); + dimensionsLayout->addWidget (xLabel1, 2, 2); + dimensionsLayout->addWidget (m_newHeightInput, 2, 3); + + dimensionsLayout->addWidget (percentLabel, 3, 0); + dimensionsLayout->addWidget (m_percentWidthInput, 3, 1); + dimensionsLayout->addWidget (xLabel2, 3, 2); + dimensionsLayout->addWidget (m_percentHeightInput, 3, 3); + + dimensionsLayout->addWidget (m_keepAspectRatioCheckBox, 4, 0, 1, 4); + dimensionsLayout->setRowStretch (4/*row*/, 1); + dimensionsLayout->setRowMinimumHeight (4/*row*/, dimensionsLayout->rowMinimumHeight (4) * 2); + + + + connect (m_newWidthInput, static_cast(&QSpinBox::valueChanged), + this, &kpTransformResizeScaleDialog::slotWidthChanged); + + connect (m_newHeightInput, static_cast(&QSpinBox::valueChanged), + this, &kpTransformResizeScaleDialog::slotHeightChanged); + + // COMPAT: KDoubleNumInput only fires valueChanged(double) once per + // edit. It should either fire: + // + // 1. At the end of the edit (triggered by clicking or tabbing + // away), like with KDE 3. + // + // OR + // + // 2. Once per keystroke. + // + // Bug in KDoubleNumInput. + connect (m_percentWidthInput, + static_cast(&QDoubleSpinBox::valueChanged), + this, &kpTransformResizeScaleDialog::slotPercentWidthChanged); + + connect (m_percentHeightInput, + static_cast(&QDoubleSpinBox::valueChanged), + this, &kpTransformResizeScaleDialog::slotPercentHeightChanged); + + connect (m_keepAspectRatioCheckBox, &QCheckBox::toggled, + this, &kpTransformResizeScaleDialog::setKeepAspectRatio); + + return dimensionsGroupBox; +} + +//--------------------------------------------------------------------- +// private + +void kpTransformResizeScaleDialog::widthFitHeightToAspectRatio () +{ + if (m_keepAspectRatioCheckBox->isChecked () && !m_ignoreKeepAspectRatio) + { + // width / height = oldWidth / oldHeight + // height = width * oldHeight / oldWidth + const int newHeight = qRound (double (imageWidth ()) * double (originalHeight ()) + / double (originalWidth ())); + IGNORE_KEEP_ASPECT_RATIO (m_newHeightInput->setValue (newHeight)); + } +} + +//--------------------------------------------------------------------- +// private + +void kpTransformResizeScaleDialog::heightFitWidthToAspectRatio () +{ + if (m_keepAspectRatioCheckBox->isChecked () && !m_ignoreKeepAspectRatio) + { + // width / height = oldWidth / oldHeight + // width = height * oldWidth / oldHeight + const int newWidth = qRound (double (imageHeight ()) * double (originalWidth ()) + / double (originalHeight ())); + IGNORE_KEEP_ASPECT_RATIO (m_newWidthInput->setValue (newWidth)); + } +} + +//--------------------------------------------------------------------- +// private + +bool kpTransformResizeScaleDialog::resizeEnabled () const +{ + return (!actOnSelection () || + (actOnSelection () && textSelection ())); +} + +//--------------------------------------------------------------------- +// private + +bool kpTransformResizeScaleDialog::scaleEnabled () const +{ + return (!(actOnSelection () && textSelection ())); +} + +//--------------------------------------------------------------------- +// private + +bool kpTransformResizeScaleDialog::smoothScaleEnabled () const +{ + return scaleEnabled (); +} + +//--------------------------------------------------------------------- +// public slot + +void kpTransformResizeScaleDialog::slotActOnChanged () +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 + qCDebug(kpLogDialogs) << "kpTransformResizeScaleDialog::slotActOnChanged()"; +#endif + + m_resizeButton->setEnabled (resizeEnabled ()); + m_scaleButton->setEnabled (scaleEnabled ()); + m_smoothScaleButton->setEnabled (smoothScaleEnabled ()); + + // TODO: somehow share logic with (resize|*scale)Enabled() + if (actOnSelection ()) + { + if (textSelection ()) + { + m_resizeButton->setChecked (true); + } + else + { + if (m_lastType == kpTransformResizeScaleCommand::Scale) { + m_scaleButton->setChecked (true); + } + else { + m_smoothScaleButton->setChecked (true); + } + } + } + else + { + if (m_lastType == kpTransformResizeScaleCommand::Resize) { + m_resizeButton->setChecked (true); + } + else if (m_lastType == kpTransformResizeScaleCommand::Scale) { + m_scaleButton->setChecked (true); + } + else { + m_smoothScaleButton->setChecked (true); + } + } + + + m_originalWidthInput->setValue (originalWidth ()); + m_originalHeightInput->setValue (originalHeight ()); + + + m_newWidthInput->blockSignals (true); + m_newHeightInput->blockSignals (true); + + m_newWidthInput->setMinimum (actOnSelection () ? + selection ()->minimumWidth () : + 1); + m_newHeightInput->setMinimum (actOnSelection () ? + selection ()->minimumHeight () : + 1); + + m_newWidthInput->blockSignals (false); + m_newHeightInput->blockSignals (false); + + + IGNORE_KEEP_ASPECT_RATIO (slotPercentWidthChanged (m_percentWidthInput->value ())); + IGNORE_KEEP_ASPECT_RATIO (slotPercentHeightChanged (m_percentHeightInput->value ())); + + setKeepAspectRatio (m_keepAspectRatioCheckBox->isChecked ()); +} + +//--------------------------------------------------------------------- +// public slot + +void kpTransformResizeScaleDialog::slotTypeChanged () +{ + m_lastType = type (); +} + +//--------------------------------------------------------------------- + +// public slot +void kpTransformResizeScaleDialog::slotWidthChanged (int width) +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 + qCDebug(kpLogDialogs) << "kpTransformResizeScaleDialog::slotWidthChanged(" + << width << ")" << endl; +#endif + const double newPercentWidth = double (width) * 100 / double (originalWidth ()); + + SET_VALUE_WITHOUT_SIGNAL_EMISSION (m_percentWidthInput,newPercentWidth); + + widthFitHeightToAspectRatio (); + + //enableButtonOk (!isNoOp ()); +} + +//--------------------------------------------------------------------- +// public slot + +void kpTransformResizeScaleDialog::slotHeightChanged (int height) +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 + qCDebug(kpLogDialogs) << "kpTransformResizeScaleDialog::slotHeightChanged(" + << height << ")" << endl; +#endif + const double newPercentHeight = double (height) * 100 / double (originalHeight ()); + + SET_VALUE_WITHOUT_SIGNAL_EMISSION (m_percentHeightInput,newPercentHeight); + + heightFitWidthToAspectRatio (); + + //enableButtonOk (!isNoOp ()); +} + +//--------------------------------------------------------------------- +// public slot + +void kpTransformResizeScaleDialog::slotPercentWidthChanged (double percentWidth) +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 + qCDebug(kpLogDialogs) << "kpTransformResizeScaleDialog::slotPercentWidthChanged(" + << percentWidth << ")"; +#endif + + SET_VALUE_WITHOUT_SIGNAL_EMISSION (m_newWidthInput, + qRound (percentWidth * originalWidth () / 100.0)); + + widthFitHeightToAspectRatio (); + + //enableButtonOk (!isNoOp ()); +} + +//--------------------------------------------------------------------- +// public slot + +void kpTransformResizeScaleDialog::slotPercentHeightChanged (double percentHeight) +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 + qCDebug(kpLogDialogs) << "kpTransformResizeScaleDialog::slotPercentHeightChanged(" + << percentHeight << ")"; +#endif + + SET_VALUE_WITHOUT_SIGNAL_EMISSION (m_newHeightInput, + qRound (percentHeight * originalHeight () / 100.0)); + + heightFitWidthToAspectRatio (); + + //enableButtonOk (!isNoOp ()); +} + +//--------------------------------------------------------------------- +// public slot + +void kpTransformResizeScaleDialog::setKeepAspectRatio (bool on) +{ +#if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 + qCDebug(kpLogDialogs) << "kpTransformResizeScaleDialog::setKeepAspectRatio(" + << on << ")"; +#endif + + if (on != m_keepAspectRatioCheckBox->isChecked ()) { + m_keepAspectRatioCheckBox->setChecked (on); + } + + if (on) { + widthFitHeightToAspectRatio (); + } +} + +//--------------------------------------------------------------------- + +#undef IGNORE_KEEP_ASPECT_RATIO +#undef SET_VALUE_WITHOUT_SIGNAL_EMISSION + + +//--------------------------------------------------------------------- +// private + +int kpTransformResizeScaleDialog::originalWidth () const +{ + return document ()->width (actOnSelection ()); +} + +//--------------------------------------------------------------------- +// private + +int kpTransformResizeScaleDialog::originalHeight () const +{ + return document ()->height (actOnSelection ()); +} + +//--------------------------------------------------------------------- +// public + +int kpTransformResizeScaleDialog::imageWidth () const +{ + return m_newWidthInput->value (); +} + +//--------------------------------------------------------------------- +// public + +int kpTransformResizeScaleDialog::imageHeight () const +{ + return m_newHeightInput->value (); +} + +//--------------------------------------------------------------------- +// public + +bool kpTransformResizeScaleDialog::actOnSelection () const +{ + return (m_actOnCombo->currentIndex () == Selection); +} + +//--------------------------------------------------------------------- +// public + +kpTransformResizeScaleCommand::Type kpTransformResizeScaleDialog::type () const +{ + if (m_resizeButton->isChecked ()) { + return kpTransformResizeScaleCommand::Resize; + } + + if (m_scaleButton->isChecked ()) { + return kpTransformResizeScaleCommand::Scale; + } + + return kpTransformResizeScaleCommand::SmoothScale; +} + +//--------------------------------------------------------------------- +// public + +bool kpTransformResizeScaleDialog::isNoOp () const +{ + return (imageWidth () == originalWidth () && + imageHeight () == originalHeight ()); +} + +//--------------------------------------------------------------------- +// private slot virtual [base QDialog] + +void kpTransformResizeScaleDialog::accept () +{ + enum { eText, eSelection, eImage } actionTarget = eText; + + if (actOnSelection ()) + { + if (textSelection ()) + { + actionTarget = eText; + } + else + { + actionTarget = eSelection; + } + } + else + { + actionTarget = eImage; + } + + + KLocalizedString message; + QString caption, continueButtonText; + + // Note: If eText, can't Scale nor SmoothScale. + // If eSelection, can't Resize. + + switch (type ()) + { + default: + case kpTransformResizeScaleCommand::Resize: + if (actionTarget == eText) + { + message = + ki18n ("

Resizing the text box to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.

" + + "

Are you sure you want to resize the text box?

"); + + caption = i18nc ("@title:window", "Resize Text Box?"); + continueButtonText = i18n ("R&esize Text Box"); + } + else if (actionTarget == eImage) + { + message = + ki18n ("

Resizing the image to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.

" + + "

Are you sure you want to resize the image?

"); + + caption = i18nc ("@title:window", "Resize Image?"); + continueButtonText = i18n ("R&esize Image"); + } + + break; + + case kpTransformResizeScaleCommand::Scale: + if (actionTarget == eImage) + { + message = + ki18n ("

Scaling the image to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.

" + + "

Are you sure you want to scale the image?

"); + + caption = i18nc ("@title:window", "Scale Image?"); + continueButtonText = i18n ("Scal&e Image"); + } + else if (actionTarget == eSelection) + { + message = + ki18n ("

Scaling the selection to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.

" + + "

Are you sure you want to scale the selection?

"); + + caption = i18nc ("@title:window", "Scale Selection?"); + continueButtonText = i18n ("Scal&e Selection"); + } + + break; + + case kpTransformResizeScaleCommand::SmoothScale: + if (actionTarget == eImage) + { + message = + ki18n ("

Smooth Scaling the image to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.

" + + "

Are you sure you want to smooth scale the image?

"); + + caption = i18nc ("@title:window", "Smooth Scale Image?"); + continueButtonText = i18n ("Smooth Scal&e Image"); + } + else if (actionTarget == eSelection) + { + message = + ki18n ("

Smooth Scaling the selection to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.

" + + "

Are you sure you want to smooth scale the selection?

"); + + caption = i18nc ("@title:window", "Smooth Scale Selection?"); + continueButtonText = i18n ("Smooth Scal&e Selection"); + } + + break; + } + + + if (kpTool::warnIfBigImageSize (originalWidth (), + originalHeight (), + imageWidth (), imageHeight (), + message.subs (imageWidth ()).subs (imageHeight ()).toString (), + caption, + continueButtonText, + this)) + { + QDialog::accept (); + } + + // store settings + KConfigGroup cfg(KSharedConfig::openConfig(), kpSettingsGroupGeneral); + + cfg.writeEntry(kpSettingResizeScaleLastKeepAspect, m_keepAspectRatioCheckBox->isChecked()); + cfg.writeEntry(kpSettingResizeScaleScaleType, static_cast(m_lastType)); + cfg.sync(); +} + +//--------------------------------------------------------------------- + diff --git a/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.h b/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.h new file mode 100644 index 0000000..20ce224 --- /dev/null +++ b/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.h @@ -0,0 +1,122 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2011 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTransformResizeScaleDialog_H +#define kpTransformResizeScaleDialog_H + +#include + +#include "imagelib/kpColor.h" +#include "commands/imagelib/transforms/kpTransformResizeScaleCommand.h" + + +class QCheckBox; +class QComboBox; +class QGroupBox; +class QToolButton; +class QSpinBox; +class QDoubleSpinBox; + +class kpAbstractSelection; +class kpDocument; +class kpTextSelection; +class kpTransformDialogEnvironment; + + +class kpTransformResizeScaleDialog : public QDialog +{ +Q_OBJECT + + public: + kpTransformResizeScaleDialog(kpTransformDialogEnvironment *_env, QWidget *parent); + + enum ActOn + { + Image, Selection + }; + + int imageWidth () const; + int imageHeight () const; + bool actOnSelection () const; + kpTransformResizeScaleCommand::Type type () const; + + bool isNoOp () const; + + public slots: + void slotActOnChanged (); + void slotTypeChanged (); + + void slotWidthChanged (int width); + void slotHeightChanged (int height); + + void slotPercentWidthChanged (double percentWidth); + void slotPercentHeightChanged (double percentHeight); + + private: + kpDocument *document () const; + kpAbstractSelection *selection () const; + kpTextSelection *textSelection () const; + + QWidget *createActOnBox(QWidget *baseWidget); + QGroupBox *createOperationGroupBox(QWidget *baseWidget); + QGroupBox *createDimensionsGroupBox(QWidget *baseWidget); + + void widthFitHeightToAspectRatio (); + void heightFitWidthToAspectRatio (); + + bool resizeEnabled () const; + bool scaleEnabled () const; + bool smoothScaleEnabled () const; + int originalWidth () const; + int originalHeight () const; + + private slots: + void accept() override; + void setKeepAspectRatio(bool on); + + private: + kpTransformDialogEnvironment *m_environ; + + QComboBox *m_actOnCombo; + + QToolButton *m_resizeButton, + *m_scaleButton, + *m_smoothScaleButton; + + QSpinBox *m_originalWidthInput, *m_originalHeightInput, + *m_newWidthInput, *m_newHeightInput; + QDoubleSpinBox *m_percentWidthInput, *m_percentHeightInput; + QCheckBox *m_keepAspectRatioCheckBox; + + int m_ignoreKeepAspectRatio; + + kpTransformResizeScaleCommand::Type m_lastType; +}; + + +#endif // kpTransformResizeScaleDialog_H diff --git a/dialogs/imagelib/transforms/kpTransformRotateDialog.cpp b/dialogs/imagelib/transforms/kpTransformRotateDialog.cpp new file mode 100644 index 0000000..012e3ae --- /dev/null +++ b/dialogs/imagelib/transforms/kpTransformRotateDialog.cpp @@ -0,0 +1,336 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_ROTATE 0 + + +#include "kpTransformRotateDialog.h" + +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "pixmapfx/kpPixmapFX.h" +#include "tools/kpTool.h" +#include "environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.h" +#include "views/manager/kpViewManager.h" + +#include "kpLogCategories.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// private static +int kpTransformRotateDialog::s_lastWidth = -1, + kpTransformRotateDialog::s_lastHeight = -1; + +// private static +bool kpTransformRotateDialog::s_lastIsClockwise = true; +int kpTransformRotateDialog::s_lastAngleCustom = 0; + + +kpTransformRotateDialog::kpTransformRotateDialog (bool actOnSelection, + kpTransformDialogEnvironment *_env, QWidget *parent) + : kpTransformPreviewDialog (kpTransformPreviewDialog::AllFeatures, + false/*don't reserve top row*/, + actOnSelection ? i18nc ("@title:window", "Rotate Selection") : i18nc ("@title:window", "Rotate Image"), + i18n ("After rotate:"), + actOnSelection, + _env, parent) +{ + s_lastAngleCustom = 0; + + + createDirectionGroupBox (); + createAngleGroupBox (); + + + if (s_lastWidth > 0 && s_lastHeight > 0) { + resize (s_lastWidth, s_lastHeight); + } + + + slotAngleCustomRadioButtonToggled (m_angleCustomRadioButton->isChecked ()); + slotUpdate (); +} + +kpTransformRotateDialog::~kpTransformRotateDialog () +{ + s_lastWidth = width (); + s_lastHeight = height (); +} + + +// private +void kpTransformRotateDialog::createDirectionGroupBox () +{ + auto *directionGroupBox = new QGroupBox (i18n ("Direction"), mainWidget ()); + addCustomWidget (directionGroupBox); + + QPixmap antiClockwisePixmap; + const QIcon rotateLeftIcon = QIcon::fromTheme(QStringLiteral("object-rotate-left")); + // Don't fall back to a generic "object-rotate" or "object" + if (rotateLeftIcon.isNull() || rotateLeftIcon.name() != QLatin1String("object-rotate-left")) { + antiClockwisePixmap = QPixmap(QStringLiteral(":/icons/image_rotate_anticlockwise")); + } else { + antiClockwisePixmap = rotateLeftIcon.pixmap(48); + } + + auto *antiClockwisePixmapLabel = new QLabel (directionGroupBox); + antiClockwisePixmapLabel->setPixmap (antiClockwisePixmap); + + QPixmap clockwisePixmap; + const QIcon rotateRightIcon = QIcon::fromTheme(QStringLiteral("object-rotate-right")); + if (rotateRightIcon.isNull() || rotateRightIcon.name() != QLatin1String("object-rotate-right")) { + clockwisePixmap = QPixmap(QStringLiteral(":/icons/image_rotate_clockwise")); + } else { + clockwisePixmap = rotateRightIcon.pixmap(48); + } + + auto *clockwisePixmapLabel = new QLabel (directionGroupBox); + clockwisePixmapLabel->setPixmap (clockwisePixmap); + + m_antiClockwiseRadioButton = new QRadioButton (i18n ("Cou&nterclockwise"), directionGroupBox); + m_clockwiseRadioButton = new QRadioButton (i18n ("C&lockwise"), directionGroupBox); + + + m_antiClockwiseRadioButton->setChecked (!s_lastIsClockwise); + m_clockwiseRadioButton->setChecked (s_lastIsClockwise); + + + auto *directionLayout = new QGridLayout (directionGroupBox ); + directionLayout->addWidget (antiClockwisePixmapLabel, 0, 0, Qt::AlignCenter); + directionLayout->addWidget (clockwisePixmapLabel, 0, 1, Qt::AlignCenter); + directionLayout->addWidget (m_antiClockwiseRadioButton, 1, 0, Qt::AlignCenter); + directionLayout->addWidget (m_clockwiseRadioButton, 1, 1, Qt::AlignCenter); + + + connect (m_antiClockwiseRadioButton, &QRadioButton::toggled, + this, &kpTransformRotateDialog::slotUpdate); + + connect (m_clockwiseRadioButton, &QRadioButton::toggled, + this, &kpTransformRotateDialog::slotUpdate); +} + +// private +void kpTransformRotateDialog::createAngleGroupBox () +{ + auto *angleGroupBox = new QGroupBox (i18n ("Angle"), mainWidget ()); + addCustomWidget (angleGroupBox); + + + m_angle90RadioButton = new QRadioButton (i18n ("90 °rees"), angleGroupBox); + m_angle180RadioButton = new QRadioButton (i18n ("180 d&egrees"), angleGroupBox); + m_angle270RadioButton = new QRadioButton (i18n ("270 de&grees"), angleGroupBox); + + m_angleCustomRadioButton = new QRadioButton (i18n ("C&ustom:"), angleGroupBox); + m_angleCustomInput = new QSpinBox; + m_angleCustomInput->setMinimum(-359); + m_angleCustomInput->setMaximum(+359); + m_angleCustomInput->setValue(s_lastAngleCustom); + auto *degreesLabel = new QLabel (i18n ("degrees"), angleGroupBox); + + + m_angleCustomRadioButton->setChecked (true); + + + auto *angleLayout = new QGridLayout (angleGroupBox ); + + angleLayout->addWidget (m_angle90RadioButton, 0, 0, 1, 3); + angleLayout->addWidget (m_angle180RadioButton, 1, 0, 1, 3); + angleLayout->addWidget (m_angle270RadioButton, 2, 0, 1, 3); + + angleLayout->addWidget (m_angleCustomRadioButton, 3, 0); + angleLayout->addWidget (m_angleCustomInput, 3, 1); + angleLayout->addWidget (degreesLabel, 3, 2); + + angleLayout->setColumnStretch (1, 2); // Stretch Custom Angle Input + + + + connect (m_angle90RadioButton, &QRadioButton::toggled, + this, &kpTransformRotateDialog::slotUpdate); + connect (m_angle180RadioButton, &QRadioButton::toggled, + this, &kpTransformRotateDialog::slotUpdate); + connect (m_angle270RadioButton, &QRadioButton::toggled, + this, &kpTransformRotateDialog::slotUpdate); + + connect (m_angleCustomRadioButton, &QRadioButton::toggled, + this, &kpTransformRotateDialog::slotAngleCustomRadioButtonToggled); + connect (m_angleCustomRadioButton, &QRadioButton::toggled, + this, &kpTransformRotateDialog::slotUpdate); + + connect (m_angleCustomInput, + static_cast(&QSpinBox::valueChanged), + this, &kpTransformRotateDialog::slotUpdate); +} + + +// public virtual [base kpTransformPreviewDialog] +bool kpTransformRotateDialog::isNoOp () const +{ + return (angle () == 0); +} + +// public +int kpTransformRotateDialog::angle () const +{ + int retAngle; + + + if (m_angle90RadioButton->isChecked ()) { + retAngle = 90; + } + else if (m_angle180RadioButton->isChecked ()) { + retAngle = 180; + } + else if (m_angle270RadioButton->isChecked ()) { + retAngle = 270; + } + else { // if (m_angleCustomRadioButton->isChecked ()) + retAngle = m_angleCustomInput->value (); + } + + + if (m_antiClockwiseRadioButton->isChecked ()) { + retAngle *= -1; + } + + + if (retAngle < 0) { + retAngle += ((0 - retAngle) / 360 + 1) * 360; + } + + if (retAngle >= 360) { + retAngle -= ((retAngle - 360) / 360 + 1) * 360; + } + + + return retAngle; +} + + +// private virtual [base kpTransformPreviewDialog] +QSize kpTransformRotateDialog::newDimensions () const +{ + QTransform matrix = kpPixmapFX::rotateMatrix (m_oldWidth, m_oldHeight, angle ()); + QRect rect = matrix.mapRect (QRect (0, 0, m_oldWidth, m_oldHeight)); + return rect.size (); +} + +// private virtual [base kpTransformPreviewDialog] +QImage kpTransformRotateDialog::transformPixmap (const QImage &image, + int targetWidth, int targetHeight) const +{ + return kpPixmapFX::rotate (image, angle (), + m_environ->backgroundColor (m_actOnSelection), + targetWidth, targetHeight); +} + + +// private slot +void kpTransformRotateDialog::slotAngleCustomRadioButtonToggled (bool isChecked) +{ + m_angleCustomInput->setEnabled (isChecked); + + if (isChecked) { + m_angleCustomInput->setFocus(); + } +} + +// private slot virtual [base kpTransformPreviewDialog] +void kpTransformRotateDialog::slotUpdate () +{ + s_lastIsClockwise = m_clockwiseRadioButton->isChecked (); + s_lastAngleCustom = m_angleCustomInput->value (); + + kpTransformPreviewDialog::slotUpdate (); +} + + +// private slot virtual [base QDialog] +void kpTransformRotateDialog::accept () +{ + KLocalizedString message; + QString caption, continueButtonText; + + if (document ()->selection ()) + { + if (!document ()->textSelection ()) + { + message = + ki18n ("

Rotating the selection to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.

" + + "

Are you sure you want to rotate the selection?

"); + + caption = i18nc ("@title:window", "Rotate Selection?"); + continueButtonText = i18n ("Rotat&e Selection"); + } + } + else + { + message = + ki18n ("

Rotating the image to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.

" + + "

Are you sure you want to rotate the image?

"); + + caption = i18nc ("@title:window", "Rotate Image?"); + continueButtonText = i18n ("Rotat&e Image"); + } + + + const int newWidth = newDimensions ().width (); + const int newHeight = newDimensions ().height (); + + if (kpTool::warnIfBigImageSize (m_oldWidth, + m_oldHeight, + newWidth, newHeight, + message.subs (newWidth).subs (newHeight).toString (), + caption, + continueButtonText, + this)) + { + QDialog::accept (); + } +} + + diff --git a/dialogs/imagelib/transforms/kpTransformRotateDialog.h b/dialogs/imagelib/transforms/kpTransformRotateDialog.h new file mode 100644 index 0000000..8e2cf3d --- /dev/null +++ b/dialogs/imagelib/transforms/kpTransformRotateDialog.h @@ -0,0 +1,92 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTransformRotateDialog_H +#define kpTransformRotateDialog_H + + +#include +#include + +#include "imagelib/kpColor.h" +#include "dialogs/imagelib/transforms/kpTransformPreviewDialog.h" + + +class QButtonGroup; +class QRadioButton; +class QSpinBox; + + +class kpTransformRotateDialog : public kpTransformPreviewDialog +{ +Q_OBJECT + +public: + kpTransformRotateDialog (bool actOnSelection, + kpTransformDialogEnvironment *_env, + QWidget *parent); + ~kpTransformRotateDialog () override; + +private: + static int s_lastWidth, s_lastHeight; + static bool s_lastIsClockwise; + static int s_lastAngleCustom; + + void createDirectionGroupBox (); + void createAngleGroupBox (); + +public: + bool isNoOp () const override; + int angle () const; // 0 <= angle < 360 (clockwise); + +private: + QSize newDimensions () const override; + QImage transformPixmap (const QImage &pixmap, + int targetWidth, int targetHeight) const override; + +private slots: + void slotAngleCustomRadioButtonToggled (bool isChecked); + void slotUpdate () override; + +private slots: + void accept () override; + +private: + QRadioButton *m_antiClockwiseRadioButton, + *m_clockwiseRadioButton; + + QButtonGroup *m_angleButtonGroup; + QRadioButton *m_angle90RadioButton, + *m_angle180RadioButton, + *m_angle270RadioButton, + *m_angleCustomRadioButton; + QSpinBox *m_angleCustomInput; +}; + + +#endif // kpTransformRotateDialog_H diff --git a/dialogs/imagelib/transforms/kpTransformSkewDialog.cpp b/dialogs/imagelib/transforms/kpTransformSkewDialog.cpp new file mode 100644 index 0000000..c9e77e7 --- /dev/null +++ b/dialogs/imagelib/transforms/kpTransformSkewDialog.cpp @@ -0,0 +1,297 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_SKEW 0 +#define DEBUG_KP_TOOL_SKEW_DIALOG 0 + + +#include "dialogs/imagelib/transforms/kpTransformSkewDialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kpLogCategories.h" +#include + +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "pixmapfx/kpPixmapFX.h" +#include "tools/kpTool.h" +#include "environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.h" + + +// private static +int kpTransformSkewDialog::s_lastWidth = -1, + kpTransformSkewDialog::s_lastHeight = -1; + +// private static +int kpTransformSkewDialog::s_lastHorizontalAngle = 0, + kpTransformSkewDialog::s_lastVerticalAngle = 0; + + +kpTransformSkewDialog::kpTransformSkewDialog (bool actOnSelection, + kpTransformDialogEnvironment *_env, QWidget *parent) + : kpTransformPreviewDialog (kpTransformPreviewDialog::AllFeatures, + false/*don't reserve top row*/, + actOnSelection ? i18nc ("@title:window", "Skew Selection") : i18nc ("@title:window", "Skew Image"), + i18n ("After skew:"), + actOnSelection, + _env, parent) +{ + // Too confusing - disable for now + s_lastHorizontalAngle = s_lastVerticalAngle = 0; + + + createAngleGroupBox (); + + + if (s_lastWidth > 0 && s_lastHeight > 0) { + resize (s_lastWidth, s_lastHeight); + } + + + slotUpdate (); + + + m_horizontalSkewInput->setFocus (); +} + +kpTransformSkewDialog::~kpTransformSkewDialog () +{ + s_lastWidth = width (); + s_lastHeight = height (); +} + + +// private +void kpTransformSkewDialog::createAngleGroupBox () +{ + auto *angleGroupBox = new QGroupBox (i18n ("Angle"), mainWidget ()); + addCustomWidget (angleGroupBox); + + + auto *horizontalSkewPixmapLabel = new QLabel (angleGroupBox); + horizontalSkewPixmapLabel->setPixmap (QStringLiteral(":/icons/image_skew_horizontal")); + + auto *horizontalSkewLabel = new QLabel (i18n ("&Horizontal:"), angleGroupBox); + m_horizontalSkewInput = new QSpinBox; + m_horizontalSkewInput->setValue(s_lastHorizontalAngle); + m_horizontalSkewInput->setMinimum(-89); + m_horizontalSkewInput->setMaximum(+89); + + auto *horizontalSkewDegreesLabel = new QLabel (i18n ("degrees"), angleGroupBox); + + + auto *verticalSkewPixmapLabel = new QLabel (angleGroupBox); + verticalSkewPixmapLabel->setPixmap (QStringLiteral(":/icons/image_skew_vertical")); + + auto *verticalSkewLabel = new QLabel (i18n ("&Vertical:"), angleGroupBox); + m_verticalSkewInput = new QSpinBox; + m_verticalSkewInput->setValue(s_lastVerticalAngle); + m_verticalSkewInput->setMinimum(-89); + m_verticalSkewInput->setMaximum(+89); + + auto *verticalSkewDegreesLabel = new QLabel (i18n ("degrees"), angleGroupBox); + + + horizontalSkewLabel->setBuddy (m_horizontalSkewInput); + verticalSkewLabel->setBuddy (m_verticalSkewInput); + + + auto *angleLayout = new QGridLayout (angleGroupBox); + + angleLayout->addWidget (horizontalSkewPixmapLabel, 0, 0); + angleLayout->addWidget (horizontalSkewLabel, 0, 1); + angleLayout->addWidget (m_horizontalSkewInput, 0, 2, Qt::AlignVCenter); + angleLayout->addWidget (horizontalSkewDegreesLabel, 0, 3); + + angleLayout->addWidget (verticalSkewPixmapLabel, 1, 0); + angleLayout->addWidget (verticalSkewLabel, 1, 1); + angleLayout->addWidget (m_verticalSkewInput, 1, 2, Qt::AlignVCenter); + angleLayout->addWidget (verticalSkewDegreesLabel, 1, 3); + + + connect (m_horizontalSkewInput, + static_cast(&QSpinBox::valueChanged), + this, &kpTransformSkewDialog::slotUpdate); + + connect (m_verticalSkewInput, + static_cast(&QSpinBox::valueChanged), + this, &kpTransformSkewDialog::slotUpdate); +} + + +// private virtual [base kpTransformPreviewDialog] +QSize kpTransformSkewDialog::newDimensions () const +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + auto skewMatrix = kpPixmapFX::skewMatrix (doc->image (), + horizontalAngleForPixmapFX (), + verticalAngleForPixmapFX ()); + auto skewRect = skewMatrix.mapRect (doc->rect (m_actOnSelection)); + + return {skewRect.width (), skewRect.height ()}; +} + +// private virtual [base kpTransformPreviewDialog] +QImage kpTransformSkewDialog::transformPixmap (const QImage &image, + int targetWidth, int targetHeight) const +{ + return kpPixmapFX::skew (image, + horizontalAngleForPixmapFX (), + verticalAngleForPixmapFX (), + m_environ->backgroundColor (m_actOnSelection), + targetWidth, + targetHeight); +} + + +// private +void kpTransformSkewDialog::updateLastAngles () +{ + s_lastHorizontalAngle = horizontalAngle (); + s_lastVerticalAngle = verticalAngle (); +} + +// private slot virtual [base kpTransformPreviewDialog] +void kpTransformSkewDialog::slotUpdate () +{ + updateLastAngles (); + kpTransformPreviewDialog::slotUpdate (); +} + + +// public +int kpTransformSkewDialog::horizontalAngle () const +{ + return m_horizontalSkewInput->value (); +} + +// public +int kpTransformSkewDialog::verticalAngle () const +{ + return m_verticalSkewInput->value (); +} + + +// public static +int kpTransformSkewDialog::horizontalAngleForPixmapFX (int hangle) +{ + return -hangle; +} + +// public static +int kpTransformSkewDialog::verticalAngleForPixmapFX (int vangle) +{ + return -vangle; +} + + +// public +int kpTransformSkewDialog::horizontalAngleForPixmapFX () const +{ + return kpTransformSkewDialog::horizontalAngleForPixmapFX (horizontalAngle ()); +} + +// public +int kpTransformSkewDialog::verticalAngleForPixmapFX () const +{ + return kpTransformSkewDialog::verticalAngleForPixmapFX (verticalAngle ()); +} + + +// public virtual [base kpTransformPreviewDialog] +bool kpTransformSkewDialog::isNoOp () const +{ + return (horizontalAngle () == 0) && (verticalAngle () == 0); +} + + +// private slot virtual [base QDialog] +void kpTransformSkewDialog::accept () +{ + KLocalizedString message; + QString caption, continueButtonText; + + if (document ()->selection ()) + { + if (!document ()->textSelection ()) + { + message = + ki18n ("

Skewing the selection to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.

" + + "

Are you sure you want to skew the selection?

"); + + caption = i18nc ("@title:window", "Skew Selection?"); + continueButtonText = i18n ("Sk&ew Selection"); + } + } + else + { + message = + ki18n ("

Skewing the image to %1x%2" + " may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.

" + + "

Are you sure you want to skew the image?

"); + + caption = i18nc ("@title:window", "Skew Image?"); + continueButtonText = i18n ("Sk&ew Image"); + } + + + const int newWidth = newDimensions ().width (); + const int newHeight = newDimensions ().height (); + + if (kpTool::warnIfBigImageSize (m_oldWidth, + m_oldHeight, + newWidth, newHeight, + message.subs (newWidth).subs (newHeight).toString (), + caption, + continueButtonText, + this)) + { + QDialog::accept (); + } +} + + diff --git a/dialogs/imagelib/transforms/kpTransformSkewDialog.h b/dialogs/imagelib/transforms/kpTransformSkewDialog.h new file mode 100644 index 0000000..042c0a9 --- /dev/null +++ b/dialogs/imagelib/transforms/kpTransformSkewDialog.h @@ -0,0 +1,83 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTransformSkewDialog_H +#define kpTransformSkewDialog_H + +#include "kpTransformPreviewDialog.h" +#include "imagelib/kpColor.h" + +class QSpinBox; + + +class kpTransformSkewDialog : public kpTransformPreviewDialog +{ +Q_OBJECT + +public: + kpTransformSkewDialog (bool actOnSelection, + kpTransformDialogEnvironment *_env, QWidget *parent); + ~kpTransformSkewDialog () override; + +private: + static int s_lastWidth, s_lastHeight; + static int s_lastHorizontalAngle, s_lastVerticalAngle; + + void createAngleGroupBox (); + + QSize newDimensions () const override; + QImage transformPixmap (const QImage &image, + int targetWidth, int targetHeight) const override; + + void updateLastAngles (); + +private slots: + void slotUpdate () override; + +public: + // These are the angles the users sees in the dialog and... + int horizontalAngle () const; + int verticalAngle () const; + + // ...these functions translate them for use in kpPixmapFX::skew(). + static int horizontalAngleForPixmapFX (int hangle); + static int verticalAngleForPixmapFX (int vangle); + + int horizontalAngleForPixmapFX () const; + int verticalAngleForPixmapFX () const; + + bool isNoOp () const override; + +private slots: + void accept () override; + +private: + QSpinBox *m_horizontalSkewInput, *m_verticalSkewInput; +}; + + +#endif // kpTransformSkewDialog_H diff --git a/dialogs/kpColorSimilarityDialog.cpp b/dialogs/kpColorSimilarityDialog.cpp new file mode 100644 index 0000000..7a044b0 --- /dev/null +++ b/dialogs/kpColorSimilarityDialog.cpp @@ -0,0 +1,141 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "kpColorSimilarityDialog.h" + +#include "widgets/colorSimilarity/kpColorSimilarityFrame.h" + +#include +#include "../widgets/imagelib/effects/kpNumInput.h" + +#include +#include +#include +#include +#include +#include +#include + +kpColorSimilarityDialog::kpColorSimilarityDialog (QWidget *parent) + : QDialog (parent) +{ + setWindowTitle (i18nc ("@title:window", "Color Similarity")); + auto *buttons = new QDialogButtonBox (QDialogButtonBox::Ok | + QDialogButtonBox::Cancel, this); + connect (buttons, &QDialogButtonBox::accepted, this, &kpColorSimilarityDialog::accept); + connect (buttons, &QDialogButtonBox::rejected, this, &kpColorSimilarityDialog::reject); + + auto *baseWidget = new QWidget (this); + + auto *dialogLayout = new QVBoxLayout (this); + dialogLayout->addWidget (baseWidget); + dialogLayout->addWidget (buttons); + + auto *cubeGroupBox = new QGroupBox (i18n ("Preview"), baseWidget); + + m_colorSimilarityFrame = new kpColorSimilarityFrame(cubeGroupBox); + m_colorSimilarityFrame->setMinimumSize (240, 180); + + auto *updatePushButton = new QPushButton (i18n ("&Update"), cubeGroupBox); + + + auto *cubeLayout = new QVBoxLayout (cubeGroupBox); + cubeLayout->addWidget (m_colorSimilarityFrame, 1/*stretch*/); + cubeLayout->addWidget (updatePushButton, 0/*stretch*/, Qt::AlignHCenter); + + + connect (updatePushButton, &QPushButton::clicked, + this, &kpColorSimilarityDialog::slotColorSimilarityValueChanged); + + + auto *inputGroupBox = new QGroupBox (i18n ("&RGB Color Cube Distance"), + baseWidget); + + m_colorSimilarityInput = new kpIntNumInput (inputGroupBox); + m_colorSimilarityInput->setRange (0, int (kpColorSimilarityHolder::MaxColorSimilarity * 100 + 0.1/*don't floor below target int*/), + 5/*step*/); + m_colorSimilarityInput->setSuffix (i18n ("%")); + m_colorSimilarityInput->setSpecialValueText (i18n ("Exact Match")); + + // TODO: We have a good handbook section on this, which we should + // somehow link to. + m_whatIsLabel = new QLabel ( + i18n ("" + "What is Color Similarity?"), + inputGroupBox); + m_whatIsLabel->setAlignment (Qt::AlignHCenter); + connect (m_whatIsLabel, &QLabel::linkActivated, + this, &kpColorSimilarityDialog::slotWhatIsLabelClicked); + + + auto *inputLayout = new QVBoxLayout (inputGroupBox); + + inputLayout->addWidget (m_colorSimilarityInput); + inputLayout->addWidget (m_whatIsLabel); + + + // COMPAT: This is not firing properly when the user is typing in a + // new value. + connect (m_colorSimilarityInput, &kpIntNumInput::valueChanged, + this, &kpColorSimilarityDialog::slotColorSimilarityValueChanged); + + + auto *baseLayout = new QVBoxLayout (baseWidget); + baseLayout->setContentsMargins(0, 0, 0, 0); + baseLayout->addWidget (cubeGroupBox, 1/*stretch*/); + baseLayout->addWidget (inputGroupBox); +} + +kpColorSimilarityDialog::~kpColorSimilarityDialog () = default; + + +// public +double kpColorSimilarityDialog::colorSimilarity () const +{ + return m_colorSimilarityFrame->colorSimilarity (); +} + +// public +void kpColorSimilarityDialog::setColorSimilarity (double similarity) +{ + m_colorSimilarityInput->setValue (qRound (similarity * 100)); +} + + +// private slot +void kpColorSimilarityDialog::slotColorSimilarityValueChanged () +{ + m_colorSimilarityFrame->setColorSimilarity (double (m_colorSimilarityInput->value ()) / 100); +} + + +// private slot +void kpColorSimilarityDialog::slotWhatIsLabelClicked () +{ + QWhatsThis::showText(QCursor::pos(), m_colorSimilarityFrame->whatsThis(), this); +} diff --git a/dialogs/kpColorSimilarityDialog.h b/dialogs/kpColorSimilarityDialog.h new file mode 100644 index 0000000..0d224da --- /dev/null +++ b/dialogs/kpColorSimilarityDialog.h @@ -0,0 +1,68 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_COLOR_SIMILARITY_DIALOG_H +#define KP_COLOR_SIMILARITY_DIALOG_H + + +#include + + +class QLabel; + +class kpIntNumInput; + +class kpColorSimilarityFrame; + + +// LOTODO: Why doesn't this dialog automatically enforce a minimum size +// based on layout magic, like Image -> Resize / Scale? +class kpColorSimilarityDialog : public QDialog +{ +Q_OBJECT + +public: + kpColorSimilarityDialog (QWidget *parent); + ~kpColorSimilarityDialog () override; + + double colorSimilarity () const; + void setColorSimilarity (double similarity); + +private slots: + void slotColorSimilarityValueChanged (); + + void slotWhatIsLabelClicked (); + +private: + kpColorSimilarityFrame *m_colorSimilarityFrame; + kpIntNumInput *m_colorSimilarityInput; + QLabel *m_whatIsLabel; +}; + + +#endif // KP_COLOR_SIMILARITY_DIALOG_H diff --git a/dialogs/kpDocumentSaveOptionsPreviewDialog.cpp b/dialogs/kpDocumentSaveOptionsPreviewDialog.cpp new file mode 100644 index 0000000..7af2cf3 --- /dev/null +++ b/dialogs/kpDocumentSaveOptionsPreviewDialog.cpp @@ -0,0 +1,232 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET 0 + + +#include "kpDocumentSaveOptionsPreviewDialog.h" + +#include +#include +#include +#include +#include + +#include +#include "kpLogCategories.h" +#include + +#include "commands/kpCommandSize.h" +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "pixmapfx/kpPixmapFX.h" +#include "generic/widgets/kpResizeSignallingLabel.h" +#include "dialogs/imagelib/transforms/kpTransformPreviewDialog.h" + + +// protected static +const QSize kpDocumentSaveOptionsPreviewDialog::s_pixmapLabelMinimumSize (25, 25); + + +kpDocumentSaveOptionsPreviewDialog::kpDocumentSaveOptionsPreviewDialog ( + QWidget *parent ) + : kpSubWindow (parent), + m_filePixmap (nullptr), + m_fileSize (0) +{ + setWindowTitle (i18nc ("@title:window", "Save Preview")); + + auto *baseWidget = this;//new QWidget (this); + //setMainWidget (baseWidget); + + + auto *lay = new QGridLayout ( baseWidget ); + + m_filePixmapLabel = new kpResizeSignallingLabel (baseWidget); + m_fileSizeLabel = new QLabel (baseWidget); + + + m_filePixmapLabel->setMinimumSize (s_pixmapLabelMinimumSize); + + + lay->addWidget (m_filePixmapLabel, 0, 0); + lay->addWidget (m_fileSizeLabel, 1, 0, Qt::AlignHCenter); + + + lay->setRowStretch (0, 1); + + + connect (m_filePixmapLabel, &kpResizeSignallingLabel::resized, + this, &kpDocumentSaveOptionsPreviewDialog::updatePixmapPreview); +} + +kpDocumentSaveOptionsPreviewDialog::~kpDocumentSaveOptionsPreviewDialog () +{ + delete m_filePixmap; +} + + +// public +QSize kpDocumentSaveOptionsPreviewDialog::preferredMinimumSize () const +{ + const auto contentsWidth = 180; + const auto totalMarginsWidth = fontMetrics ().height (); + + return {contentsWidth + totalMarginsWidth, contentsWidth * 3 / 4 + totalMarginsWidth}; +} + + +// public slot +void kpDocumentSaveOptionsPreviewDialog::setFilePixmapAndSize (const QImage &pixmap, + qint64 fileSize) +{ + delete m_filePixmap; + m_filePixmap = new QImage (pixmap); + + updatePixmapPreview (); + + m_fileSize = fileSize; + + const kpCommandSize::SizeType pixmapSize = kpCommandSize::PixmapSize (pixmap); + // (int cast is safe as long as the file size is not more than 20 million + // -- i.e. INT_MAX / 100 -- times the pixmap size) + const int percent = pixmapSize ? + qMax (1, + static_cast (static_cast (fileSize * 100 / pixmapSize))) : + 0; +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + qCDebug(kpLogDialogs) << "kpDocumentSaveOptionsPreviewDialog::setFilePixmapAndSize()" + << " pixmapSize=" << pixmapSize + << " fileSize=" << fileSize + << " raw fileSize/pixmapSize%=" + << (pixmapSize ? (kpCommandSize::SizeType) fileSize * 100 / pixmapSize : 0); +#endif + + m_fileSizeLabel->setText (i18np ("1 byte (approx. %2%)", "%1 bytes (approx. %2%)", + m_fileSize, percent)); +} + +// public slot +void kpDocumentSaveOptionsPreviewDialog::updatePixmapPreview () +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + qCDebug(kpLogDialogs) << "kpDocumentSaveOptionsPreviewDialog::updatePreviewPixmap()" + << " filePixmapLabel.size=" << m_filePixmapLabel->size () + << " filePixmap.size=" << m_filePixmap->size (); +#endif + + if (m_filePixmap) + { + int maxNewWidth = qMin (m_filePixmap->width (), + m_filePixmapLabel->width ()), + maxNewHeight = qMin (m_filePixmap->height (), + m_filePixmapLabel->height ()); + + double keepsAspect = kpTransformPreviewDialog::aspectScale ( + maxNewWidth, maxNewHeight, + m_filePixmap->width (), m_filePixmap->height ()); + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + qCDebug(kpLogDialogs) << "\tmaxNewWidth=" << maxNewWidth + << " maxNewHeight=" << maxNewHeight + << " keepsAspect=" << keepsAspect; + #endif + + const int newWidth = kpTransformPreviewDialog::scaleDimension ( + m_filePixmap->width (), + keepsAspect, + 1, + maxNewWidth); + const int newHeight = kpTransformPreviewDialog::scaleDimension ( + m_filePixmap->height (), + keepsAspect, + 1, + maxNewHeight); + #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + qCDebug(kpLogDialogs) << "\tnewWidth=" << newWidth + << " newHeight=" << newHeight; + #endif + + QImage transformedPixmap = + kpPixmapFX::scale (*m_filePixmap, + newWidth, newHeight); + + + QImage labelPixmap (m_filePixmapLabel->width (), + m_filePixmapLabel->height (), QImage::Format_ARGB32_Premultiplied); + labelPixmap.fill(QColor(Qt::transparent).rgba()); + kpPixmapFX::setPixmapAt (&labelPixmap, + (labelPixmap.width () - transformedPixmap.width ()) / 2, + (labelPixmap.height () - transformedPixmap.height ()) / 2, + transformedPixmap); + + + m_filePixmapLabel->setPixmap (QPixmap::fromImage(labelPixmap)); + } + else + { + m_filePixmapLabel->setPixmap (QPixmap ()); + } +} + + +// protected virtual [base QWidget] +void kpDocumentSaveOptionsPreviewDialog::closeEvent (QCloseEvent *e) +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + qCDebug(kpLogDialogs) << "kpDocumentSaveOptionsPreviewDialog::closeEvent()"; +#endif + + QWidget::closeEvent (e); + + emit finished (); +} + +// protected virtual [base QWidget] +void kpDocumentSaveOptionsPreviewDialog::moveEvent (QMoveEvent *e) +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + qCDebug(kpLogDialogs) << "kpDocumentSaveOptionsPreviewDialog::moveEvent()"; +#endif + + QWidget::moveEvent (e); + + emit moved (); +} + +// protected virtual [base QWidget] +void kpDocumentSaveOptionsPreviewDialog::resizeEvent (QResizeEvent *e) +{ +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET + qCDebug(kpLogDialogs) << "kpDocumentSaveOptionsPreviewDialog::resizeEvent()"; +#endif + + QWidget::resizeEvent (e); + + emit resized (); +} + + diff --git a/dialogs/kpDocumentSaveOptionsPreviewDialog.h b/dialogs/kpDocumentSaveOptionsPreviewDialog.h new file mode 100644 index 0000000..0f35327 --- /dev/null +++ b/dialogs/kpDocumentSaveOptionsPreviewDialog.h @@ -0,0 +1,82 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpDocumentSaveOptionsPreviewDialog_H +#define kpDocumentSaveOptionsPreviewDialog_H + +#include "generic/widgets/kpSubWindow.h" + +#include + + +class QCloseEvent; +class QImage; +class QLabel; +class QMoveEvent; +class QResizeEvent; + +class kpResizeSignallingLabel; + + +class kpDocumentSaveOptionsPreviewDialog : public kpSubWindow +{ +Q_OBJECT + +public: + kpDocumentSaveOptionsPreviewDialog (QWidget *parent); + ~kpDocumentSaveOptionsPreviewDialog () override; + + QSize preferredMinimumSize () const; + +protected: + static const QSize s_pixmapLabelMinimumSize; + +signals: + void moved (); + void resized (); + void finished (); + +public slots: + void setFilePixmapAndSize (const QImage &filePixmap, qint64 fileSize); + void updatePixmapPreview (); + +protected: + void closeEvent (QCloseEvent *e) override; + void moveEvent (QMoveEvent *e) override; + void resizeEvent (QResizeEvent *e) override; + +protected: + QImage *m_filePixmap; + qint64 m_fileSize; + + kpResizeSignallingLabel *m_filePixmapLabel; + QLabel *m_fileSizeLabel; +}; + + +#endif // kpDocumentSaveOptionsPreviewDialog_H diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt new file mode 100644 index 0000000..e104c08 --- /dev/null +++ b/doc/CMakeLists.txt @@ -0,0 +1,2 @@ +########### install files ############### +kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kolourpaint) diff --git a/doc/KolourPaint.png b/doc/KolourPaint.png new file mode 100644 index 0000000000000000000000000000000000000000..fbfdffd7f6b65a286a8f885785a47c4824dd0203 GIT binary patch literal 20907 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 literal 0 HcmV?d00001 diff --git a/doc/brush_shapes.png b/doc/brush_shapes.png new file mode 100644 index 0000000000000000000000000000000000000000..0fa3d2cfc6fe79c717eeb011e2cdd5e0edfa0d08 GIT binary patch literal 241 zcmeAS@N?(olHy`uVBq!ia0vp^7C;=p!3-p;|GP;8DbWC*5Z50+e!PGGe&4=*p`oGw z|Nm!T03wDZw;VHp)O1f5$B+ufx7V+V9ySnR3G80>zwaz{cwHG o^fg6J|Ncd+WjFVdQ&MBb@04=F!tpET3 literal 0 HcmV?d00001 diff --git a/doc/color_box.png b/doc/color_box.png new file mode 100644 index 0000000000000000000000000000000000000000..106d1f532264b490d34f58bd4c4e3c73898d9586 GIT binary patch literal 1672 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 NKr|y0o}YHP{eJ?0_pbl| literal 0 HcmV?d00001 diff --git a/doc/fcc_trans_text.png b/doc/fcc_trans_text.png new file mode 100644 index 0000000000000000000000000000000000000000..725fc312477e77a372b30c8026c1bc0c4c94a3d4 GIT binary patch literal 6386 zcmXAO3p`W*|Nq&nI7w+!bVu7^DR$|G+-jw*V=B)LVd|9yV{$9bH`Iq&y*z22|)`~7;op0C&IefW+{d#y#Q76AaD zMc+qb0RS2T02H91rs^rsa*(MW%N^l^d*z`SViz~wa2@bsDy>-!xM&CcL=Z!Ru8UI_S7wl6*%e(rp%pXQR zA8?rvdK5pQDmv_l%g>s~0~V2D1unBQ*_Yx4w$%TTWo? zvR*0xT!$$br=Zb`3BWn4RhwR9(!gompXaByzbbRkhP-rS*uj>HeBurkvE`AY_p^AoR=!O{bkb5RAd7;JVkUBfof z5p|CHc}Vk2i7f&}awk43Fa_}+0yH|#sHYhpvR;-^arSv8x&Xzv+$>l{xJ*3s%k^_; z9uUE8IWa@n%D@c;Jq?N1)ZTiu<8u#3#d5hPbZ9?uXrvJ3IzOg%0O4g9hR$QBB0#AJ z>;9HVvrhEhGV4zYOb1Bwi-?MCp8Qi;wm3tR=69@2>-V}3b7RS<9DgGx^fu`NeQ
^914mC4yE8=TfNh%FYpPe~&8?9lm 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 literal 0 HcmV?d00001 diff --git a/doc/fill_style.png b/doc/fill_style.png new file mode 100644 index 0000000000000000000000000000000000000000..2950938f5705159a9fc6e2aa63e1438d63558717 GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^7C;=p!3-p;|GP;8DTx4|5Z50+e!PGGe&4=*p`oGw z|Nm!T03wD33zkfL{^~uD73S&U7*fIb_PQw(gCY-umnU7c>7$0MKt$c?Lgpzg-^#KQtyT-1aevuxpeedfy1|;A<4Vc%7IwpP bs^JZD8bP0l+XkK*aAfg literal 0 HcmV?d00001 diff --git a/doc/image_balance.png b/doc/image_balance.png new file mode 100644 index 0000000000000000000000000000000000000000..41657c8bb97f80354d127e331a3475cf93b03b60 GIT binary patch literal 27700 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�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 literal 0 HcmV?d00001 diff --git a/doc/image_flatten.png b/doc/image_flatten.png new file mode 100644 index 0000000000000000000000000000000000000000..4f29ad0fdd65afdf74f688c225d0801edada1a72 GIT binary patch literal 19123 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 HP6Mj literal 0 HcmV?d00001 diff --git a/doc/image_invert.png b/doc/image_invert.png new file mode 100644 index 0000000000000000000000000000000000000000..831b66577d0bf6870c385a3ad65c821f6116db1f GIT binary patch literal 21804 zcma&O1z1#V*ET##hysGrE#08fEg&UQQqmwfq`=Urq%=r3GJr}63?MC1(lB%kT|-JY z{CnWOpZ|H^H?oxYrul=ISG#35lct`?=i*qhmMx9PMYfIn2D%QfA{_SqjvnNssp>gFV6yx-BWv$Gf0RvE3Y+c1*KaDOs1zovm3H zIcq_yBXTtSIwP(7TMTllmEEYG589?AV08|EC-!HN=$eLCCX~}949L$Gd${wTmUazy z#QykgIPRVwzdFV-r@?bZa6cu;wRXFi^udv_u66 zqS+GRkK?8|s7kW@1U=6x2HQ-uqmUZ@mgB}pX-}xO@0-BrkhFo;yOo|Fq;h_qVn#%% zEU4%zUKv?hQetEqXSyxguLQ_N50&qWDc|ef)W3sHnDe^k^PPYUA576pDf#t`Bf5Jisv8Ii>yS9TwY6a4qQGrGv(U$mwsuknUb?xtU8HWG zD$CE0_vG6DVkz*(gvfT}Tr-_bKI|wAl5O(7zGDO(h^ub7^BiKbvBJYsjsH33w{7X{ zKEFuQ+qb%4w(9dlZp^NluWKLXA8|6Crukef>6|2NP)@(Q!hJjSz5}inl#PZ(nY#Gs zrRDQ>?tnPs#f9E{k~k=*;4hx1yNQ&nQAgjD|88)HQ-OQDo{m)W4(_ZLKgU=2c?wl+ zywW%M#eHyh5Ir3?1oO<{OX=48dK^zWhbc4$gP&(7^hUul`X=DgQeVy@s_yUB;oXIe z%i$kBdQclNjOi>dlU%s$Ij_zUm)eTddPOp5#khQABmIT*-12D^a-ya=Rt@x_MRoCw z{URyZ9!yIOo7Zxv`&FE{g;_H$0WEi*_8V%yd`^+tYKEMqz3mp{Oi-Dxi+gCS{O@Mk zppV@GYu*}O+IWKy$gv5{gM(DMMyl7nUDDzpd{qYA(l$nw-X1ZZBT5<3!1jXaX0PnF zWiO95b1CP%zQ#~|A}RrKq06=1q2_tDdNgrAR!=kUz4_dZH3Ym<+B6@ck)S=s$%SYc zt}4%O*05I$j!!T`7Yl|e*DFky)3kWzDU*6<^2RdFDyOAq8t69CP-T5terdY6Oad2q z8W77?($W>XPp9 zN(W=MEsxlG(ke`*^Q_dPjipDghUitA>Dw#R32Hd6cG}J^$jf~fzDUlos+eIR>D(q*DKvfmsNfCLYGUf?x zJkF3KnZVRwi<*YQ0sTiw-WxNQA(}3cPIU?I6upw#i~Qb?FTRd>!O&6R?WruOY|_~k zvke{NtID<+7?{R%pltt@&HOw-WtuE)O2iRspul2z9THV(RX??o(#gQDTqBcr7DnQ& z+}k7k)%#PN&}$A{c}E}b%AOuVS9v(jGx(mm&eW4i7uoj&_A_mZ*@<_8c-$ArNXcui z=S*IEKj7l&^NoLKU#KwbR7~AA61bi(w>mvt1#??SHTg4yLq^V9I-{8kP_Z0t+w?Ph zGMdx9kIR{1Z;$hb|JKI;>Ksk~cC@fO*e9}%CC#(1|Niy@G)KNb)4deQ;ypvS@u=}j^4WTuZ59_eY0Rr5X7 zey1fDY*sT#(k?q)P8mR~YfAsTLdEvFePY1FAHAo7yKpg<@Q|XkLnZ4p3zs<+x1|;C zPw3K~wAPk*#7H#aFC^P)==T28ognsnCkj!-s6h3+r1i!OA^SHNi6q0|%SW#?kb|6- zt9r`t3|mhfB{=2+CoM0J_{phe$EAyaPk4#oQ6j@(Vr%tkTV!RT42I`^WY2ir7u4!V z!mD|l_i0JE=1HCpq7}W_du>imb~g+<|4iC>;hunz71mq&k%^?lU;3?PW#1~%HhKp9 znN_l4X-#6x+{1{a{syrPLN8Uc3I|*gnSb)J4-~G~rCInMff=X`+nF zqGS9#2UwRiy2}FY>%rms0xTwPe}$z_+?F~ zh`Loz3`7_>;=yJSxSkJ9jRy!)npgjvp!WqQK4$f9V5IK%3FzzV<6L`YSP;-p&%HqM zJniW-(kFVRc^dR-Y_>Y>SWi1bC-8){3H!{JiAMN&H^!I^XP?--OT+T_U(l-0vs`t9 z96o4!@JCpZ2*xrkl8W?2V9Bj;~*J|%1ArV0^y z3Y)B_yZzC7Zx5w%=Jw1-7Y&$Z&chOpvcFY|A+=Kbf|Ew>KW(wV7oF4-@iG>QZ)Sd! z94>EbDaDn}n<#IW-d$)t_V9i+bL{Vj!+e!e{U$g7Z}XhA&Wg@#zB3M9Np$0J;_Ipj zVtgA{HD36!RB2#9;P2+aX1(v?^7>Y^&{@r&LXO|9X8zH?%Xp$3m|802HSN&4N$;$X z+O378v)ikF?320MP}eq!ucQ_5Lq_}Ur#pHu+P6X;w#x(@7mVdMp^rG#?vEte=VOi| z|H`iZKzoP#xkuOusdRfV;~nNZJ6B8P%~P5RGYR*7Q*z?M&=V$yTGs|hLNM|vj{aG8 zOLVCl1~&+cW!*M3WI*wmqjat*9*qQ&hEj;iJL+cD;&)7v|A_nUL7Y4>`+!B))jdmS zh1=-w*^$~>WXcfp zvFgiD8e!mkYQ7TlYf0&}qr7fUO#8@(Wu{ckm+L)ZXU3jp|3oSGr?hvOEcm=@BkxUk zeE)3s{rF0Axg|Lv64?o2{jwwcdasLdN77AO7D5~5k&`9Dzzp0VDaZZY#UlqvME#|X zYgrr=@BZtQ(}4?r@OLo6$?mnofsOsn=KTFeQP$Jn{7u>Wr7rZ>j{`Y;=?(%@)*7Ps z8RO(QIqldqPvlB95jYgzRC*)d+rJXYnq%iZJCPaRlxK+-AroSHZnMd2Zw0Q)$ZVK; zm?hPmC?*%Jj_*nY*70CCEN9^3NviaVgPt<=)E9XUHB4~Y9c|Zq_#sf*cu4Knf8Mca z{%A2l`kHO~yxML$Yv%bI8c}cfZctL)-McFU1akHD?|iSY@A1rRy)VsKhuy98ny>jVa$ z7>D0I$5MVzwx8mX9#tT#&Xo2XW{9>B*5tr{J$1P)VHCr^eGPFKxb~}- zW1`Uh7X0!{#C5qF-itUWn6kaU%FeXGw6fRE)OM<>jB?6i$~9Wd&W2R{@_6r^+EA^@ z$SA>X_^0Z|lCDni%^@QTmn}Tsxxtpc?kO*;(Bt2O^WCzT$NOt}(sw|AKjq+I52mqA z)`X0HzRn8p!v|+&kNVg&SNl<32XN_787X+}rcE{OdeWqoT=Co;i|6=swOYRBhdaC9 z--OrozT74Cve<#yd&_237{YSV>!9qr?QBR@H}TWGsecgBH#pKfWE651cn4%WJv?z1 zKasds#ErZk{eJvN_6)DZ@MJxc`&#spnX26Iyw|H~gLo4@WU1Q!C`4|jp;~Y~%hZ+M zb-kiX^IX?{7Vct4|dbNxv_Qe9wkX!o@v6pn(zO2m1EX+44n* zMv=Ih9Pe5V@gbww{2)DoNzW*2Hu_`(d#7=(s-3@)0mW|n z!iYhj; zh#6kywmpLe`bEd0msG0Hcq7=&%)W15Y7{*Kfs7wblz*4+AO$2Mj^(7Y1D5gxu2jAA z799l2bcWhkZ^%Zb4R<#)veai$XGVp(?*6TGgorp@EDePokkQ!*fk2j-I?#J~G?z{x zB{c4CmrR`7GhRS@Gn?cCG{%P6W@Os(Qm1w< z+7!2EuA(Wy6K-?wjjd>KRa$DWNnAYJaX>HhrOILTR~K{*CPP}dG;bTWx2RqF47?^q zyiPVJ>m*=1FJvY=d%XsJsGV6mf+xoBt8b9iWhRpgdXgJ#ms2HMWY5q8e|nv7O*g27 zhbI*er&vAZ(5rM^8oy5-7N*$#I@xx0X?p=#=d+rL7xlwy_Cx28h0D6d{!DQ1c001> zJn)bLT6z~$q_6KC=ck%{W$Ub#X$du-k6Yd+=6bfW7se)23x(-d;7|%X&3wt5bT(>q zQgZvYNbtGx=TWhni^m0gx$t}C(SYu!&?uMvqakBet-L&EK}3}NhGlTKb}6`zTeP5i zt~xA?53*^YZm9N6CL=mGEonQ$hWe>lpmJkLX@mBfKn4i?@5zBRrx2tZlbvqe1hsR!mv$ zWhEIBS+N3sD)`f#sUY753umLwa#j-eOs9FYQ^};!b1S${m2vb-Pio!!?T?J@k7T2> zikeZlFNV}60JE4!i6CJS9E_?YWw`4L&~H4ICB=4zlJko%b@faLypCxeU~QT9sOR?^ zHhN}Y25_ktc9BN}RN{G;JE4wrb0ZO-{d8(l(rLy!#my z^I1DPp(wpx8DV#r`F_#gGkoI6z)M#+L_L}uOeW&k75Z#3pyeFQ4;c2{=ro4;GEwS? zgZcS9y_uQ$#)17b2V4mvh7QWUj4xh2^@10_tc4Zn?+lIB)g2x=&|Jq1AH3$Yc4)9d zzPhKal2E$yD@XScLT1~3V~vKg#G`%l)HA>>JXV|_@2&6f;36}XeglNaKp^h${oaAj zt0K$*y5i~pvI^I~!VPvJZZ`bWY;1%3huhm+Vp|QD0iWN%sl`98J@9a&kZa0*-wOi4 zvdYcHXt2M_%k3EnfquPV(L)g*-DQ1-D8aYc%)0>7q{B+&vzP#;3D2?B(S*{Ulv09> zS>ri3!XS1AVgSv20JQf4+WYI1I?&hnSv)6u7zCn!5u}&|unh?G)cJwdYyICS`eKC3 zw6&cDtE3B~f&5d#KShr5*ZzdI>e3baZ6!78U21%Bpa6lK^L4-Juq7?a{eMvKEt7&k zzrN5H&mMGj_A0(oup@S%TSzO?&M^&J#aXpj1Y0HaNOV1 ztFb_AiMUbkv=DT~sSojrasg4|2|UAV9ITqQ|KNd`H`GO@!YMEo$FSTEUUz)xJZ-xX(S#IP{p0y^eQm`Z~J-AiJ+_8w~NF13IBYj1R}B zWGn)V1boY+hlg#zQk=Wk*sM3!M~uZp9oHio38^SbQJ9WQ<8>LCtcKB@Kcswsvz0p^ zATMwpXD8x{OD(R;zFroYTj=m2bgw7`U~Ex~-)2KYXut&93T|tYijQy~0B(ORX=+eB z!hal4yftsi45Q$;*!86UJDl<-`6oIF9*rscwAw!lG-x|HHeI@57&Vn(EH})#|hXB{_U}Crm$?yUdi-JIc={|o%r>g9xv{oi$#ivrY zIQ5)M%uTtysxJSI9NS)9*}Jw>*~i7lRjtkqZq?ZodM~$cA?`(qdY*l;>8Hd8<0CyW zf@Bh$qu4DCxD6_=T2rw32&&nsgd8mp`CH2$UhGp;IezZC6v9r_>WeIyJ?ShuRV;Vf z^CkvB`2-Gxd*CM&5`B|*&>7&Naus~!_FWXHfzy||&^v%CUO;7lbKnVh-MsZ0jV|5q zcQ!qISWftCO61AAPmrgN!Rz!?`(~3?#d<_n#{0WoZqzSxgGh0`H(yDGE?gakyPHP1 zxyWj8WJ+l3QcwT^S!GpqRVwhGbd_$e}!u{d-Bs^b4Ouc`yDrR zG4(GyfmGu>Kf=-3leji3n42#u`-wTp?&CfC9qKcK;eQQdoTxEJQ{9?0z+nSRDGBVX%+Noa(=VM>*rnNFb)*{b zz&Iv4WP6yDVse&mvAm#_8}t>m~8&nwrs9MCJK+CC+t~qpC#)?U1J=Kuta()qr_0YMP=gT^yASb?Z-MQdgCu zjs&4I8+JR zoipuijNvK>F*9+vU9WBI4FnqbIgK|-Y&KonbaZ+ZN|olhUW8?02I4=r)WY{J!q}qO zbWl|wc$FFo)wAyEIc@5`K7T&fd%bhsjXkurx2SBHc-|Zoys&a^MvJ>T+QK#&Cdo3b z$jiZ0lUT7~9@>pZn$mnJ}WyuZ5*cE}Xt5#(fjn1;85km56E=&zePaPPC2e%sn|?`YvJ zabK2RdX|A&T(m?GmAHRWmD!;8Qgw+D*iYZcKy5+XC$%D1+EQ^b(_rebeyNb8{~INq zKrikHvh)5cc!3IKTY_SD!_*kfbWM%pW$KrD4&HSkcR9s?uKskv$w7Syivt$2&W5@w zi7ngMiaDA2xs-38A}g7Y5hUlZ!o$(Z?mrvfyZ*v4pMd!`>A=(B(4TAY-|6&^atdO_~Ef&#jvmtwErl`VMbI=ztJpKUxI;?Q=r7~B`hN39Ky%(s>j{m zpO4X~);$LnDwn6{>+0F0b@ym{@`G7hIb-=Z**6*wdho>A1@WWr?iB>DKWI%G0%K z4t;j`lJLuK>Fjo9s@^n#qLzGLg+(Hh{kbc#cYH5jKalAOeV#?E;CHyr>XeZ#;Ul$N ztW>!YmpI~83~f$S+IaRv{=nF`_l4{m<>G{cdZCJ^Czi_8()Mhe@$DN)+&IQe#lZ#& z7*R#BLyxciD6`}h0Ts{S;4;>}LHt}m}g*psC5Y_vGFmq$PlundKHiBy4Q%B)bpzg z>c|zQx%Dfw;H2I<%pGwcS2?`tpgZ!qxOH*7?e{`jo{Nad0j@hasR~Db{F?+We?=Kj zvP%DuC_jQJSl6qDao;m}cyFh+>VG1;H%wbv=UkB00oI4Ebyaf+el$|^U$e8U0i z@Oa)EFGd;Xzx^C(5{%+-T4&3CDF##<1$B6!D$x4Y?SboC`am`M?}s7xU+J>xfM1;d zHOU}A`jr9wVO4=k5$zW6*87<8yxU1cGi9hzBJVQV;j2H;608j4;!=JE|CJh?DPcKE zBL$}o!MmolfYa;1SwbJv>>IySL{SbaG9n(`ON4?o-JDM$7we##nTm+S#d_r(-yC4 zjV((L79qDY!G&C}>Kp>>O#TES+|&y^^q+g0x7KY zrtoPLXsMT_NZt|5pqy^*bKPsbPrW_Fto)ur#3KHHj7h;XMKV+V?uAbR)cnrT=C{RA zzWm3n*p@1^va{6;;O71@3P*W{Vl5xL_Q>X?$Dj6|K`+ywW5p@FCKq)&NZps%fDgWeOrG4s*Hzd_?w;4{geTwU zV_;3C0)|Y9FWhVqI7(kO4?g9_I=x3SHENJNm4|gBzB~|3oo?{JJ@txs*o#pw53epbld%u`6zZPA$Jewd~+PFV0 z>4CNwdl#EF?wARDs!Xo(;rValsl-V4#m*_)<0v~SPFzf07zZR83!oi=z%zMlcnrV1G^$JQ6RK#!LMbRME?B-}F{Rq}q{?V^#@SLiyaSz4 ztTTH}4rr;J?atwL^nQCCm@_6ABHAEfYwyjJ$$7H07=6NE7t>fCXz#7hC@&2>j*>ed z$*RAa#FVFiUMlOxXa&Vy4RTjcRoN4jDmkpT?(WJ6Kygt^NnnOkiNG9YOM3B?`G?1t z?jx2H(-s~L--_19BQMty^6;#`ji-7VB3+osNnO8_*+Jv+R4#|sE+!cEBm*BBKGoo} ze}$N;b3yJQLv|Q1xvx*=eWnEbfQ%dy876R5nUwEyCRwjVbfYC2uMW8%51ynjFvg$W7OsFQhDDulDs&8|s{>Aq9xD0%53$AP*&l$*dj{IzpconASd zjhC%GA-(O;g^P4%j&8O29sT4+JH>lX#LR!&Oh`I@X0Cq#zL8K92Wd4nikl%^#Vt-& zdOKdp`;u+(9~~ozn`vv)ua!N=s;~>9xsw-nZtb)jWomH?Il!a@D9p*H-Ga|9hRg4N zEO;jq@wpZmyz!3+-H(u1x6*xt>0rbHHoNVL8f|gLL`lx`jG5-e?#3tKOYA$SUWc;AiB31Sr#_*{tpgCR8dHz^W&s;o>2_v2zg zQXyOUqsV{pg$4&pkf)!GT(5F@L4UF+5cxV?b? z_u4{^wzW8<_jAPeKS3hy`6&H4~i{C^3(2Jt}IUX47^qo`=9${ zNQ=j{a@AZDdYHItJAfCVz}Q8DJn=oZ#G<0fsHl#h=9>;Oy}eI{oD=GL7#G;Md$w_Q zh^+Lu6aY|LvVB7?T3qt0@AKYPb5#@iYzF_M6&c|}9?0k(VI3KxUs?haF6BTn~jlNid&eBM95d$A=ttKpbMM@(KH|bLDnNA zmELH9I5vCj`!jb@aBh_|^MBOCle1Wvuin8>;04)uEG2hcJ}>@g^65j^@TYluFLSCe|)`9o+Ldg0BY!iJQZ=E(AHLWw%So{ z`h26_#IzLN{r}hWC^G*)($oLa>D7P83|D)yAtjIB@d&u?K zJjJA2Czlm=k9wd?G7tniWk8{q2q3UmWiEwD9VwNCiT{8-;brY#GV*_+LGTava{8c1 z|G&6L`N6*t4I4O-6lMgF`VmHU>m-=Sc%Usd9AGX~Ckp8Pe_ErwedN~WY40e+7krt|A$R$Q&S)R z+bN8;!L<8~*za0zSK$^#g?l7Xk$#75Bi$r*?{B^R3cdRulhp}hA{|X-8!lId$%e$k z#snK9{-IKmt~>uTsQfRh(W2JLdkbs~@IPMvzpo+Fr6Sn3D%5-ot#-J&n&KWi+88P{ zJ$J=J_&HqA-wv>k2mhu_Bk{Ak6(w}{w32|nHokCFA&?FLdaA3k|I^2tcfMUyar@k8 zR$NRDhvspqqrTk@-6%T=H!g7bJO;w;E6t3XTYc$AF4Z^TZ8-pvbA7q5pE@)VWNK7o z1DPLp@H`7RNQ|5%qLHrMLj~3Vh{gWjoPhA4U`S8^Ds4f2 z)$ZSJ+;04hN@4&-ssEGA+?ZZB=4ux(a?^BW+lf7AcRDnw4(1Q=04`WTTmc)dZ4t9Q zQqd73i3U6}6!Uy>lM`K@&t59Ok*S@GMNtMq)u_(%bYZ_BT2dDn+J_7SxfpU>X*V>< z3?yD3VORY#LTjVM7$zrotWp1DP-Q1j3w(UHJI>k8dR=Z^m$B4uRu1V~skW#(!iS%f zv?uejtovdE}*bxtM*iy9G$fOQINZJe0-74 zp@!vhb&gTzp%it{Vv(O`Wdr8oQh60R_4=3@Z@Wh&QryqmPp76OY=~04`96D~Awi4J zZrLDv2M+fAxX-aV8zeS`Y{_&*4eBxqIW;L{tuYqzJkV(8T8i1gnF|-IE++@s@Of*C zcMD6#cw3Zk1G_I{iL|(Snug!MX8-W*1~Nb6Aj+*R2&^(@-e?qO(aOg9x6VEINR;e#)~b=Zx{;CLO%s@BNZs(4{OIqd0;EEclm~bM;TTSLYFW!9cyQ zp{Sag7A7>0fsw+x&>Wh_+0)C?DljnA68*cQpwi^gHg+8;d}*$_OEGL%Lo=R`$fmGM zQ&WR?ctp+K+(cP=e)Vte0v-t7{Nd{T4=b%i;#BhW_Tb>xB%>u*_n%7IoZx_id8N-t zrO3?yJ2-M5Yta@L7UTLi(?&jov~+cL-PJ}14~H@E>^|=5?z=df$32zeA!rE=_#k`Y z=3p3bczoE5;q(#4@+boR*14KJV4B_K_X z5wulMl00Ysx%{!qRe(}NG*OK2`QA5wUVQewadfb>k65Z&Bl3db zhNy3j5Z@be)4e$gOQ1Xr^_2!VW@O!@)_|On!V>Zwmw9#M@D55>W8Z`(T@z&6$RI!q z#j*F0!4@`&kH)Ng^$2D5B}y@nZ6rpbC8`aV`}qm)(kP=fPYdZBGU6J@!{`dK^XB8} z3g#{O35AF}+AtP3(-(Sr!uglnx>$wUBr=>0(UE7&fNZozh*)LOtBtCWFZRbJ8I;i& z02%7WjQ$CLIAiF&i!=0Wzjm$jDjrWoQH-RnX_kWU(T0eEL(>^f8n=jIwb$6Et%n;u z@8W_X2t8!Kgf@5Bpj&eg_KVsrKm?o>@lA z2}Id#?$j@bF@fdB3KMEw6h#e-^_8P=sH%jaQ-Z*XDm03{p@l}5%D#H%3_cfz>~^p} ztzM-fDz;wYl^#P^NzwLTKv)Canwo)@U`9F2hdLE3PY}C+v<6#{%`tI>ruS{%@7eKQ zCn_qFPZ{>xX-*jVRRYDoF(qY0zGvXdH{WVUOhw)i711IjQEP0JK`=my$eEGm#OZsX zoW#?yTNXtuU5_guxYX6liDR#kS)fH^raes#9iug^7i#?D9!tlgqwQ!ashSX{yH zcr9@!t7+V&uE%|^_Oq;y;p{hX!S(9-R#n$gZVtSvv~d@aq~YhBf=6l z;E_;}aK7*!+kEPi*SNE(d$|}R5zV6@UCS6alYdeOu70)lPzZ<&d&q4<+C%*{w>D!-i)<@Yidabqn1H6h8er%ETQu zhp#{TocopeIf-grq46)>p~%@vE<9`Yx{>10Kk`o2mcZFPTi&H&&GIo;nOOA!r>=b& z<(7WYyg%Rmuyc`Gt`b^W7#_r5>|rkX8A=E;7Py8&Oii`rPzjw49-v?ecwU?W>WUT@ z=^6~FNuO~o($Fn-mN1r%NmOwI!ZZ~KdC-XXHeeTt%60BlrvTBH3JaMW^EoXn=r=v3 zj;U4ffSo_;VF7zby>bLE)aN~iwOjvZ)C&bv&_?1Qj!<5S3YtibwJKTEd}&@3M ze+ueEfVT`+m&XYUW&{;5ERQWNlf!Y&z;S1r-{ytLvH*2&3Y$;$GVoaZxFY zYfJL!n5aai4+|b+9{|h^yme3pX21ZuJO^5)WZqE2s{q))d@zO&JiFzHh=1<^GC^c- z*uirp#nSrnI*p-Mc)uq`82}lgFYfjmeB908aX^X*`02ldf_kITbl%?0EqXZ~*3C_E zF2q?j{-s~+aBX%o70sDb5UuPH$@iBt#WsF^-ZPCJXx%c{D_5sitb%{=nw;i)rbtwtEWgm*d$)ahc1rK= zZHrTSK3pdKx63E(gz?>()!EhG?MRE7-LV?e^D}O^7PMGiFP14t=o2RG>Djze|2AT7 z-=g2!`(=z6nOaS$m8_jt z;URdNl7*u7B$Pi5ILB=UFNB(!e9SrlJeK#@X{(ePiK`H=kLD4K%{KW4E9nd3>3RJy z-DtNr5c$lSCSVg5&9U!e^7q#j>nH3s6-puh1d)&WRVJ(YH6y?&WM$@ z7VN{Mr9a{;cP0l!+Gv|3aVw~Cl9EvL;%1O?2{0*qfG}?W`78i^BE8Gel3Gc<@6lRcWcM(gTI_t^ z*qhJ9;W7Ez+(uGp=pRruU z^s3`8#;PBr4=&cJ*}U4axwX1IO9pTDx|{`Q$RY9a{D@1iOFEd$jf41{F z@@8d$_b{{AC+0K7dgzqsHGJW%dpkhc(f(30>@hgAiXL8J`IS9&3XOUuOI)3uW*jF6)p3(|+5BBRQ0 zjVApF>EGXd`V5S<+sO=X@PxG7#XTmaU~271rMukNxQEr@yAH+MM&woSf;lI&=-;jQ zdP6tT#!5}(`fMhos(*h+)VpjwcAd&UjFYzbWJQZ^MOf;pucytkmYgzwno(a6-nW<5~*XFF#jOS4^Y?h9`3)gv! z$-?4Cy`ybv9Y}TxWm?>M=Ak1aoE4qFBOJMs9*Q6WYeBp5*x|&uTTOMG`6?;n#y-_A zl4G|%Uif((&kt)fUi)1*)Vhtm;(kNeHX~VP!mYQ7FfiyYnM!LY8E0qoA17+X)ViCK zgpPIJcGl>$@xYt8auPzsk@o)+;IU@H)y1uiy*|1+90K?ry5S;`!M028L=b z;$npqbQv;0JsR#oguH-=p78wU>1wQflhb<7zP!!f!DW)zqdkA0dwASNmCDj%&s|VW z@sU3RO(b?E(?reN2VxfwnvQ7*DK_wE#8RG{y2_jN#5^)>JfF8zfgVmSmk}^;Al6Qr zXRl%E?99p;xRGHSG{kul){854x@8I494GG`HEg9WV&;L;+Os4%)H3?q^q>X(sZ+*o zR#)2{R4uZRHrB4z)MDZumj!8CQSI<@C(Q{0q}Ory_Ow@aPWInzFCXblQO?04{_0r; z6{U=d?axY4?Mw;cbQY?8ZHl^^f%`AX*B-@Uz87z(qx0EZ0-I{Z&F+wV$0U*PyqFbl z9^tEOKO0;u5RJ9xworXHvs}n6bB5$8%+%A-%+=8+|mZ({b$Ywcwp zq-9}7LLO(f3!!E0Me>$r<`flyavhedD+aYroD9!LE0WXPbYl5e77O}%lEoxEUadpF zee?N9e?ZIWdnO9E;S1$5W{01d;(9K~DQa0Y5J3K1ugIgJtcg**^EKb)5H~Zyq4Ap4 zrSGl^6c4D??McuaS>HgMn(TX4#GK@@Gvi$F=q%A8 z*ZL}i5%cYMziRqqb;p7`UMF7+!V|hI?F@XhTuZ9DE&B?*=)|0C-+ZdnNXi>4x`v#r z-H%l2eg1CqIUWx5*x% z!cqID)JJQ(>X!`}qW4}vKTtmcgI2lB*gj@yad@V3XrEI2Zg)?!wyw_a?ZJTBB4kfLJR@702`+Xw6mSln;bN`=c|9jC zYpfT-W^{Je0J38Hx|raxO1DQ zkO~>ykk=@@ql?qw`peV%KLXd~4r+u#`cVxcPU(Dt<&UvYcunUqg7!Z`W&%F!&&aIZ zl>WVW`{{iuTh~4r8!+oFNf^jzCVX*Ona21$&}>a%plQ%6AqME_#j3rXW*MwPqqgUT z-1o&1HGnwq!4g2(@lCNZsty=ML^nG;Ad)5~=H?09SwI~zOKB0uPlL4rl+*4xkgTVD z6aaR4U7uf%S5fpTFZoG9%csq zM*t126_S~G5?d?~=z^F71k_B|QT{6iFyO!Ppz1h(*p5tY8Tl19XD!^r>#p*|Zh@%9u|WOSxz<&wVl zu?UI*e^qT6MUp2i+1q#>LXIbKDJ*+3X~>Bv(w-g+#OAhphId!mE|V7r;*ko1t=D-9 zHN!{Y<;jwtB(}%-JiVP3LZ5kR0efqcTn3NP6m$qyP^Z4^*yGRPXV@+?K{avGQ1Wy|ZO69pGq!Ql*L8lq4MjBw>YC z+?=gcB_47VJIds{2TN(M#$v(%tRD~(JyWss%ZC2)G-(#rr0cyriHi$RL2dVI=31h` z)>u=_TP$>8oiKLr~auo$Xb`M$K=_vOhBn>Gl5 zV&mR3l@7+dHGSA|lCcM!n9peaA%%d55k%YEj>a@;YU@+k@u4ZR%0}Eig#+b~Wu6?{ zYNV2q=a@dwIlEQ&yW;O~al`M5P1ZTeN+dH4tBmm4?#)#LFW$AshpnAM!X(Jrv*17D z=oy@2Q95H)E@{cJCMr-D_LLG?8y4S#w@EE7xOx>RJ^35DR{qUpG*NThImXr$8L{Aw z?W^;avCMIqt$g5fwt3noqf1Rlq>?72JxjB8#G4^nT3qTt14bC-H_@?inXG+vJkx{S z{oiiGeY-2_f8GZ20aSzs-8h-??TH(J?0>gHk=~vlfbCCoxWBfol{y^*(_u|&ac%Z) zwU-BBOCA2?cBt^8Ca)ETe{&|jC~%7jlxdkx0En35uxPvm-z8{#&0bN*z!+Nn%9_O^w%(J?xeW|C${Wh*^5J(IS5)FN7cC#oxcW}stjM0s z%TdWQv(ITms?x#+oZ?7+@mWdl;CXTF8D>HE;lwC6N=bK4SmDp@R;Ckd1gyRm7wT16 z7jJJrd=Nbf{tLKYrIi~mmqD2@hp?fW+sf)J6^|Dv zXR&}4%Klc=-SGoL#-_5{8;gdjKMqhSiK@gJJ-y3=8Wa-r?W>PEgrm1IsE0U%P{sWK z_a5Cq=N3^fQ*WsJzjI0yzA$fcPRQ{M)&Ho|Rd=Fny!aT&5RTY}oFfTPn;D>HB1bFQ zlT;6TXYz=qKJ%;_*bC#HV8mi(bCy5YIuAJYf%{*h5<)LbZqaDgGF?8<+B1{}iu241 zM|~bAJuPy({t=q|fybiX@8SR{A})LbFBv=<47t`K{kr*?0J^%|vSAAmmzDI2yk#mH zDgg2LNMMuL_W^RlN`T$z<)XO_%4!|tp5f(yUq=`&aDr>+wCms>hs%aii21Ej5s+V)}e2swFnc75=$*6UhQE>eW%N&u(p0jCt5u%(y#CZP#sxQ{#{pP zcxz!&<+@ts2b+CS>BJAm%l6)O=P8E)Q%t6rp9!URZU>7T6=z`^<*+W+TH!piIv<-Q z&$(PETn#WeEAjwnX*VF`|Ec8a!0Gb38cZ?FgxroD@ru=*%i%8CE%wX;h05 zwjMU`L+@S^tB}elLz%534AGoY5ywlzu*E`RUYeMw3=hoaet?yY~Bi z-|o--xj*-=&sT?RSoL78uW9l8tZZeJmgd~cM)#+(

oB%a-U)jed35W?zS?e#o+s-|ximrp-t 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 literal 0 HcmV?d00001 diff --git a/doc/image_resize_scale.png b/doc/image_resize_scale.png new file mode 100644 index 0000000000000000000000000000000000000000..9a42d437e9507cf1bc3890bb6f9d287eeda3a164 GIT binary patch literal 27797 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%0D6uKYi^2 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 literal 0 HcmV?d00001 diff --git a/doc/image_rotate.png b/doc/image_rotate.png new file mode 100644 index 0000000000000000000000000000000000000000..9ffff520e540eda6ef08113c6076c73d84cf5302 GIT binary patch literal 22127 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 Hx*hg^`4p9b`_k$El?6BdAgou`rz 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 OAT1#;UMi;J^S=P{I;t}O literal 0 HcmV?d00001 diff --git a/doc/image_soften_sharpen.png b/doc/image_soften_sharpen.png new file mode 100644 index 0000000000000000000000000000000000000000..8de84c07cb55e0a4f42771e4ccf7067d46d96c40 GIT binary patch literal 52335 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 literal 0 HcmV?d00001 diff --git a/doc/index.docbook b/doc/index.docbook new file mode 100644 index 0000000..818184c --- /dev/null +++ b/doc/index.docbook @@ -0,0 +1,1505 @@ + +ClarenceDang"> + dang@kde.org"> + ThurstonDang"> + thurston_dang@users.sourceforge.net"> + + +]> + + + + +The &kolourpaint; Handbook + + + + +Thurston +Dang + +&Thurston.Dang.mail; + + + + +Clarence +Dang + + + + +&Lauri.Watts; + + + + + +2004 +2005 +&Thurston.Dang; + + + +&FDLNotice; + +2018-03-23 +Applications 18.04 + + + +&kolourpaint; is a free, easy-to-use paint program by &kde;. + + + + +kolourpaint +kdegraphics + + + + + +Introduction +&kolourpaint; is a free, easy-to-use paint program by &kde;. It's +perfect for everyday tasks such as: + + + +Painting - drawing diagrams and finger painting + + +Manipulating Screenshots - acquiring and editing screenshots + + +Image Manipulation - editing photos and acquired images; applying +effects + + +Icon Editing - drawing clipart and logos with transparency + + + + + + + + + + + + + +Using &kolourpaint; + +Click on the following links to explore &kolourpaint;'s +capabilities: + + + +Tools + + +Working with Color + + +View Options + + +Image Effects + + + + + +Tools + +An additional option in the Settings menu allows to +define if the tools draw with anti-aliasing (default) or not. + + + +Acquiring Screenshots + +A screenshot is a snapshot of what is on your computer's screen. It can be useful +to explain some actions that should be done to obtain the result, or to show the issue that you have found. + +To make screenshot ready for editing in &kolourpaint; window you can use File +Acquire Screenshot item from &kolourpaint; main menu. + + + + + + + + + +Using the opened dialog you can change Screenshot Delay (in seconds), and choose +to Hide Main Window during screenshoting. When you are ready to take screenshot +just click OK. The screenshot taken will be placed directly into the &kolourpaint; +editing area. + + + + +Tool Reference + + +A quick way to select a tool in &kolourpaint; is to press the single key shortcut associated with it, +documented below and in the Tool Box tooltips. You can also hold +&Alt;&Shift; while pressing the key, which is necessary when you are +writing text (as the single key shortcuts will be disabled). For example, to select the brush, press +&Alt;&Shift;B or just B (when not writing text). + + + + + + + +Brush (B) + + + + + + + + +Color Eraser (O) + + + + + + + + + +Color Picker (C) + + + + + + + + + + + + +Connected Lines (N) + + + + + + + + + + + +Curve (V) + + + + + + + + + + + + +Ellipse (E) + + + + + + + + + + + + +Eraser (A) + + + + + + + + + + + + +Flood Fill (F) + + + + + + + + + + + + +Line (L) + + + + + + + + + + + + +Pen (P) + + + + + + + + + + + + +Polygon (G) + + + + + + + + + + + + +Rectangle (R) + + + + + + + + + + + + +Rounded Rectangle (U) + + + + + + + + + + + + +Selection (Elliptical) (I) + + + + + + + + + + + + +Selection (Free-Form) (M) + + + + + + + + + + + + +Selection (Rectangular) (S) + + + + + + + + + + + + +Spraycan (Y) + + + + + + + + + + + + +Text (T) + + + + + + + +Brush +<inlinemediaobject> +<imageobject> +<imagedata fileref="tool_brush.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Click or click and drag with the brush to draw. + + + + + + + + + + + + + + + + +Click on one of the shapes to select the brush shape. You can use a +circular, square, slash or backslash brush shape. + + + + + + +The &LMB; draws in the foreground color. The &RMB; draws in the +background color. + + + +Color Picker <inlinemediaobject> +<imageobject> +<imagedata fileref="tool_color_picker.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +To set the foreground color, left click on +a pixel. To set the background color, right click on a pixel. +&kolourpaint; will then return to the previously selected tool. + + + + +Connected Lines and Polygon +<inlinemediaobject> +<imageobject> +<imagedata fileref="tool_polystar.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Click and drag to draw connected lines. The polygon tool is used in +the same way, however, the start and end points are automatically connected +to form a polygon. + +The &LMB; draws in the foreground color. The &RMB; draws in the +background color, and will also reverse the fill color for polygons. + +You can set the line width. For +polygons, you can also set the fill +style. + + + + +Curve <inlinemediaobject> +<imageobject> +<imagedata fileref="tool_curve.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Click and drag to draw a line - this sets the start and end +points. You can set up to two control points by dragging. To finish the +curve without using both or any control points, click the other mouse +button. The curve tool draws a Cubic Bezier. + +The &LMB; draws in the foreground color. The &RMB; draws in the +background color. + +You can also set the line +width. + + + + +Ellipse <inlinemediaobject> +<imageobject> +<imagedata fileref="tool_ellipse.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Click and drag to draw an ellipse. + +The &LMB; draws in the foreground color. The &RMB; draws in the +background color, and will reverse the fill color. + +You can also set the line width and fill +style. + +For additional functionality, use the modifier keys: + + + +Hold &Shift; and drag to draw a circle. + + +To draw an ellipse with a center point of your choice, hold &Ctrl;, +click on the center point, and drag until the ellipse is the correct size +and shape. + + +To draw a circle with a center point of your choice, hold &Ctrl; and +&Shift;, click on the center point, and drag until the circle is the correct +size. + + + + + + +Erasers + + +Eraser +<inlinemediaobject> +<imageobject> +<imagedata fileref="tool_eraser.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Click and drag with the eraser to rub out mistakes. + + +Unlike other tools, the erasers draw in the background color. To draw +in the foreground color, use the &RMB;. + + +The eraser only has square +shapes. To draw with other shapes such as circles use the Brush and the &RMB;. + + +Double-click on the Eraser icon to clear the entire image. This is +equivalent to using the Clear option on +the Image menu. + + + + + +Color Eraser +<inlinemediaobject> +<imageobject> +<imagedata fileref="tool_color_washer.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Click and drag to replace pixels of the foreground color with the +background color. To replace all pixels similar (but not necessarily exactly +equal) to the foreground color, such as in dithered images and photos, use a +Color Similarity setting other than +Exact. + + +Unlike other tools, the erasers draw in the background color. To +replace pixels of the background color with the foreground color, use the +&RMB;. + + +You can configure the eraser +size. + + +Double-click on the Color Eraser icon to apply it to the entire image. + + + + + + +Flood Fill +<inlinemediaobject> +<imageobject> +<imagedata fileref="tool_flood_fill.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Click to fill a region. To fill a dithered region, use a Color Similarity setting other than Exact. + +The &LMB; fills in the foreground color. The &RMB; fills in the +background color. + + + + +Line +<inlinemediaobject> +<imageobject> +<imagedata fileref="tool_line.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Click and drag to draw a line. + +The &LMB; draws in the foreground color. The &RMB; draws in the +background color. + +You can also set the line +width. + + + + + + + + + + + + + + + + + + + + + +Hold &Ctrl; to draw lines angled at the nearest multiple of 30 degrees +- these are the lines in the red diagram. + + +Hold &Shift; to draw lines angled at the nearest multiple of 45 +degrees - these are the lines in the blue diagram. + + +Hold &Ctrl; and &Shift; to draw lines angled at the nearest multiple +of 30 or 45 degrees - these are the lines in the green diagram. + + + + + + +Pen +<inlinemediaobject> +<imageobject> +<imagedata fileref="tool_pen.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Click to draw a dot or click and drag to draw a freehand line. + +The &LMB; draws in the foreground color. The &RMB; draws in the +background color. + + + + +Rectangles +<inlinemediaobject> +<imageobject> +<imagedata fileref="tool_rectangles.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Click and drag to draw a rectangle. The Rounded Rectangle is a Rectangle with rounded +corners. + +The &LMB; draws in the foreground color. The &RMB; draws in the background color, +and will reverse the fill color. + +You can also set the line width and fill +style. + + For additional functionality, use the modifier keys: + + + +Hold &Shift; and drag to draw a square. + + +To draw a rectangle with a center point of your choice, hold &Ctrl;, +click on the center point, and drag until the rectangle is the correct size +and shape. + + +To draw a square with a center point of your choice, hold &Ctrl; and &Shift;, +click on the center point, and drag until the square is the correct size. + + + + + + +Selections +<inlinemediaobject> +<imageobject> +<imagedata fileref="tool_selections.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Use the selection tools to draw out the boundary of a +selection. + +To move the selection, click and drag on it. The main view will scroll as required to allow you to move the selection to part of the image that is not currently displayed. + + +You can free-form Resize the entire image or +Smooth Scale the selection using the corresponding handles. +Hold &Shift; while free-form scaling the selection to maintain aspect ratio. +The &RMB; invokes a context menu with common Edit commands and Image Effects. + + + +You can use the cursor keys while drawing out the boundary of the +selection or while moving it. + + + +If you hold &Ctrl; before moving the selection, then you will move a +copy of it. The selection will be smeared when moving it while &Shift; is held. + + + + + + + + + + + + + + +There are two selection modes: Opaque (default) and Transparent. If +you use the Transparent selection mode, all pixels of the background color +will be transparent (background subtraction). This allows you to paste a +selection without the background. To perform background subtraction on a +dithered image, use a Color Similarity +setting other than Exact. + + + + + + +You can apply Image Effects to a selection - see the Image Effects section for more +information. + +It is possible to save selection to file using the Copy to File... item from the selection context menu or EditCopy to File... item from the main menu. + + + + +Spraycan +<inlinemediaobject> +<imageobject> +<imagedata fileref="tool_spraycan.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + +Click and drag to spray graffiti. Hold down the mouse button for a +more concentrated spray. + + + + + + + + + + + + + +Click on one of the shapes to select the spray size. You can select +from spray sizes of 9x9, 17x17 and 29x29. + + + + + + +The &LMB; draws in the foreground color. The &RMB; draws in the +background color. + + + + +Text +<inlinemediaobject> +<imageobject> +<imagedata fileref="tool_text.png" format="PNG"/> +</imageobject> +</inlinemediaobject> + + + +Click and drag an area in which to write text. As soon as the border will be shown you can start writing the text. Click and drag +on the border to move it. You can resize the text box by dragging on the +handles or by using the Resize dialog. + + + +If you have deselected a text box you can use Undo to edit the text +again. + + + +Using the Transparent Color + + + + + + + + +The left picture shows the example image. The right picture shows the addition of text +with opaque foreground and background colors. + + + + + + + + + + +The left picture shows the addition of text with opaque foreground +colors and a transparent background color. The right picture shows the +addition of text with a transparent foreground color and opaque background +color. + + + + +Common Tool Options + + + + + + + + + + + + + +Click on one of the squares to select the eraser size. You can select +from squares of side length 2, 3, 5, 9, 17 and 29 pixels. + +The eraser size setting affects the Erasers. + + + + + + + + + + + +Click on one of the lines to select the line width. You can select +from line widths of 1, 2, 3, 5 and 8 pixel(s). + +The line width setting affects the Connected Lines, Curve, Ellipse, Line, Polygon, Rectangle and Rounded Rectangle tools. + + + + + + + + + + + +Click on one of the rectangles to select the fill style. You can +select from No Fill, Fill with Background Color and Fill with Foreground +Color. The fill style setting affects the Ellipse, Polygon, Rectangle and Rounded Rectangle tools. + + + + + + + + + + + +Working with Color + + +The Color Box + + + + + + + +Color Box + + + + +The Color Box has 3 main sections: the Color Tablet, the Color Palette +and the Color Similarity Selector. + +The Color Tablet shows the current foreground color as a square on top +of another square representing the current background color. When drawing +with the &LMB;, the foreground color is used, and when drawing with the +&RMB; the background color is used (except for the Erasers). You can click on the double-ended +arrow to swap the foreground and background colors. + +The Color Palette shows a selection of colors for you to choose +from. The translucent pyramid represents the transparent color. Left-click +on a color to set the foreground color and right-click on a color to set the +background color. You can also drag and drop any opaque color into the Color +Tablet squares. To edit a color in the Color Tablet or Palette, double-click +on it. The Color Picker tool allows +you to select a color from the image. + +Color Similarity allows you to work more effectively with dithered +images and photos, in a comparable manner to the Magic Wand feature of other paint programs. It applies to transparent selections, as well as the +Flood Fill, Color Eraser and Autocrop / Remove Internal Border tools. Double-click on the Color +Similarity Selector to choose how similar colors must be to be considered +identical. When using selections in Transparent mode, any color in the +selection that is similar to the background color will also be made +transparent. + + + + + + + + + + +The left picture shows the example image. The right pictures demonstrate the use of a flood fill, with Color Similarity settings of 5%, 15% and 30%. In this example, with a Color Similarity setting of Exact, a flood fill at (80, 100) would only fill one pixel, as the surrounding pixels are similar but not identical. As Color Similarity is increased, more pixels that are similar in color are considered identical, hence the fill extends further. + + + + + + +View Options + + +View Options Reference + +Zoom incorporating the Grid +Thumbnail + + + + +Zoom incorporating the Grid +Increase the zoom level to edit images with more precision, or reduce it to see more of the image. + + + +At zoom levels that aren't multiples of 100%, parts of the image may appear to move when the user interacts with it. Other minor redraw glitches may also occur at such zoom levels. + + + +At zoom levels of 400% or greater that are also multiples of 100%, you can Show Grid to more accurately edit individual pixels. + + + + + + + + + + + + + + + +The first picture shows the Text tool icon, while the latter shows it at 600% zoom with the grid on. + + + +Another way of zooming when not drawing is to scroll the wheel while holding &Ctrl;. + + + + + + +Thumbnail + + + + + + + + +If Zoomed Thumbnail Mode is selected, the entire image is displayed, scaled as required to fit the thumbnail window (top-right picture). + + +Otherwise, the thumbnail displays as much of the image as possible, starting from the top-left of the main view (bottom-right picture). + + + + + + +Image Effects + + +Image Effects Reference +Autocrop / Remove Internal Border +Balance +Clear +Emboss +Flatten +Flip (upside down) +Invert +Reduce Colors +Reduce to Grayscale +Reduce to Monochrome (Dithered) +Mirror (horizontally) +Resize / Scale +Rotate +Set as Image (Crop) +Skew +Soften & Sharpen +More Effects +Notes + + + +Autocrop / Remove Internal Border + +This automatically removes the border of an image or selection. Use +Autocrop if you have a figure that does not fill the entire image or selection and you +wish to remove the excess whitespace. To use this feature with a dithered +image border, you will also need to use Color +Similarity. + + + + +Balance + + +This feature is accessible from the More Effects dialog. + + + + + + + + + +This allows you to set the brightness, contrast and gamma of the image or selection. + + + + +The more common measure of gamma (a decimal from 0.10 to 10.00) is located between the +Gamma spinbox and the Reset button. + + + + + +Clear + +This fills the entire image or selection with the background +color. + + +Double-click on the Eraser +icon to clear the entire image. + + + + + +Emboss + + +This feature is accessible from the More Effects dialog. + + + + + + + + + + +Check Enable to apply the Emboss effect. This emphasises the edges and gives the +image or selection an "engraved look". + + + + +Flatten + + +This feature is accessible from the More Effects dialog. + + + + + + + + + + +This recolors the image with varying shades of the two selected colors. + + + + +Flip (upside down) + +This flips the entire image or selection vertically. + + + + +Invert + + +This feature is accessible from the More Effects dialog. + + + + + + + + + +This allows you to invert one or more RGB channels in the image or selection. Select All to change a photo into a negative and vice versa. This generally looks quite funny. + + +To quickly invert all channels, you do not need to use this dialog. You can instead +access the Invert Colors item in the Image or +Selection menu. The Selection item is only displayed +in the menubar if you use one of the selection tools. Additionally you can reach this action +from the context menu opened with a &RMB; click in the image area. + + + + + + +Mirror (horizontally) + +This mirrors the entire image or selection horizontally. + + + + +Reduce Colors + + +This feature is accessible from the More Effects dialog. + + + + + + + + + + +This reduces the number of colors used by the image or selection, with or without dithering. + + + +Dithering generally provides better quality results, however, you may wish to disable it for artistic effects; +⪚ using Monochrome instead of +Monochrome (Dithered) gives a silhouette effect. + + + +Another important distinction is that while Monochrome (Dithered) will always reduce the entire image or selection to black and white, Monochrome will do this only if the image or selection contains more than 2 colors. + + + +For a quick, dithered monochrome image or selection, use the Reduce to Monochrome (Dithered) item of the Image or Selection menu. The Selection item is only displayed +in the menubar if you use one of the selection tools. Additionally you can reach this action +from the context menu opened with a &RMB; click in the image area. + + + + + +Changing the number of colors here has no effect on the color depth of the file format. If you want to +change the color depth, you should select it in the file saving dialogs. Note that, confusingly, changing +the color depth also changes the number of colors. + + + + + +Reduce to Grayscale + + This reduces the entire image or selection to grayscale. + + + + +Reduce to Monochrome (Dithered) + +This reduces the entire image or selection to black and white. + + + +If you do not want the image or selection to be dithered, use the +Reduce Colors dialog. + + + + + +Resize / Scale + + + + + + + + + +Resizing the image changes the dimensions of the image without +applying a transformation to the existing contents. Scaling the image will +stretch the existing contents to the new dimensions. Smooth Scale +generally provides better quality results than Scaling, by blending neighbouring colors. + +You can express the new dimensions in pixels, or as a percentage of +the original size. If you select Keep aspect ratio, the +width and height will be scaled by the same percentage. + + + +You can free-form Resize the entire image or Smooth Scale the selection using the corresponding handles. + + + + +Only scaling is supported for selections, and only resizing is +supported for text boxes. See Notes for +additional details about applying these effects. + + + + +Rotate + + + + + + + + + +This rotates the image. You can specify the angle and direction of +rotation. + + +You can reverse the direction of rotation by specifying a negative +custom angle. + + + +See Notes for details about +applying this effect to a selection. + + + + + +Set as Image (Crop) + +This will set the selection as the image. + + +This is only available when you have an active selection. + + + + +Skew + + + + + + + + + +This skews the entire image or selection horizontally and/or +vertically. + + +See Notes for details about +applying this effect to a selection. + + + + +Soften & Sharpen + + +This feature is accessible from the More Effects dialog. + + + + + + + + + + +Use this effect to soften or sharpen the image. + + + + + +More Effects + + +This dialog contains the Balance, +Emboss, Flatten, +Invert, Reduce Colors +and Soften & Sharpen features. + + + + +Notes + +Resizing / Scaling, Rotating and Skewing may change the dimensions of the +image. You can view the new dimensions in the dialog. + +If you apply these effects to an image, the image will be resized if +necessary. However, if you apply these effects to a selection, the image +will not be resized, even if the transformed selection does not fit. + + + + + + + + + + +The left image has been rotated 30 degrees clockwise to form the right image. &kolourpaint; has +automatically enlarged the image to accommodate the larger contents. + + + + + + + + + + + +The left selection has been rotated 30 degrees clockwise to form the right +selection. The image size has remained the same, hence parts of the selection will not be visible +without Resizing the image. + + + + + + +Credits and License + +Carl Tucker + +It might not be concise documentation; it might not be complete documentation; but it is +honest documentation. + + + + +&kolourpaint; + +Program Copyright © 2003, 2004, 2005 &Clarence.Dang; &Clarence.Dang.mail; + + +&kolourpaint;-specific icons Copyright © 2004, 2005 +Kristof Borrey borrey@kde.org, +Nuno Pinheiro nf.pinheiro@gmail.com, +&Danny.Allen; dannya40uk@yahoo.co.uk + + +Documentation and additional documentation artwork Copyright © 2004, 2005 +&Thurston.Dang; &Thurston.Dang.mail; + +Portions reproduced with permission from . + + + +&underFDL; + +This program is licensed as follows: + +Copyright © 2003, 2004, 2005 &Clarence.Dang; &Clarence.Dang.mail; + + +All rights reserved. + + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +&documentation.index; + + + diff --git a/doc/line_width.png b/doc/line_width.png new file mode 100644 index 0000000000000000000000000000000000000000..3f54ba6479844f3bf86f6635745fce8b3a0fc477 GIT binary patch literal 168 zcmeAS@N?(olHy`uVBq!ia0vp^7C;=p!3-p;|GP;8DbWC*5Z50+e!PGGe&4=*p`oGw z|Nm!T03wDZw;VHpl%J=IV@SoVx7RmvF(_~_1nR#2Z@+V=>lTidci*-z;p==6$ooU! zTgA4+f_yVto*iF(tzGI}xW!*R#m5XqvU5Zy#6Go(U`W^)yVa%UIrkyf-aPfGZh_b4 Q0ZnG`boFyt=akR{0JNY%WB>pF literal 0 HcmV?d00001 diff --git a/doc/lines_30_45_deg.png b/doc/lines_30_45_deg.png new file mode 100644 index 0000000000000000000000000000000000000000..6e245a185664e429cb1f591c18d16603ea51288a GIT binary patch literal 589 zcmV-T0==2|(2Bhv&D(iyL`Xu+V7a4Os@ zC0HmS#0Z6hkwh{$WC>82 zu22#9qX3mDN=0B*P7YFGW2jk-qk^Bs*#2Ho3IgYw&zb282cutwlEOy>{Lt5x>oJ+m z&=JTnarOexl@Y6pKsmhN_SWU!Z6B;XJJp2Kp+GH z(TZ>YfW#}-(HENecYj-c_~SD190UJ)nSbruaV$I;$0o*Pct5|4%#{=KKD=MU!29(W zWP@ZUcc&>6>ts$y$bkt`fr0(Y#9j=(+*gL{eI*R+k;xG$U%UN2yzgCr?ZX7yhk=2f z(nNoZf>b4Z-Rpyaz9kVz`@ZyG;e9_Dd~aMb;i}Isn9(x}jmV44pCt(-L^JbeeD~*- zE(Z}PnAvk=frLnA{(Qe9=I13FohBFr;tHE|e3)VAh*3%m8VQli;NM(HBt$a=pQITC ba$V#X%_$_o;FToK00000NkvXXu0mjfCD{U` literal 0 HcmV?d00001 diff --git a/doc/lines_30_deg.png b/doc/lines_30_deg.png new file mode 100644 index 0000000000000000000000000000000000000000..5b711ef627dba38e3adbaef65219b7539d1d1b32 GIT binary patch literal 462 zcmV;<0WtoGP)@GL>$R0{r|q@ z7}Qrn4C*T(hW@(>Eky*8wjy#!Suq&;?~nBqgCTtK6&o)CR&~2&CIA2c07*qoM6N<$ Ef=z_Vng9R* literal 0 HcmV?d00001 diff --git a/doc/lines_45_deg.png b/doc/lines_45_deg.png new file mode 100644 index 0000000000000000000000000000000000000000..04efa317848208d765e33c816f3c4db28a281dbe GIT binary patch literal 382 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 XEVgIJv$r8n=YfiXrhMg7%i#Y2N@_C4 literal 0 HcmV?d00001 diff --git a/doc/selections_opaque_transparent.png b/doc/selections_opaque_transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..aa1a7e63602f6cf59f247b94819e6b7d6123bfe3 GIT binary patch literal 984 zcmWMleNfW{7=Cf%^BPSv6yf*~TwVt_=!h>JO}@4Q0vs7qNrGACg^ccoz$DP%;7Y>M zGX*EyA$gwg(?|zn%rRU}pb6C&8%`$#92;zYunpw5w>|fK{Pn(nJaMFwdW$yNlBRA497e$@(lT+JPxFi~%&j86~cKlY~X#nCp+R6fzoc8mc9z zjM$91TX8S#cN&OO0QK-SfuCZ9oaYDZoBJR|6A(YQRxI6`&Fb$C3(QLn1eo&8u)*nsjg0zele(QUv9r zF1$7uKiV2&x8es!zr4}d`|FvJm)wnW<6|ucJG;A`*ELp(u|~@g@o1u->=HI3n&JH! zb*EuV`z*Ep?gedh^l8g+k4qN|59oDCiIV=#iORdgzRo#~VbJNUmA4yxelZ(~PC?1H z$EpwUk6z3->`_dfx~$X1B__qm2yySI(I?nfzqWr~3Hjto32BLGVYBT=hlB zws&S4OWtl%%{~86)JGigZE&A|kzTBp?mA{aOYJF9M2PqIWa*`$?5PSU=e^Nf7;+_Fu<+TK;i z@@qBQ9@nut=(gN<*Y9l9W|(=}3`PHs)3vNablW(w-XftsvwN+5d|WOW7A}UqSlGUP zV~C|wb03HT&0&IsO|!z&>cc~>|7FU&uax}NMbGcHHofAkHFdDgUiqy#eCJ}AchefD su7j0#r6M`TE&0v&eQg0J#m?80DIB3;!hUJ=BiA5-o0gs0y|<|De+cIL$p8QV literal 0 HcmV?d00001 diff --git a/doc/spraycan_patterns.png b/doc/spraycan_patterns.png new file mode 100644 index 0000000000000000000000000000000000000000..4d177d290c0e124da0ac69fdf5eccda2f7bf6fe3 GIT binary patch literal 404 zcmV;F0c-w=P)Ek6St!a zqGIx%BMMoE_KZyy70&xT9``4!9Ukutex8H*h?s#oMcNv~_xvbAg8CIGLWI!Op#W3S zZ(J)9p0&mnTquU%l+409(F{^8_aOIGTk-rdc zt{TDlhZT@KK41f0T#rQ9Z?+&@ri!bQCBNp)T7L#Wh6J`@zn;!!1@<>OSkfJR9T^zg78t&m77yezC3(BM zF#K=tKeHdm<1FxqEM{QfI}E~%$MaXD00r4gJbhi+AFxZZ>1%a#|IlM#U}W}maSX}0 z_jZ;yU$X%Z>)tni&mWj%y-n$E!-SJh+J#@To?IhjGxPe2MO$;_t8f0Zbz64JP;crP zudCZiPG8ybV(FyF|Lh$K{sx&{2JUN_bDUT{FF)~m?`-D$@4GDp4!+T3lF8+3;1pzF zTB>pH;4hX$&0XJb+&ox2-P&5DT*{7^Q3h6A6=hO#a{?ViGdC`Xot z2TMgh7GF{RA#=F=!;efCzDvwC>`E0167!TMJe`(bzcs(~WQ=lb$ALrjjG60L|4&%4 zTIs|!c{zUtC6~=Ro&rNiwZt`|BqgyV)hf9t6-Y4{85o-B8kp-E8H5;ESQ(jE8Cq%^ z7+Dz@<>&pIsU6M^-tE2me9s>g-v!{z=h{pNk1PQ4Fx385? zzn{!8n{~buJKw#RJe)i{|Gx`{$Y^{^XKq$y27+W|#-sag)o(MZ(;y`VHEADiZ;}iZJDVbwsqy$_{d-;Ui@C0ao9A;a z{&2i|&v6OK|37moyIxAGO#Ykp*Z=SN1G^_GcKtp1O8UO_QmvAUQh^kMk%6I^u7SC(kx7V=p_QS5m5HggfsvJg!QpL{RVW&A^HVa@DsgMr T^!ng2paup{S3j3^P6<>&pIsU6M^-tE2me9#H7Dr;B5V#`)v~i9H9-UpOx? z+d??(>dF6;HgFmz8BYKHU%Bmn&!^dT!ik^$2eC1-9sIBAGUJWT}Ncv%j@*b|VP1FPzzM((YH!^9O8ye&=s_sw6jA^7llSf5zV)7PhVDZ&nui&eIHJ zI~nziaLh=;Z=>{`}a8MM_=^ENR@B9_b=rs<4G^jN_&&` z<7mO8hq{M9|JVNa;{We&`#;B@Wc5E+cptHiCLF!RPdpaup{S3j3^P6<>&pIsU6M^-tE2me9#H7Ar;B5V#`&|C6!{JrNVGlN z?o+^cV4=co39jCkj2`(11uuxSJ0ula7@SG6Vt0QrK|)7ef=NQwAnm8`pZ@0l zZ||l3_TJujli5nniN2~~Rd`nFA|M5J~@7&XN*W$+AX{HMWPR?DW$gS||?OV&^jV}9MZGX1Y zxZagrA$$32ndgz*^^8rbr3|xP-pN$Hmh^4ugvXC<_H2Eab~fJc#bu?$IdMOy?_|_T z46Eo<>&pIsU6M^-tE2me9#Ck#r;B5V#`&w0Hu5$b2)O=t z&5U4ldCs|sRVdwml4``^=m$#COO`Bm%5-*aEE4h5;d)X$Gv?`|pEv)1X`C-{3Frl& z`=2Ktx%y~{qOz*Mi`s`5?u5PQPz}&`HqegKA9|isg4HvAA-!tpW?Tb2oPInr(%&%mxRPu3a%ZWIZbm@V=gIL1Mtx>MU z0l&95??3yi`6k0Weq+`JKF=osovvEq8c~vxSdwa$T$Bo=7>o=I&2$aSb&X6z49u*I s4Xlh!wGE7{3=9@$%{qpnAvZrIGp!Q0hD+v3RX`04p00i_>zopr0GYa*9{>OV literal 0 HcmV?d00001 diff --git a/doc/tool_ellipse.png b/doc/tool_ellipse.png new file mode 100644 index 0000000000000000000000000000000000000000..10a20450499fc7926c82ccf323d5ed433409c8b4 GIT binary patch literal 489 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H0wnYHF4+L2SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+ueoXe|!I#{XiaP zfk$L90|Vb-5N14{zaj-F$X?><>&pIsU6M^-tE2me9#H6xr;B5V#`)v~iDwOhQ=f}} zvY&HshU1*Z#)qnhBacm;ea+y{@l`j@zpvLm%zn)B|GNMC4j(wM>d=y(_EQ_@Z>+kk z#-sh`RE_-4TZ_AYS^Tj5rtKV__5Al8f7#jAf3MWe|9x{I$Iraay@#Ia-q*OFDzL>81w+S0uLVZLUH0{nkYxL%v&3?F0|5 rD`RtQ10yQ~gWKj0E~042%}>cptHiBgRaQhUPy>UftDnm{r-UW|59PS2 literal 0 HcmV?d00001 diff --git a/doc/tool_elliptical_selection.png b/doc/tool_elliptical_selection.png new file mode 100644 index 0000000000000000000000000000000000000000..0491e16d754c2cb1887cf117862e8f5865f58631 GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H0wnYHF4+L2SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+ueoXe|!I#{XiaP zfk$L90|Vb-5N14{zaj-F$X?><>&pIsU6M^-tE2me9s>iTyr+v}h{pM|lQw!WI|{V@ z?>rcCy_I8GxB4%(ut%beBJX0neJb6>kwB%gq)^N-h1_Dw&}kjETwo$ zmYV7HdIvd+^Sb}uEg8Q;jlbf#`_;#a^UsJ_K9CdNTvIf$T4u@b(>9;(Rwn<{>kpCq z@yUlVYT-Qgb}3yeBZJ4c#Ae=USiT`d?Y!BKx18C*6@j(4B%YY(T)3q6>zG>$v$W6e z4xXB}UAk6Rp4!O%edNu^aP`T;PiYt5zxIoj)t=*Y`Reb(uemqunRR=bbI1#eob;I3 zlIc5kOwlUkDpXw77g9KJP5ayXEx*-&F=XwI=i!YJdH@VP)e_f;l9a@fRIB8oR3OD* zWMF8fYhbQxWD;UzXk}<=WniprU}R-raKkdQ07XM?eoAIqC2kEXQ!btXYGCkm^>bP0 Hl+XkKV86-M literal 0 HcmV?d00001 diff --git a/doc/tool_eraser.png b/doc/tool_eraser.png new file mode 100644 index 0000000000000000000000000000000000000000..8efa08d7791c0a5667e90fd516510a3f611764a7 GIT binary patch literal 398 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H0wnYHF4+L2SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+ueoXe|!I#{XiaP zfk$L90|Vb-5N14{zaj-F$X?><>&pIsU6M^-tE2me9#E*!)5S4F3$+cbvPV?YFJ*@8ct#^Un4xs%3WqV|6)P$e6!mf2ZeU{iThp>y%_r5=Aa=GN)LA3=or-!*NBpo#FA92XDTQ#5E^pxQ!>*kacdA>sOtvQz~JfX=d#Wzp$PzknuCA< literal 0 HcmV?d00001 diff --git a/doc/tool_flood_fill.png b/doc/tool_flood_fill.png new file mode 100644 index 0000000000000000000000000000000000000000..bacf06ff27a6af13dc6d52b291c3486f383441fb GIT binary patch literal 534 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H0wnYHF4+L2SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+ueoXe|!I#{XiaP zfk$L90|Vb-5N14{zaj-F$X?><>&pIsU6M^-tE2me9s>g-r>Bc!h{pM|6E}J>I|{Vz z-)ns-R#{`wf~gme9Xsa~U~Oux@PngC;fUKBS!r%Z9i6kmF>($EAFVQ(r@4h^hWG|9 zmTOLY@AI0c`~A;*{{P$jn~^`njjlGuE;_8HdHk;9Rqj2nWhc)Jo&3?jRoi-!gvvJM z`=2LE&*@g1Z&G9N$@?9LvPD*6;Pw8p#6J<%>sQp?oy}@7XLIev`xoCxuRZGe_B4Og zk=)&zcmvB16;HV_?~Y7PCWAp-8r!$oceaMD;-B+ex5W;A7ncGPRU_LbiRLf+gmLz-!r>**ZO;(eELO3S6fH!)0dMkj)PsvQH#H}INeZC)11B0ilpUXO@geCwP2f{A^ literal 0 HcmV?d00001 diff --git a/doc/tool_free_form_selection.png b/doc/tool_free_form_selection.png new file mode 100644 index 0000000000000000000000000000000000000000..567305c360fcf521e5030f9ca2c91bc7511320fe GIT binary patch literal 580 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H0wnYHF4+L2SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+ueoXe|!I#{XiaP zfk$L90|Vb-5N14{zaj-F$X?><>&pIsU6M^-tE2me9s>iTxu=U`h{pNk1PPG_!I_T_ z99n(u$K=GX^{Q;;Z0c;i|E>40vH8HhI>~&v?bGH2Z{|)~Yd$e@Q zUf)mB_~!kqzW4E|SNbc&+CSBsCkEX%kv){$6tGX}fAGwCi?mY{BK}T%`K#WOT};F= z-k+T>=H92!FKe9*XD)1>Ej&G`ubgL-MptCa{<>pJe>}H87OK(p-{)~RkQ0ArzY0gU zq|?gzX*<^(tb5G9>=N6u>E|WC-T&2)^6A;{`nmt&J{5ALI)}b`UiIx={ob?R4V8`_ zpIK;prhD&tX}nLMv0BszlLXdXWBzntlC00YDP zlgVt|dj*dGLshlJHKHUXu_Vl&Gc7?@cZn^_qcYa19@85lTg g--tlbkei>9nO2EggHc(+6QBkLPgg&ebxsLQ0KOaITL1t6 literal 0 HcmV?d00001 diff --git a/doc/tool_line.png b/doc/tool_line.png new file mode 100644 index 0000000000000000000000000000000000000000..cae18978dd421e84ca274f3efb9b0afd7cfe2602 GIT binary patch literal 342 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H0wnYHF4+L2SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+ueoXe|!I#{XiaP zfk$L90|Vb-5N14{zaj-F$X?><>&pIsU6M^-tE2me9#F{B)5S4F<9u?0gh<202~79@ z^8mqYNl6I_i5VQTw@ci^L=KEz1X(~W$bZYe5LHOV3N;Ye;=s? zr!YGg28R1vm5f-;%&^VR;K3K n21Zr}2H*07LQyp2=BH$)RpQprZuIIUPy>UftDnm{r-UW|&z4^S literal 0 HcmV?d00001 diff --git a/doc/tool_pen.png b/doc/tool_pen.png new file mode 100644 index 0000000000000000000000000000000000000000..d4bf2b2cb6b23bdeca4a6824c219fc4e54119579 GIT binary patch literal 612 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H0wnYHF4+L2SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+ueoXe|!I#{XiaP zfk$L90|Vb-5N14{zaj-F$X?><>&pIsU6M^-tE2me9s>hoxTlL_h{pNk1c@^TwqIP& z7(7LC^(D17D>ioCW3$;gw7zAhaU^skeMtJ?6yCS+#`e~O|NB2*TRqGAU;6^>bf@53 zryhJiUimwlt*v*uq|%3~)636(pR`<~MONckHS28k#AVxl|NE}pyes<7_f`4I=PrE6 zP51cu{?LWB-=1Ex)aN|*mnYl&`s08%w@*Ac{$bX{-i-~3^Q??7yBym(`?bN_w8l%N zJkkGEHs$;~|G|FFM+xWK%c64lQd?&Incw*E|FZp>I?)nS{++)U)Ot7Eqf1gkVw=$k z!<<_&%!1rc|4jUKfBMBK4>_CE=oEt=6I zbXV<>&pIsU6M^-tE2me9#E*n)5S4F<9u?0gh+#+)6akI z9Jy&nmd;>3_TxUo)w>P1Q`CayIQk#G{#U9a>`(pG{{}Iq41NFgM_ZbiKMv`;+iWrA zF&FQeQ-+Iv+dDBTCrdnD<;yG4m3ZL$|N4~67p6S!7cn_5(B&xdRN~x$b?qAkZ@y*b zVNm>J93*8MAqli!wZt`|BqgyV)hf9t6-Y4{85o-B8kp-EnT8mcSs9yH85(FC7+Dz@ j?3yE#hN2-iKP5A*61Rra2Upz!YGCkm^>bP0l+XkK{2F`K literal 0 HcmV?d00001 diff --git a/doc/tool_polyline.png b/doc/tool_polyline.png new file mode 100644 index 0000000000000000000000000000000000000000..d56bfa57ce7f6530bf2627a8ae14e85d4f4f043c GIT binary patch literal 384 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H0wnYHF4+L2SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+ueoXe|!I#{XiaP zfk$L90|Vb-5N14{zaj-F$X?><>&pIsU6M^-tE2me9#E**)5S4F<9u?0gh+#+)6akI z9Jy&K3)mM*L`qvFC>@pj5>j(>ePhPa`Z@pE9|rX;toWUM=hVg>lG}^c$Q@ZKim#pV*FqFSK`Btx%U_t zR=Sutciowp2ee+b#5JNMC9x#cD!C{XNHG{07@FxCnClvuh8UPx8ChDH7-$<9Ss54{ iA!kQY`6?zK#qG8~eHcB(ehe3dtTp zz6=aiY77hwEes65fI)^mS!_z%I$Auhr50Lk}pF=IP=XqH+H0#e=*J20Y9M zbWSLBY&}tv6A&i@U2*WHXwuQts2#&SpZXqw>e z)E&_W5;@DP3vP+F`|3PaPS4>@YJ9%dZ1GyN{W%*Wi)Oax^gUza&v&WmzE{Nqv{ALh zHKHUXu_Vl&Gc7+6>tTUwbKXd4(=85kIzn>`0bLvDUbW?Cg~ V4Gxo)6@VHTJYD@<);T3K0RY?JbC&=B literal 0 HcmV?d00001 diff --git a/doc/tool_rect_selection.png b/doc/tool_rect_selection.png new file mode 100644 index 0000000000000000000000000000000000000000..94fcbea343fe223907c8e00b63761cb91713cd64 GIT binary patch literal 408 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H0wnYHF4+L2SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+ueoXe|!I#{XiaP zfk$L90|Vb-5N14{zaj-F$X?><>&pIsU6M^-tE2me9#E*;)5S4F<9u?0gw28P``_0m zJ&6=xQkmh~qr;uZJ6keQN9@g^8;rtsp8MJMv!&Q^%Pl?o|FXo9KN4mKO${CfzB>Qp z#D^}9^dF}Gzo&Qo*xpoVbtp!r>!H_mMeU=jB&G+gk^3|M*pm)l%~>Zem?(UEuVA*+ zL`h0wNvc(HQ7VvP zFfuSS(={;HH8Ke?GPE+Zv@$i;HZZa>Fqm*NT?9o#ZhlH;S|x4`4_of90BT_HboFyt I=akR{02l3s`~Uy| literal 0 HcmV?d00001 diff --git a/doc/tool_rectangle.png b/doc/tool_rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..5ebe1ffbdc822878ced1900e3b141ac787378f0e GIT binary patch literal 310 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#=`Bp4pvH+~MJSkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=cu>$S;_Ip=|P53lJ~K z+ueoXe|!I#{XiaPfk$L90|Vb-5N14{zaj-F$X?><>&pIsU6M^-tE2me9#BZy)5S4F z;&O7r0-+DC%m#@jp^Fcxa3}LebTKmAxGKckQ_RQ*RHjo=I z&2$aSb&X6yj0~*|EvyU;v<-}`3=B@~lmNOPNkeXaN@iLmZVkJiEcgi2z~JfX=d#Wz Gp$PyHno#-x literal 0 HcmV?d00001 diff --git a/doc/tool_rectangles.png b/doc/tool_rectangles.png new file mode 100644 index 0000000000000000000000000000000000000000..10ef9e12bd1a677c92bcc0ae8f06a176fd6d82ac GIT binary patch literal 390 zcmeAS@N?(olHy`uVBq!ia0vp^DnKm20VEir8=C$DDVB6cUq=Rpjs4tz5?O(Kg=CK) zUj~LMH3o);76yi2K%s^g3=E|P3=FRl7#OT(FffQ0%-I!a1C(G&@^*J&_}|`tWO>_%)r2R7=#&*=dVZs3bL1Y`ns||V3%am*Xrp0p$8PI^mK6y(Kw&{!Ik;{Mg}&M zz)2#t7F(+%1=iSoWn8|+m~ zG&Igw!o(oY{QY)BiL52i5vnDw5hW>!C8<`)MX5lF!N|bSOxM6%*T^))z{1MJz{=QE o+rY@mz@T7z^H&rNx%nxXX_dG&Fut<(1ZrULboFyt=akR{01`TQhX4Qo literal 0 HcmV?d00001 diff --git a/doc/tool_rounded_rectangle.png b/doc/tool_rounded_rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..ae37b1ed98a0b09224dbfa964a5ca1a19a939991 GIT binary patch literal 493 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H0wnYHF4+L2SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+ueoXe|!I#{XiaP zfk$L90|Vb-5N14{zaj-F$X?><>&pIsU6M^-`+v`g9YCQ6o-U3d8t0P}B%U=0&U~zS zD(T#c(+BvT{`*t^^Zd!T>jgiZF=%7%jx)Wi#CGz(yu^Pemw$zZRmNLaZ}gU}{=lzv zh$;BY?hp3&NW!?GVd&8&W=V*k%C(D-Nnzh1bEziQL}{G>VkVw(HB zC4c;1mcY5ol1JX$eR1pO=N0p~o}J#e{9k;^FV~ zmuGt2e5by5iTn&VogESwPu1!H{i<5x8c~vxSdwa$T$Bo=7>o=I&2$aSb&X6z49u*I sO{`3fwGE7{3=H_~*ceeXmdKI;Vst0Q?}WkpKVy literal 0 HcmV?d00001 diff --git a/doc/tool_selections.png b/doc/tool_selections.png new file mode 100644 index 0000000000000000000000000000000000000000..c5fc040fbb2b851f06bc050eeb4aa6124b9ae32b GIT binary patch literal 631 zcmeAS@N?(olHy`uVBq!ia0vp^_CPGa0VEh&&i!}*q*&4&eH|GXHuiJ>Nn{1`6_P!I zd>I(3)EF2VS{N990fib~Fff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9;eUJonf*W> zXMsm#F#`kNVGw3Kp1&dmD9B#o>Fdh=fL)SJU;BU0i5(0Kj9H#8jv*T7YbP7}A94^m z{+l&hLV+js;@CiCdv4CJxY3XP?)~2X`)=&Jd&v=E2Xaq2S?Qke6;GJP{%oH_YtHXK53ad#h4B;}oBZx) z(%$D5d#fhaADG5-t-QW1c*dPKo{tM`b9o$GE1Wvc&2O!~ew&XgcWsy1f^+O_S(h|~ z+=6&rZ-=Syi-G0~@^<9?_vQs(gO*jO{-5Rjzm0Xkxq!^4049#>6%yo@SLkujeObo3IEVK=b mtPBhabo+o2j-(+sKP5A*61N7%0<#dH1_n=8KbLh*2~7aH<@?qE literal 0 HcmV?d00001 diff --git a/doc/tool_spraycan.png b/doc/tool_spraycan.png new file mode 100644 index 0000000000000000000000000000000000000000..8086cca87ba6a27dfa41b3b2fe8bd8b4d97a7410 GIT binary patch literal 379 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H0wnYHF4+L2SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+ueoXe|!I#{XiaP zfk$L90|Vb-5N14{zaj-F$X?><>&pIsU6M^-`+v`g9YCQxPZ!4!jq}L~5z?X%#P2`NWXg_#ehsoy`(W5YAw{PRN1HV-3%AM+A^Y`e+1?x{U9 zGf;L%qYsY-ql;$Kv6nm{t_GVg&S~`3bh4C7Phb*T;isM?GCAebibswd49w1QOONDF z_yM$8wZt`|BqgyV)hf9t6-Y4{85o-B8kp-EnT8mcSs9yInHp#t7+Dz@SSg=bh@v4k aKP5A*61N8Lx)NES1_n=8KbLh*2~7Z8SaMSU literal 0 HcmV?d00001 diff --git a/doc/tool_text.png b/doc/tool_text.png new file mode 100644 index 0000000000000000000000000000000000000000..95ffa2a58960a051ecc9ccbabc756f21abf593a1 GIT binary patch literal 331 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#=`Bp4pvH+~MJSkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=cu>$S;_Ip=|P53lJ~K z+ueoXe|!I#{XiaPfk$L90|Vb-5N14{zaj-F$X?><>&pIsU6M^-`+v`g9Y7%iPZ!4! ziOb0e3xqy={d)A+N7juG8xD%^&ti)gQJxqPapr(BkD=({364rWa~CWUVPKfTDF68D zV`CnmUeyxUh?11Vl2ohYqEsNoU}RuurfXoXYh)5)WN2k*YGq`uZD3?&U{I33Y66Oe c-29Zxv`X9>SdD~qfEpM)UHx3vIVCg!09q4VKmY&$ literal 0 HcmV?d00001 diff --git a/doc/view_thumbnails.png b/doc/view_thumbnails.png new file mode 100644 index 0000000000000000000000000000000000000000..214d0f877b7d7a7f117b5f5981c92fba05d7d65e GIT binary patch literal 44736 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 Q1OXpaC5;Cq3W%Wp2Ma5E1poj5 literal 0 HcmV?d00001 diff --git a/document/kpDocument.cpp b/document/kpDocument.cpp new file mode 100644 index 0000000..dd3db8d --- /dev/null +++ b/document/kpDocument.cpp @@ -0,0 +1,457 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_DOCUMENT 0 + + +#include "kpDocument.h" +#include "kpDocumentPrivate.h" + +#include "layers/selections/kpAbstractSelection.h" +#include "layers/selections/image/kpAbstractImageSelection.h" +#include "imagelib/kpColor.h" +#include "widgets/toolbars/kpColorToolBar.h" +#include "kpDefs.h" +#include "environments/document/kpDocumentEnvironment.h" +#include "document/kpDocumentSaveOptions.h" +#include "imagelib/kpDocumentMetaInfo.h" +#include "imagelib/effects/kpEffectReduceColors.h" +#include "tools/kpTool.h" +#include "widgets/toolbars/kpToolToolBar.h" +#include "lgpl/generic/kpUrlFormatter.h" + + +#include "kpLogCategories.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +kpDocument::kpDocument (int w, int h, + kpDocumentEnvironment *environ) + : QObject (), + m_constructorWidth (w), m_constructorHeight (h), + m_isFromExistingURL (false), + m_savedAtLeastOnceBefore (false), + m_saveOptions (new kpDocumentSaveOptions ()), + m_metaInfo (new kpDocumentMetaInfo ()), + m_modified (false), + m_selection (nullptr), + m_oldWidth (-1), m_oldHeight (-1), + d (new kpDocumentPrivate ()) +{ +#if DEBUG_KP_DOCUMENT && 0 + qCDebug(kpLogDocument) << "kpDocument::kpDocument (" << w << "," << h << ")"; +#endif + + m_image = new kpImage(w, h, QImage::Format_ARGB32_Premultiplied); + m_image->fill(QColor(Qt::white).rgb()); + + d->environ = environ; +} + +//--------------------------------------------------------------------- + +kpDocument::~kpDocument () +{ + delete d; + + delete m_image; + + delete m_saveOptions; + delete m_metaInfo; + + delete m_selection; +} + +//--------------------------------------------------------------------- + +// public +kpDocumentEnvironment *kpDocument::environ () const +{ + return d->environ; +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::setEnviron (kpDocumentEnvironment *environ) +{ + d->environ = environ; +} + +//--------------------------------------------------------------------- + +// public +bool kpDocument::savedAtLeastOnceBefore () const +{ + return m_savedAtLeastOnceBefore; +} + +//--------------------------------------------------------------------- + +// public +QUrl kpDocument::url () const +{ + return m_url; +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::setURL (const QUrl &url, bool isFromExistingURL) +{ + m_url = url; + m_isFromExistingURL = isFromExistingURL; +} + +//--------------------------------------------------------------------- + +// public +bool kpDocument::isFromExistingURL () const +{ + return m_isFromExistingURL; +} + +//--------------------------------------------------------------------- + +// public +bool kpDocument::urlExists (const QUrl &url) const +{ + if (url.isEmpty()) { + return false; + } + KIO::StatJob *job = KIO::statDetails(url, KIO::StatJob::SourceSide, KIO::StatNoDetails); + KJobWidgets::setWindow (job, d->environ->dialogParent ()); + return job->exec(); +} + +//--------------------------------------------------------------------- + +// public +QString kpDocument::prettyUrl () const +{ + return kpUrlFormatter::PrettyUrl (m_url); +} + +//--------------------------------------------------------------------- + +// public +QString kpDocument::prettyFilename () const +{ + return kpUrlFormatter::PrettyFilename (m_url); +} + +//--------------------------------------------------------------------- + +// public +const kpDocumentSaveOptions *kpDocument::saveOptions () const +{ + return m_saveOptions; +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::setSaveOptions (const kpDocumentSaveOptions &saveOptions) +{ + *m_saveOptions = saveOptions; +} + +//--------------------------------------------------------------------- + +// public +const kpDocumentMetaInfo *kpDocument::metaInfo () const +{ + return m_metaInfo; +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::setMetaInfo (const kpDocumentMetaInfo &metaInfo) +{ + *m_metaInfo = metaInfo; +} + +//--------------------------------------------------------------------- + +/* + * Properties + */ + +void kpDocument::setModified (bool yes) +{ + if (yes == m_modified) { + return; + } + + m_modified = yes; + + if (yes) { + emit documentModified (); + } +} + +//--------------------------------------------------------------------- + +bool kpDocument::isModified () const +{ + return m_modified; +} + +//--------------------------------------------------------------------- + +bool kpDocument::isEmpty () const +{ + return url ().isEmpty () && !isModified (); +} + +//--------------------------------------------------------------------- + +int kpDocument::constructorWidth () const +{ + return m_constructorWidth; +} + +//--------------------------------------------------------------------- + +int kpDocument::width (bool ofSelection) const +{ + return (ofSelection && m_selection) ? m_selection->width() : m_image->width(); +} + +//--------------------------------------------------------------------- + +int kpDocument::oldWidth () const +{ + return m_oldWidth; +} + +//--------------------------------------------------------------------- + +void kpDocument::setWidth (int w, const kpColor &backgroundColor) +{ + resize (w, height (), backgroundColor); +} + +//--------------------------------------------------------------------- + +int kpDocument::constructorHeight () const +{ + return m_constructorHeight; +} + +//--------------------------------------------------------------------- + +int kpDocument::height (bool ofSelection) const +{ + return (ofSelection && m_selection) ? m_selection->height() : m_image->height(); +} + +//--------------------------------------------------------------------- + +int kpDocument::oldHeight () const +{ + return m_oldHeight; +} + +//--------------------------------------------------------------------- + +void kpDocument::setHeight (int h, const kpColor &backgroundColor) +{ + resize (width (), h, backgroundColor); +} + +//--------------------------------------------------------------------- + +QRect kpDocument::rect (bool ofSelection) const +{ + return (ofSelection && m_selection) ? m_selection->boundingRect() : m_image->rect(); +} + +//--------------------------------------------------------------------- + +// public +kpImage kpDocument::getImageAt (const QRect &rect) const +{ + return kpPixmapFX::getPixmapAt (*m_image, rect); +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::setImageAt (const kpImage &image, const QPoint &at) +{ +#if DEBUG_KP_DOCUMENT && 0 + qCDebug(kpLogDocument) << "kpDocument::setImageAt (image (w=" + << image.width () + << ",h=" << image.height () + << "), x=" << at.x () + << ",y=" << at.y (); +#endif + + kpPixmapFX::setPixmapAt (m_image, at, image); + slotContentsChanged (QRect (at.x (), at.y (), image.width (), image.height ())); +} + +//--------------------------------------------------------------------- + +// public +kpImage kpDocument::image (bool ofSelection) const +{ + kpImage ret; + + if (ofSelection) + { + kpAbstractImageSelection *imageSel = imageSelection (); + Q_ASSERT (imageSel); + + ret = imageSel->baseImage (); + } + else { + ret = *m_image; + } + + return ret; +} + +//--------------------------------------------------------------------- + +// public +kpImage *kpDocument::imagePointer () const +{ + return m_image; +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::setImage (const kpImage &image) +{ + m_oldWidth = width (); + m_oldHeight = height (); + + *m_image = image; + + if (m_oldWidth == width () && m_oldHeight == height ()) { + slotContentsChanged (image.rect ()); + } + else { + slotSizeChanged (QSize (width (), height ())); + } +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::setImage (bool ofSelection, const kpImage &image) +{ + if (ofSelection) + { + kpAbstractImageSelection *imageSel = imageSelection (); + + // Have to have an image selection in order to set its pixmap. + Q_ASSERT (imageSel); + + imageSel->setBaseImage (image); + } + else { + setImage (image); + } +} + +//--------------------------------------------------------------------- + +void kpDocument::fill (const kpColor &color) +{ +#if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "kpDocument::fill ()"; +#endif + + m_image->fill(color.toQRgb()); + slotContentsChanged (m_image->rect ()); +} + +//--------------------------------------------------------------------- + +void kpDocument::resize (int w, int h, const kpColor &backgroundColor) +{ +#if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "kpDocument::resize (" << w << "," << h << ")"; +#endif + + m_oldWidth = width (); + m_oldHeight = height (); + +#if DEBUG_KP_DOCUMENT && 1 + qCDebug(kpLogDocument) << "\toldWidth=" << m_oldWidth + << " oldHeight=" << m_oldHeight; +#endif + + if (w == m_oldWidth && h == m_oldHeight) { + return; + } + + kpPixmapFX::resize (m_image, w, h, backgroundColor); + + slotSizeChanged (QSize (width (), height ())); +} + +//--------------------------------------------------------------------- + +void kpDocument::slotContentsChanged (const QRect &rect) +{ + setModified (); + emit contentsChanged (rect); +} + +//--------------------------------------------------------------------- + +void kpDocument::slotSizeChanged (const QSize &newSize) +{ + setModified (); + emit sizeChanged (newSize.width(), newSize.height()); + emit sizeChanged (newSize); +} + +//--------------------------------------------------------------------- + + + diff --git a/document/kpDocument.h b/document/kpDocument.h new file mode 100644 index 0000000..543124d --- /dev/null +++ b/document/kpDocument.h @@ -0,0 +1,360 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_DOCUMENT_H +#define KP_DOCUMENT_H + + +#include +#include +#include +#include + +#include "imagelib/kpImage.h" +#include "pixmapfx/kpPixmapFX.h" +#undef environ + +class QImage; +class QIODevice; +class QPoint; +class QRect; +class QSize; + +class kpColor; +class kpDocumentEnvironment; +class kpDocumentSaveOptions; +class kpDocumentMetaInfo; +class kpAbstractImageSelection; +class kpAbstractSelection; +class kpTextSelection; + + +// REFACTOR: rearrange method order to make sense and reflect kpDocument_*.cpp split. +class kpDocument : public QObject +{ +Q_OBJECT + +public: + // REFACTOR: Hide constructor and have 2 factory methods: + // + // Method 1. Creates a blank document with dimensions x. + // + // Method 2. Calls open(). and (aka constructorWidth() + // and constructorHeight()) need not be specified. + // + // ? + kpDocument (int w, int h, kpDocumentEnvironment *environ); + ~kpDocument () override; + + kpDocumentEnvironment *environ () const; + void setEnviron (kpDocumentEnvironment *environ); + + + // + // File I/O - Open + // + + + static QImage getPixmapFromFile (const QUrl &url, bool suppressDoesntExistDialog, + QWidget *parent, + kpDocumentSaveOptions *saveOptions = nullptr, + kpDocumentMetaInfo *metaInfo = nullptr); + // REFACTOR: fix: open*() should only be called once. + // Create a new kpDocument() if you want to open again. + void openNew (const QUrl &url); + bool open (const QUrl &url, bool newDocSameNameIfNotExist = false); + + static void getDataFromImage(const QImage &image, + kpDocumentSaveOptions &saveOptions, + kpDocumentMetaInfo &metaInfo); + + // + // File I/O - Save + // + + static bool lossyPromptContinue (const QImage &pixmap, + const kpDocumentSaveOptions &saveOptions, + QWidget *parent); + static bool savePixmapToDevice (const QImage &pixmap, + QIODevice *device, + const kpDocumentSaveOptions &saveOptions, + const kpDocumentMetaInfo &metaInfo, + bool lossyPrompt, + QWidget *parent, + bool *userCancelled = nullptr); + static bool savePixmapToFile (const QImage &pixmap, + const QUrl &url, + const kpDocumentSaveOptions &saveOptions, + const kpDocumentMetaInfo &metaInfo, + bool lossyPrompt, + QWidget *parent); + bool save (bool lossyPrompt = false); + bool saveAs (const QUrl &url, + const kpDocumentSaveOptions &saveOptions, + bool lossyPrompt = true); + + + // Returns whether save() or saveAs() have ever been called and returned true + bool savedAtLeastOnceBefore () const; + + QUrl url () const; + void setURL (const QUrl &url, bool isFromExistingURL); + + // Returns whether the document's image was successfully opened from + // or saved to the URL returned by url(). This is not true for a + // new kpDocument and in the case of open() being passed + // "newDocSameNameIfNotExist = true" when the URL doesn't exist. + // + // If this returns true and the kpDocument hasn't been modified, + // this gives a pretty good indication that the image stored at url() + // is equal to image() (unless the something has happened to that url + // outside of KolourPaint). + // + // e.g. If the user types "kolourpaint doesnotexist.png" to start + // KolourPaint, this method will return false. + bool isFromExistingURL () const; + + // Checks whether @p url still exists + bool urlExists (const QUrl &url) const; + + // (will convert: empty Url --> "Untitled") + QString prettyUrl () const; + + // (will convert: empty Url --> "Untitled") + QString prettyFilename () const; + + // (guaranteed to return valid pointer) + + const kpDocumentSaveOptions *saveOptions () const; + void setSaveOptions (const kpDocumentSaveOptions &saveOptions); + + const kpDocumentMetaInfo *metaInfo () const; + void setMetaInfo (const kpDocumentMetaInfo &metaInfo); + + + /* + * Properties (modified, width, height, color depth...) + */ + + void setModified (bool yes = true); + bool isModified () const; + bool isEmpty () const; + + // REFACTOR: Rename to originalWidth()? + int constructorWidth () const; // as passed to the constructor + int width (bool ofSelection = false) const; + int oldWidth () const; // only valid in a slot connected to sizeChanged() + void setWidth (int w, const kpColor &backgroundColor); + + // REFACTOR: Rename to originalHeight()? + int constructorHeight () const; // as passed to the constructor + int height (bool ofSelection = false) const; + int oldHeight () const; // only valid in a slot connected to sizeChanged() + void setHeight (int h, const kpColor &backgroundColor); + + QRect rect (bool ofSelection = false) const; + + + // + // Image access + // + + // Returns a copy of part of the document's image (not including the + // selection). + kpImage getImageAt (const QRect &rect) const; + + void setImageAt (const kpImage &image, const QPoint &at); + + // "image(false)" returns a copy of the document's image, ignoring any + // floating selection. + // + // "image(true)" returns a copy of a floating image selection's base + // image (i.e. before selection transparency is applied), which may be + // null if the image selection is a just a border. + // + // ASSUMPTION: For == true only, an image selection exists. + kpImage image (bool ofSelection = false) const; + kpImage *imagePointer () const; + + void setImage (const kpImage &image); + // ASSUMPTION: If setting the selection's image, the selection must be + // an image selection. + void setImage (bool ofSelection, const kpImage &image); + + + // + // Selections + // + +public: + kpAbstractSelection *selection () const; + kpAbstractImageSelection *imageSelection () const; + kpTextSelection *textSelection () const; + + // Sets the document's selection to the given one and changes to the + // matching selection tool. Tool changes occur in the following situations: + // + // 1. Setting a when a selection tool is not active. + // + // 2. Setting an image when the text tool is active. + // ASSUMPTION: There is no text selection active when calling this + // method (push it onto the document before calling this, + // to avoid this problem). + // + // 3. Setting a text when an image selection tool is active. + // ASSUMPTION: There is no image selection active when calling this + // method (push it onto the document before calling this, + // to avoid this problem). + // + // The justification for the above assumptions are to reduce the complexity + // of this method's implementation -- changing from an image selection tool + // to a text selection tool, or vice-versa, calls the end() method of the + // current tool, which pushes any active selection onto the document. Since + // this method sets the selection, losing the old selection in the middle of + // the method would be tricky to work around. + // + // WARNING: Before calling this, you must ensure that the UI (kpMainWindow) + // has the 's selection transparency or + // for a text selection, its text style, selected. + // TODO: Why can't we change it for them, if we change tool automatically for them already? + void setSelection (const kpAbstractSelection &selection); + + // Returns the base image of the current image selection. If this is + // null (because the selection is still a border), it extracts the + // pixels of the document marked out by the border of the selection. + // + // ASSUMPTION: There is an imageSelection(). + // + // TODO: this always returns base image - need ver that applies selection + // transparency. + kpImage getSelectedBaseImage () const; + + // Sets the base image of the current image selection to the pixels + // of the document marked out by the border of the selection. + // + // ASSUMPTION: There is an imageSelection() that is just a border + // (no base image). + void imageSelectionPullFromDocument (const kpColor &backgroundColor); + + // Deletes the current selection, if there is a selection(), else NOP + void selectionDelete (); + + // Stamps a copy of the selection onto the document. + // + // For image selections, set to true, means that + // the transparent image of the selection is used. If set to false, + // the base image of the selection is used. This argument is ignored + // for non-image selections. + // + // ASSUMPTION: There is a selection() with content, else NOP + void selectionCopyOntoDocument (bool applySelTransparency = true); + + // Same as selectionCopyOntoDocument() but deletes the selection + // afterwards. + void selectionPushOntoDocument (bool applySelTransparency = true); + + // + // Same as image() but returns a _copy_ of the document image + // + any (even non-image) selection pasted on top. + // + // Even if the selection has no content, it is still pasted: + // + // 1. For an image selection, this makes no difference. + // + // 2. For a text selection: + // + // a) with an opaque background: the background rectangle is + // included -- this is necessary since the rectangle is visually + // there after all, and the intention of this method is to report + // everything. + // + // b) with a transparent background: this makes no difference. + // + kpImage imageWithSelection () const; + + + /* + * Transformations + * (convenience only - you could achieve the same effect (and more) with + * kpPixmapFX: these functions do not affect the selection) + */ + + void fill (const kpColor &color); + void resize (int w, int h, const kpColor &backgroundColor); + + +public slots: + // these will emit signals! + void slotContentsChanged (const QRect &rect); + void slotSizeChanged (const QSize &newSize); + +signals: + void documentOpened (); + void documentSaved (); + + // Emitted whenever the isModified() flag changes from false to true. + // This is the _only_ signal that may be emitted in addition to the others. + void documentModified (); + + void contentsChanged (const QRect &rect); + void sizeChanged (int newWidth, int newHeight); // see oldWidth(), oldHeight() + void sizeChanged (const QSize &newSize); + + void selectionEnabled (bool on); + + // Emitted when setSelection() is given a selection such that we change + // from a non-text-selection tool to the text selection tool or vice-versa. + // reports whether the new selection is text (and therefore, + // whether we've switched to the text tool). + void selectionIsTextChanged (bool isText); + +private: + int m_constructorWidth, m_constructorHeight; + kpImage *m_image; + + QUrl m_url; + bool m_isFromExistingURL; + bool m_savedAtLeastOnceBefore; + + kpDocumentSaveOptions *m_saveOptions; + kpDocumentMetaInfo *m_metaInfo; + + bool m_modified; + + kpAbstractSelection *m_selection; + + int m_oldWidth, m_oldHeight; + + // There is no need to maintain binary compatibility at this stage. + // The d-pointer is just so that you can experiment without recompiling + // the kitchen sink. + struct kpDocumentPrivate *d; +}; + + +#endif // KP_DOCUMENT_H diff --git a/document/kpDocumentPrivate.h b/document/kpDocumentPrivate.h new file mode 100644 index 0000000..9d42eb4 --- /dev/null +++ b/document/kpDocumentPrivate.h @@ -0,0 +1,47 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpDocumentPrivate_H +#define kpDocumentPrivate_H + + +class kpDocumentEnvironment; + + +struct kpDocumentPrivate +{ + kpDocumentPrivate () + : environ(nullptr) + { + } + + kpDocumentEnvironment *environ; +}; + + +#endif // kpDocumentPrivate_H diff --git a/document/kpDocumentSaveOptions.cpp b/document/kpDocumentSaveOptions.cpp new file mode 100644 index 0000000..808b851 --- /dev/null +++ b/document/kpDocumentSaveOptions.cpp @@ -0,0 +1,532 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_DOCUMENT_SAVE_OPTIONS 0 + + +#include "kpDocumentSaveOptions.h" + +#include "kpDefs.h" +#include "pixmapfx/kpPixmapFX.h" + +#include +#include "kpLogCategories.h" +#include + +#include +#include +#include + +//--------------------------------------------------------------------- + +class kpDocumentSaveOptionsPrivate +{ +public: + QString m_mimeType; + int m_colorDepth{}; + bool m_dither{}; + int m_quality{}; +}; + +//--------------------------------------------------------------------- + +kpDocumentSaveOptions::kpDocumentSaveOptions () + : d (new kpDocumentSaveOptionsPrivate ()) +{ + d->m_mimeType = invalidMimeType (); + d->m_colorDepth = invalidColorDepth (); + d->m_dither = initialDither (); + d->m_quality = invalidQuality (); +} + +//--------------------------------------------------------------------- + +kpDocumentSaveOptions::kpDocumentSaveOptions (const kpDocumentSaveOptions &rhs) + : d (new kpDocumentSaveOptionsPrivate ()) +{ + d->m_mimeType = rhs.mimeType (); + d->m_colorDepth = rhs.colorDepth (); + d->m_dither = rhs.dither (); + d->m_quality = rhs.quality (); +} + +//--------------------------------------------------------------------- + +kpDocumentSaveOptions::kpDocumentSaveOptions (const QString &mimeType, int colorDepth, bool dither, int quality) + : d (new kpDocumentSaveOptionsPrivate ()) +{ + d->m_mimeType = mimeType; + d->m_colorDepth = colorDepth; + d->m_dither = dither; + d->m_quality = quality; +} + +//--------------------------------------------------------------------- + +kpDocumentSaveOptions::~kpDocumentSaveOptions () +{ + delete d; +} + +//--------------------------------------------------------------------- + + +// public +bool kpDocumentSaveOptions::operator== (const kpDocumentSaveOptions &rhs) const +{ + return (mimeType () == rhs.mimeType () && + colorDepth () == rhs.colorDepth () && + dither () == rhs.dither () && + quality () == rhs.quality ()); +} + +//--------------------------------------------------------------------- + +// public +bool kpDocumentSaveOptions::operator!= (const kpDocumentSaveOptions &rhs) const +{ + return !(*this == rhs); +} + +//--------------------------------------------------------------------- + + +// public +kpDocumentSaveOptions &kpDocumentSaveOptions::operator= (const kpDocumentSaveOptions &rhs) +{ + setMimeType (rhs.mimeType ()); + setColorDepth (rhs.colorDepth ()); + setDither (rhs.dither ()); + setQuality (rhs.quality ()); + + return *this; +} + +//--------------------------------------------------------------------- + + +// public +void kpDocumentSaveOptions::printDebug (const QString &prefix) const +{ + const QString usedPrefix = !prefix.isEmpty () ? + prefix + QLatin1String (": ") : + QString(); + + qCDebug(kpLogDocument) << usedPrefix + << "mimeType=" << mimeType () + << " colorDepth=" << colorDepth () + << " dither=" << dither () + << " quality=" << quality (); +} + +//--------------------------------------------------------------------- + + +// public +QString kpDocumentSaveOptions::mimeType () const +{ + return d->m_mimeType; +} + +//--------------------------------------------------------------------- + +// public +void kpDocumentSaveOptions::setMimeType (const QString &mimeType) +{ + Q_ASSERT(mimeType.isEmpty () || mimeType.contains ('/')); + d->m_mimeType = mimeType; +} + +//--------------------------------------------------------------------- + + +// public static +QString kpDocumentSaveOptions::invalidMimeType () +{ + return {}; +} + +//--------------------------------------------------------------------- + +// public static +bool kpDocumentSaveOptions::mimeTypeIsInvalid (const QString &mimeType) +{ + return (mimeType == invalidMimeType ()); +} + +//--------------------------------------------------------------------- + +// public +bool kpDocumentSaveOptions::mimeTypeIsInvalid () const +{ + return mimeTypeIsInvalid (mimeType ()); +} + +//--------------------------------------------------------------------- + + +// public +int kpDocumentSaveOptions::colorDepth () const +{ + return d->m_colorDepth; +} + +// public +void kpDocumentSaveOptions::setColorDepth (int depth) +{ + d->m_colorDepth = depth; +} + + +// public static +int kpDocumentSaveOptions::invalidColorDepth () +{ + return -1; +} + +// public static +bool kpDocumentSaveOptions::colorDepthIsInvalid (int colorDepth) +{ + return (colorDepth != 1 && colorDepth != 8 && colorDepth != 32); +} + +// public +bool kpDocumentSaveOptions::colorDepthIsInvalid () const +{ + return colorDepthIsInvalid (colorDepth ()); +} + + +// public +bool kpDocumentSaveOptions::dither () const +{ + return d->m_dither; +} + +// public +void kpDocumentSaveOptions::setDither (bool dither) +{ + d->m_dither = dither; +} + + +// public static +int kpDocumentSaveOptions::initialDither () +{ + return false; // to avoid accidental double dithering +} + + +// public +int kpDocumentSaveOptions::quality () const +{ + return d->m_quality; +} + +// public +void kpDocumentSaveOptions::setQuality (int quality) +{ + d->m_quality = quality; +} + + +// public static +int kpDocumentSaveOptions::invalidQuality () +{ + return -2; +} + +// public static +bool kpDocumentSaveOptions::qualityIsInvalid (int quality) +{ + return (quality < -1 || quality > 100); +} + +// public +bool kpDocumentSaveOptions::qualityIsInvalid () const +{ + return qualityIsInvalid (quality ()); +} + + +// public static +QString kpDocumentSaveOptions::defaultMimeType (const KConfigGroup &config) +{ + return config.readEntry (kpSettingForcedMimeType, + QStringLiteral ("image/png")); +} + +// public static +void kpDocumentSaveOptions::saveDefaultMimeType (KConfigGroup &config, + const QString &mimeType) +{ + config.writeEntry (kpSettingForcedMimeType, mimeType); +} + + +// public static +int kpDocumentSaveOptions::defaultColorDepth (const KConfigGroup &config) +{ + int colorDepth = + config.readEntry (kpSettingForcedColorDepth, -1); + + if (colorDepthIsInvalid (colorDepth)) + { + // (not screen depth, in case of transparency) + colorDepth = 32; + } + + return colorDepth; +} + +//--------------------------------------------------------------------- + +// public static +void kpDocumentSaveOptions::saveDefaultColorDepth (KConfigGroup &config, int colorDepth) +{ + config.writeEntry (kpSettingForcedColorDepth, colorDepth); +} + +//--------------------------------------------------------------------- + + +// public static +int kpDocumentSaveOptions::defaultDither (const KConfigGroup &config) +{ + return config.readEntry (kpSettingForcedDither, initialDither ()); +} + +//--------------------------------------------------------------------- + +// public static +void kpDocumentSaveOptions::saveDefaultDither (KConfigGroup &config, bool dither) +{ + config.writeEntry (kpSettingForcedDither, dither); +} + +//--------------------------------------------------------------------- + + +// public static +int kpDocumentSaveOptions::defaultQuality (const KConfigGroup &config) +{ + int val = config.readEntry (kpSettingForcedQuality, -1); + + return qualityIsInvalid (val) ? -1 : val; +} + +//--------------------------------------------------------------------- + +// public static +void kpDocumentSaveOptions::saveDefaultQuality (KConfigGroup &config, int quality) +{ + config.writeEntry (kpSettingForcedQuality, quality); +} + +//--------------------------------------------------------------------- + + +// public static +kpDocumentSaveOptions kpDocumentSaveOptions::defaultDocumentSaveOptions (const KConfigGroup &config) +{ + kpDocumentSaveOptions saveOptions; + saveOptions.setMimeType (defaultMimeType (config)); + saveOptions.setColorDepth (defaultColorDepth (config)); + saveOptions.setDither (defaultDither (config)); + saveOptions.setQuality (defaultQuality (config)); + +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS + saveOptions.printDebug ("kpDocumentSaveOptions::defaultDocumentSaveOptions()"); +#endif + + return saveOptions; +} + +//--------------------------------------------------------------------- + +// public static +bool kpDocumentSaveOptions::saveDefaultDifferences (KConfigGroup &config, + const kpDocumentSaveOptions &oldDocInfo, + const kpDocumentSaveOptions &newDocInfo) +{ + bool savedSomething = false; + +#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS + qCDebug(kpLogDocument) << "kpDocumentSaveOptions::saveDefaultDifferences()"; + oldDocInfo.printDebug ("\told"); + newDocInfo.printDebug ("\tnew"); +#endif + + if (newDocInfo.mimeType () != oldDocInfo.mimeType ()) + { + saveDefaultMimeType (config, newDocInfo.mimeType ()); + savedSomething = true; + } + + if (newDocInfo.colorDepth () != oldDocInfo.colorDepth ()) + { + saveDefaultColorDepth (config, newDocInfo.colorDepth ()); + savedSomething = true; + } + + if (newDocInfo.dither () != oldDocInfo.dither ()) + { + saveDefaultDither (config, newDocInfo.dither ()); + savedSomething = true; + } + + if (newDocInfo.quality () != oldDocInfo.quality ()) + { + saveDefaultQuality (config, newDocInfo.quality ()); + savedSomething = true; + } + + return savedSomething; +} + +//--------------------------------------------------------------------- +// Currently, Depth and Quality settings are mutually exclusive with +// Depth overriding Quality. I've currently favoured Quality with the +// below mimetypes (i.e. all lossy mimetypes are only given Quality settings, +// no Depth settings). +// +// To test whether depth is configurable, write an image in the new +// mimetype with all depths and read each one back. See what +// kpDocument thinks the depth is when it gets QImage to read it. + + +// public static +int kpDocumentSaveOptions::mimeTypeMaximumColorDepth(const QString &mimeType) +{ + // SYNC: update mime info here + + if ( mimeType == QStringLiteral("image/x-eps") ) + return 32; // Grayscale actually (unenforced since depth not set to configurable) + + if ( mimeType == QStringLiteral("image/x-portable-bitmap") ) + return 1; + + if ( mimeType == QStringLiteral("image/x-portable-graymap") ) + return 8; // Grayscale actually (unenforced since depth not set to configurable) + + if ( mimeType == QStringLiteral("image/x-xbitmap") ) + return 1; + + return 32; +} + +//--------------------------------------------------------------------- + +// public +int kpDocumentSaveOptions::mimeTypeMaximumColorDepth () const +{ + return mimeTypeMaximumColorDepth (mimeType ()); +} + +//--------------------------------------------------------------------- + +// public static +bool kpDocumentSaveOptions::mimeTypeHasConfigurableColorDepth (const QString &mimeType) +{ + QStringList defaultMimeTypes; + + // SYNC: update mime info here + defaultMimeTypes << QStringLiteral ("image/png"); + defaultMimeTypes << QStringLiteral ("image/bmp"); + defaultMimeTypes << QStringLiteral ("image/x-pcx"); + + // TODO: Only 1, 24 not 8; Qt only sees 32 but "file" cmd realizes + // it's either 1 or 24. + defaultMimeTypes << QStringLiteral ("image/x-rgb"); + + // TODO: Only 8 and 24 - no 1. + defaultMimeTypes << QStringLiteral ("image/x-xpixmap"); + + return defaultMimeTypes.contains(mimeType); +} + +//--------------------------------------------------------------------- + +// public +bool kpDocumentSaveOptions::mimeTypeHasConfigurableColorDepth () const +{ + return mimeTypeHasConfigurableColorDepth (mimeType ()); +} + +//--------------------------------------------------------------------- + +// public static +bool kpDocumentSaveOptions::mimeTypeHasConfigurableQuality (const QString &mimeType) +{ + QStringList defaultMimeTypes; + + // SYNC: update mime info here + defaultMimeTypes << QStringLiteral ("image/jp2"); + defaultMimeTypes << QStringLiteral ("image/jpeg"); + defaultMimeTypes << QStringLiteral ("image/webp"); + + return defaultMimeTypes.contains(mimeType); +} + +//--------------------------------------------------------------------- + +// public +bool kpDocumentSaveOptions::mimeTypeHasConfigurableQuality () const +{ + return mimeTypeHasConfigurableQuality (mimeType ()); +} + +//--------------------------------------------------------------------- + +// public +int kpDocumentSaveOptions::isLossyForSaving (const QImage &image) const +{ + int ret = 0; + + if (mimeTypeMaximumColorDepth () < image.depth ()) + { + ret |= MimeTypeMaximumColorDepthLow; + } + + if (mimeTypeHasConfigurableColorDepth () && + !colorDepthIsInvalid () /*REFACTOR: guarantee it is valid*/ && + ((colorDepth () < image.depth ()) || + (colorDepth () < 32 && image.hasAlphaChannel()))) + { + ret |= ColorDepthLow; + } + + if (mimeTypeHasConfigurableQuality () && + !qualityIsInvalid ()) + { + ret |= Quality; + } + + return ret; +} + +//--------------------------------------------------------------------- diff --git a/document/kpDocumentSaveOptions.h b/document/kpDocumentSaveOptions.h new file mode 100644 index 0000000..a28e14a --- /dev/null +++ b/document/kpDocumentSaveOptions.h @@ -0,0 +1,150 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_DOCUMENT_SAVE_OPTIONS_H +#define KP_DOCUMENT_SAVE_OPTIONS_H + + +class QImage; +class QString; + +class KConfigGroup; + + +class kpDocumentSaveOptions +{ +public: + kpDocumentSaveOptions (); + kpDocumentSaveOptions (const kpDocumentSaveOptions &rhs); + kpDocumentSaveOptions (const QString &mimeType, int colorDepth, bool dither, int quality); + virtual ~kpDocumentSaveOptions (); + + bool operator== (const kpDocumentSaveOptions &rhs) const; + bool operator!= (const kpDocumentSaveOptions &rhs) const; + + kpDocumentSaveOptions &operator= (const kpDocumentSaveOptions &rhs); + + + void printDebug (const QString &prefix) const; + + + QString mimeType () const; + void setMimeType (const QString &mimeType); + + static QString invalidMimeType (); + static bool mimeTypeIsInvalid (const QString &mimeType); + bool mimeTypeIsInvalid () const; + + + int colorDepth () const; + void setColorDepth (int depth); + + static int invalidColorDepth (); + static bool colorDepthIsInvalid (int colorDepth); + bool colorDepthIsInvalid () const; + + + bool dither () const; + void setDither (bool dither); + + static int initialDither (); + + + int quality () const; + void setQuality (int quality); + + static int invalidQuality (); + static bool qualityIsInvalid (int quality); + bool qualityIsInvalid () const; + + + // (All assume that 's group has been set) + // (None of them call KConfigBase::reparseConfig() nor KConfigBase::sync()) + + static QString defaultMimeType (const KConfigGroup &config); + static void saveDefaultMimeType (KConfigGroup &config, const QString &mimeType); + + static int defaultColorDepth (const KConfigGroup &config); + static void saveDefaultColorDepth (KConfigGroup &config, int colorDepth); + + static int defaultDither (const KConfigGroup &config); + static void saveDefaultDither (KConfigGroup &config, bool dither); + + static int defaultQuality (const KConfigGroup &config); + static void saveDefaultQuality (KConfigGroup &config, int quality); + + + static kpDocumentSaveOptions defaultDocumentSaveOptions (const KConfigGroup &config); + // (returns true if it encountered a difference (and saved it to )) + static bool saveDefaultDifferences (KConfigGroup &config, + const kpDocumentSaveOptions &oldDocInfo, + const kpDocumentSaveOptions &newDocInfo); + + +public: + // (purely for informational purposes - not enforced by this class) + static int mimeTypeMaximumColorDepth (const QString &mimeType); + int mimeTypeMaximumColorDepth () const; + + + static bool mimeTypeHasConfigurableColorDepth (const QString &mimeType); + bool mimeTypeHasConfigurableColorDepth () const; + + static bool mimeTypeHasConfigurableQuality (const QString &mimeType); + bool mimeTypeHasConfigurableQuality () const; + + + // TODO: checking for mask loss due to format e.g. BMP + enum LossyType + { + LossLess = 0, + + // mimeTypeMaximumColorDepth() < .depth() + MimeTypeMaximumColorDepthLow = 1, + // i.e. colorDepth() < .depth() || + // colorDepth() < 32 && .mask() + ColorDepthLow = 2, + // i.e. mimeTypeHasConfigurableQuality() + Quality = 4 + }; + + // Returns whether saving with these options will result in + // loss of information. Returned value is the bitwise OR of + // LossType enum possiblities. + int isLossyForSaving (const QImage &image) const; + + +private: + // There is no need to maintain binary compatibility at this stage. + // The d-pointer is just so that you can experiment without recompiling + // the kitchen sink. + class kpDocumentSaveOptionsPrivate *d; +}; + + +#endif // KP_DOCUMENT_SAVE_OPTIONS_H diff --git a/document/kpDocument_Open.cpp b/document/kpDocument_Open.cpp new file mode 100644 index 0000000..44af1e3 --- /dev/null +++ b/document/kpDocument_Open.cpp @@ -0,0 +1,254 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_DOCUMENT 0 + + +#include "kpDocument.h" +#include "kpDocumentPrivate.h" + +#include "imagelib/kpColor.h" +#include "widgets/toolbars/kpColorToolBar.h" +#include "kpDefs.h" +#include "environments/document/kpDocumentEnvironment.h" +#include "document/kpDocumentSaveOptions.h" +#include "imagelib/kpDocumentMetaInfo.h" +#include "imagelib/effects/kpEffectReduceColors.h" +#include "pixmapfx/kpPixmapFX.h" +#include "tools/kpTool.h" +#include "lgpl/generic/kpUrlFormatter.h" +#include "views/manager/kpViewManager.h" + + +#include +#include +#include +#include +#include + +#include +#include "kpLogCategories.h" +#include +#include +#include + +//--------------------------------------------------------------------- + +void kpDocument::getDataFromImage(const QImage &image, + kpDocumentSaveOptions &saveOptions, + kpDocumentMetaInfo &metaInfo) +{ + saveOptions.setColorDepth(image.depth()); + saveOptions.setDither(false); // avoid double dithering when saving + + metaInfo.setDotsPerMeterX(image.dotsPerMeterX()); + metaInfo.setDotsPerMeterY(image.dotsPerMeterY()); + metaInfo.setOffset(image.offset()); + + QStringList keys = image.textKeys(); + for (int i = 0; i < keys.count(); i++) { + metaInfo.setText(keys[i], image.text(keys[i])); + } +} + +//--------------------------------------------------------------------- + +// public static +QImage kpDocument::getPixmapFromFile(const QUrl &url, bool suppressDoesntExistDialog, + QWidget *parent, + kpDocumentSaveOptions *saveOptions, + kpDocumentMetaInfo *metaInfo) +{ +#if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "kpDocument::getPixmapFromFile(" << url << "," << parent << ")"; +#endif + + if (saveOptions) { + *saveOptions = kpDocumentSaveOptions (); + } + + if (metaInfo) { + *metaInfo = kpDocumentMetaInfo (); + } + + if (url.isEmpty ()) { + return {}; + } + + KIO::StoredTransferJob *job = KIO::storedGet (url); + KJobWidgets::setWindow(job, parent); + + if (!job->exec()) + { + if (!suppressDoesntExistDialog) + { + // TODO: Use "Cannot" instead of "Could not" in all dialogs in KolourPaint. + // Or at least choose one consistently. + // + // TODO: Have captions for all dialogs in KolourPaint. + KMessageBox::sorry (parent, + i18n ("Could not open \"%1\".", + kpUrlFormatter::PrettyFilename (url))); + } + + return {}; + } + QByteArray data = job->data(); + + QMimeDatabase db; + QMimeType mimeType = db.mimeTypeForFileNameAndData(url.fileName(), data); + + if (saveOptions) { + saveOptions->setMimeType(mimeType.name()); + } + +#if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "\tmimetype=" << mimeType.name(); + qCDebug(kpLogDocument) << "\tsrc=" << url.path (); +#endif + + QBuffer buffer(&data); + buffer.open(QIODevice::ReadOnly); + QImageReader reader(&buffer); + reader.setAutoTransform(true); + reader.setDecideFormatFromContent(true); + + // Do *NOT* convert to + // QImage image = reader.read(); + // this variant is more lenient on errors and we may get something that we would not otherwise + // e.g. image from https://bugs.kde.org/show_bug.cgi?id=441554 + QImage image; + reader.read(&image); + + if (image.isNull ()) + { + KMessageBox::sorry (parent, + i18n ("Could not open \"%1\" - unsupported image format.\n" + "The file may be corrupt.", + kpUrlFormatter::PrettyFilename (url))); + return {}; + } + +#if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "\tpixmap: depth=" << image.depth () + << " hasAlphaChannel=" << image.hasAlphaChannel (); +#endif + + if ( saveOptions && metaInfo ) { + getDataFromImage(image, *saveOptions, *metaInfo); + } + + // make sure we always have Format_ARGB32_Premultiplied as this is the fastest to draw on + // and Qt can not draw onto Format_Indexed8 (Qt-4.7) + if ( image.format() != QImage::Format_ARGB32_Premultiplied ) { + image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); + } + + return image; +} + +//--------------------------------------------------------------------- + +void kpDocument::openNew (const QUrl &url) +{ +#if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "kpDocument::openNew (" << url << ")"; +#endif + + m_image->fill(QColor(Qt::white).rgb()); + + setURL (url, false/*not from url*/); + + *m_saveOptions = kpDocumentSaveOptions (); + + if ( !url.isEmpty() ) + { + // guess the mimetype from url's filename extension. + // + // That way "kolourpaint doesnotexist.bmp" automatically + // selects the BMP file format when the save dialog comes up for + // the first time. + + QMimeDatabase mimeDb; + m_saveOptions->setMimeType(mimeDb.mimeTypeForUrl(url).name()); + } + + *m_metaInfo = kpDocumentMetaInfo (); + m_modified = false; + + emit documentOpened (); +} + +//--------------------------------------------------------------------- + +bool kpDocument::open (const QUrl &url, bool newDocSameNameIfNotExist) +{ +#if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "kpDocument::open (" << url << ")"; +#endif + + kpDocumentSaveOptions newSaveOptions; + kpDocumentMetaInfo newMetaInfo; + QImage newPixmap = kpDocument::getPixmapFromFile (url, + newDocSameNameIfNotExist/*suppress "doesn't exist" dialog*/, + d->environ->dialogParent (), + &newSaveOptions, + &newMetaInfo); + + if (!newPixmap.isNull ()) + { + delete m_image; + m_image = new kpImage (newPixmap); + + setURL (url, true/*is from url*/); + *m_saveOptions = newSaveOptions; + *m_metaInfo = newMetaInfo; + m_modified = false; + + emit documentOpened (); + return true; + } + + if (newDocSameNameIfNotExist) + { + if (urlExists (url)) // not just a permission error? + { + openNew (url); + } + else + { + openNew (QUrl ()); + } + + return true; + } + + return false; + +} + +//--------------------------------------------------------------------- diff --git a/document/kpDocument_Save.cpp b/document/kpDocument_Save.cpp new file mode 100644 index 0000000..40d965e --- /dev/null +++ b/document/kpDocument_Save.cpp @@ -0,0 +1,472 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_DOCUMENT 0 + + +#include "kpDocument.h" +#include "kpDocumentPrivate.h" + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kpLogCategories.h" +#include +#include +#include +#include + +#include "imagelib/kpColor.h" +#include "widgets/toolbars/kpColorToolBar.h" +#include "kpDefs.h" +#include "environments/document/kpDocumentEnvironment.h" +#include "document/kpDocumentSaveOptions.h" +#include "imagelib/kpDocumentMetaInfo.h" +#include "imagelib/effects/kpEffectReduceColors.h" +#include "pixmapfx/kpPixmapFX.h" +#include "tools/kpTool.h" +#include "widgets/toolbars/kpToolToolBar.h" +#include "lgpl/generic/kpUrlFormatter.h" +#include "views/manager/kpViewManager.h" + + +bool kpDocument::save (bool lossyPrompt) +{ +#if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "kpDocument::save(" + << ",lossyPrompt=" << lossyPrompt + << ") url=" << m_url + << " savedAtLeastOnceBefore=" << savedAtLeastOnceBefore (); +#endif + + // TODO: check feels weak + if (m_url.isEmpty () || m_saveOptions->mimeType ().isEmpty ()) + { + KMessageBox::detailedError (d->environ->dialogParent (), + i18n ("Could not save image - insufficient information."), + i18n ("URL: %1\n" + "Mimetype: %2", + prettyUrl (), + m_saveOptions->mimeType ().isEmpty () ? + i18n ("") : + m_saveOptions->mimeType ()), + i18nc ("@title:window", "Internal Error")); + return false; + } + + return saveAs (m_url, *m_saveOptions, + lossyPrompt); +} + +//--------------------------------------------------------------------- + +// public static +bool kpDocument::lossyPromptContinue (const QImage &pixmap, + const kpDocumentSaveOptions &saveOptions, + QWidget *parent) +{ +#if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "kpDocument::lossyPromptContinue()"; +#endif + +#define QUIT_IF_CANCEL(messageBoxCommand) \ +{ \ + if (messageBoxCommand != KMessageBox::Continue) \ + { \ + return false; \ + } \ +} + + const int lossyType = saveOptions.isLossyForSaving (pixmap); + if (lossyType & (kpDocumentSaveOptions::MimeTypeMaximumColorDepthLow | + kpDocumentSaveOptions::Quality)) + { + QMimeDatabase db; + + QUIT_IF_CANCEL ( + KMessageBox::warningContinueCancel (parent, + i18n ("

The %1 format may not be able" + " to preserve all of the image's color information.

" + + "

Are you sure you want to save in this format?

", + db.mimeTypeForName(saveOptions.mimeType()).comment()), + // TODO: caption misleading for lossless formats that have + // low maximum colour depth + i18nc ("@title:window", "Lossy File Format"), + KStandardGuiItem::save (), + KStandardGuiItem::cancel(), + QLatin1String ("SaveInLossyMimeTypeDontAskAgain"))); + } + else if (lossyType & kpDocumentSaveOptions::ColorDepthLow) + { + QUIT_IF_CANCEL ( + KMessageBox::warningContinueCancel (parent, + i18n ("

Saving the image at the low color depth of %1-bit" + " may result in the loss of color information." + + // TODO: It looks like 8-bit QImage's now support alpha. + // Update kpDocumentSaveOptions::isLossyForSaving() + // and change "might" to "will". + " Any transparency might also be removed.

" + + "

Are you sure you want to save at this color depth?

", + saveOptions.colorDepth ()), + i18nc ("@title:window", "Low Color Depth"), + KStandardGuiItem::save (), + KStandardGuiItem::cancel(), + QLatin1String ("SaveAtLowColorDepthDontAskAgain"))); + } +#undef QUIT_IF_CANCEL + + return true; +} + +//--------------------------------------------------------------------- + +// public static +bool kpDocument::savePixmapToDevice (const QImage &image, + QIODevice *device, + const kpDocumentSaveOptions &saveOptions, + const kpDocumentMetaInfo &metaInfo, + bool lossyPrompt, + QWidget *parent, + bool *userCancelled) +{ + if (userCancelled) + *userCancelled = false; + + QString type = QMimeDatabase().mimeTypeForName (saveOptions.mimeType ()).preferredSuffix (); +#if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "\tmimeType=" << saveOptions.mimeType () + << " type=" << type; +#endif + if (type.isEmpty ()) + return false; + + if (lossyPrompt && !lossyPromptContinue (image, saveOptions, parent)) + { + if (userCancelled) + *userCancelled = true; + + #if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "\treturning false because of lossyPrompt"; + #endif + return false; + } + + + // TODO: fix dup with kpDocumentSaveOptions::isLossyForSaving() + const bool useSaveOptionsColorDepth = + (saveOptions.mimeTypeHasConfigurableColorDepth () && + !saveOptions.colorDepthIsInvalid ()); + + const bool useSaveOptionsQuality = + (saveOptions.mimeTypeHasConfigurableQuality () && + !saveOptions.qualityIsInvalid ()); + + + // + // Reduce colors if required + // + +#if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "\tuseSaveOptionsColorDepth=" << useSaveOptionsColorDepth + << "current image depth=" << image.depth () + << "save options depth=" << saveOptions.colorDepth (); +#endif + QImage imageToSave(image); + + if (useSaveOptionsColorDepth && + imageToSave.depth () != saveOptions.colorDepth ()) + { + // TODO: I think this erases the mask! + // + // I suspect this doesn't matter since this is only called to + // reduce color depth and QImage's with depth < 32 don't + // support masks anyway. + // + // Later: I think the mask is preserved for 8-bit since Qt4 + // seems to support it for QImage. + imageToSave = kpEffectReduceColors::convertImageDepth (imageToSave, + saveOptions.colorDepth (), + saveOptions.dither ()); + } + + + // + // Write Meta Info + // + + imageToSave.setDotsPerMeterX (metaInfo.dotsPerMeterX ()); + imageToSave.setDotsPerMeterY (metaInfo.dotsPerMeterY ()); + imageToSave.setOffset (metaInfo.offset ()); + + foreach (const QString &key, metaInfo.textKeys()) + imageToSave.setText(key, metaInfo.text(key)); + + // + // Save at required quality + // + + int quality = -1; // default + + if (useSaveOptionsQuality) + quality = saveOptions.quality(); + +#if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "\tsaving"; +#endif + if (!imageToSave.save (device, type.toLatin1 (), quality)) + { + #if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "\tQImage::save() returned false"; + #endif + return false; + } + + +#if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "\tsave OK"; +#endif + return true; +} + +//--------------------------------------------------------------------- + +static void CouldNotCreateTemporaryFileDialog (QWidget *parent) +{ + KMessageBox::error (parent, + i18n ("Could not save image - unable to create temporary file.")); +} + +//--------------------------------------------------------------------- + +static void CouldNotSaveDialog (const QUrl &url, const QString &error, QWidget *parent) +{ + KMessageBox::error (parent, + i18n ("Could not save as \"%1\": %2", + kpUrlFormatter::PrettyFilename (url), + error)); +} + +//--------------------------------------------------------------------- + +// public static +bool kpDocument::savePixmapToFile (const QImage &pixmap, + const QUrl &url, + const kpDocumentSaveOptions &saveOptions, + const kpDocumentMetaInfo &metaInfo, + bool lossyPrompt, + QWidget *parent) +{ + // TODO: Use KIO::NetAccess:mostLocalURL() for accessing home:/ (and other + // such local URLs) for efficiency and because only local writes + // are atomic. +#if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "kpDocument::savePixmapToFile (" + << url + << ",lossyPrompt=" << lossyPrompt + << ")"; + saveOptions.printDebug (QLatin1String ("\tsaveOptions")); + metaInfo.printDebug (QLatin1String ("\tmetaInfo")); +#endif + + if (lossyPrompt && !lossyPromptContinue (pixmap, saveOptions, parent)) + { + #if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "\treturning false because of lossyPrompt"; + #endif + return false; + } + + + // Local file? + if (url.isLocalFile ()) + { + const QString filename = url.toLocalFile (); + + // sync: All failure exit paths _must_ call QSaveFile::cancelWriting() or + // else, the QSaveFile destructor will overwrite the file, + // , despite the failure. + QSaveFile atomicFileWriter (filename); + { + if (!atomicFileWriter.open (QIODevice::WriteOnly)) + { + // We probably don't need this as has not been + // opened. + atomicFileWriter.cancelWriting (); + + #if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "\treturning false because could not open QSaveFile" + << " error=" << atomicFileWriter.error () << endl; + #endif + ::CouldNotCreateTemporaryFileDialog (parent); + return false; + } + + // Write to local temporary file. + if (!savePixmapToDevice (pixmap, &atomicFileWriter, + saveOptions, metaInfo, + false/*no lossy prompt*/, + parent)) + { + atomicFileWriter.cancelWriting (); + + #if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "\treturning false because could not save pixmap to device" + << endl; + #endif + ::CouldNotSaveDialog (url, i18n("Error saving image"), parent); + return false; + } + + // Atomically overwrite local file with the temporary file + // we saved to. + if (!atomicFileWriter.commit ()) + { + atomicFileWriter.cancelWriting (); + + #if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "\tcould not close QSaveFile"; + #endif + ::CouldNotSaveDialog (url, atomicFileWriter.errorString(), parent); + return false; + } + } // sync QSaveFile.cancelWriting() + } + // Remote file? + else + { + // Create temporary file that is deleted when the variable goes + // out of scope. + QTemporaryFile tempFile; + if (!tempFile.open ()) + { + #if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "\treturning false because could not open tempFile"; + #endif + ::CouldNotCreateTemporaryFileDialog (parent); + return false; + } + + // Write to local temporary file. + if (!savePixmapToDevice (pixmap, &tempFile, + saveOptions, metaInfo, + false/*no lossy prompt*/, + parent)) + { + #if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "\treturning false because could not save pixmap to device" + << endl; + #endif + ::CouldNotSaveDialog (url, i18n("Error saving image"), parent); + return false; + } + + // Collect name of temporary file now, as QTemporaryFile::fileName() + // stops working after close() is called. + const QString tempFileName = tempFile.fileName (); + #if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "\ttempFileName='" << tempFileName << "'"; + #endif + Q_ASSERT (!tempFileName.isEmpty ()); + + tempFile.close (); + if (tempFile.error () != QFile::NoError) + { + #if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "\treturning false because could not close"; + #endif + ::CouldNotSaveDialog (url, tempFile.errorString(), parent); + return false; + } + + // Copy local temporary file to overwrite remote. + // It's the kioslave's job to make this atomic (write to .part, then rename .part file) + KIO::FileCopyJob *job = KIO::file_copy (QUrl::fromLocalFile (tempFileName), + url, + -1, + KIO::Overwrite); + KJobWidgets::setWindow (job, parent); + if (!job->exec ()) + { + #if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "\treturning false because could not upload"; + #endif + KMessageBox::error (parent, + i18n ("Could not save image - failed to upload.")); + return false; + } + } + + + return true; +} + +//--------------------------------------------------------------------- + +bool kpDocument::saveAs (const QUrl &url, + const kpDocumentSaveOptions &saveOptions, + bool lossyPrompt) +{ +#if DEBUG_KP_DOCUMENT + qCDebug(kpLogDocument) << "kpDocument::saveAs (" << url << "," + << saveOptions.mimeType () << ")" << endl; +#endif + + if (kpDocument::savePixmapToFile (imageWithSelection (), + url, + saveOptions, *metaInfo (), + lossyPrompt, + d->environ->dialogParent ())) + { + setURL (url, true/*is from url*/); + *m_saveOptions = saveOptions; + m_modified = false; + + m_savedAtLeastOnceBefore = true; + + emit documentSaved (); + return true; + } + + return false; +} + +//--------------------------------------------------------------------- diff --git a/document/kpDocument_Selection.cpp b/document/kpDocument_Selection.cpp new file mode 100644 index 0000000..04ae472 --- /dev/null +++ b/document/kpDocument_Selection.cpp @@ -0,0 +1,350 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_DOCUMENT 0 + + +#include "kpDocument.h" +#include "kpDocumentPrivate.h" + + +#include +#include +#include + +#include "kpLogCategories.h" +#include + +#include "imagelib/kpColor.h" +#include "kpDefs.h" +#include "environments/document/kpDocumentEnvironment.h" +#include "layers/selections/kpAbstractSelection.h" +#include "layers/selections/image/kpAbstractImageSelection.h" +#include "layers/selections/text/kpTextSelection.h" + + +// public +kpAbstractSelection *kpDocument::selection () const +{ + return m_selection; +} + +//--------------------------------------------------------------------- + +// public +kpAbstractImageSelection *kpDocument::imageSelection () const +{ + return dynamic_cast (m_selection); +} + +//--------------------------------------------------------------------- + +// public +kpTextSelection *kpDocument::textSelection () const +{ + return dynamic_cast (m_selection); +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::setSelection (const kpAbstractSelection &selection) +{ +#if DEBUG_KP_DOCUMENT && 1 + qCDebug(kpLogDocument) << "kpDocument::setSelection() sel boundingRect=" + << selection.boundingRect (); +#endif + + d->environ->setQueueViewUpdates (); + { + const bool hadSelection = static_cast (m_selection); + auto *oldSelection = m_selection; + + + // (must be called before giving the document a new selection, to + // avoid a potential mess where switchToCompatibleTool() ends + // the current selection tool, killing the new selection) + bool isTextChanged = false; + d->environ->switchToCompatibleTool (selection, &isTextChanged); + Q_ASSERT (m_selection == oldSelection); + + + m_selection = selection.clone (); + + // There's no need to uninitialize the old selection + // (e.g. call disconnect()) since we delete it later. + connect (m_selection, &kpAbstractSelection::changed, + this, &kpDocument::slotContentsChanged); + + + // + // Now all kpDocument state has been set. + // We can _only_ change the environment after that, as the environment + // may access the document. Exception is above with + // switchToCompatibleTool(). + // + + d->environ->assertMatchingUIState (selection); + + + // + // Now all kpDocument and environment state has been set. + // We can _only_ fire signals after that, as the signal receivers (the + // "wider environment") may access the document and the environment. + // + + #if DEBUG_KP_DOCUMENT && 1 + qCDebug(kpLogDocument) << "\tcheck sel " << (int *) m_selection + << " boundingRect=" << m_selection->boundingRect (); + #endif + if (oldSelection) + { + if (oldSelection->hasContent ()) { + slotContentsChanged (oldSelection->boundingRect ()); + } + else { + emit contentsChanged (oldSelection->boundingRect ()); + } + + delete oldSelection; + oldSelection = nullptr; + } + + if (m_selection->hasContent ()) { + slotContentsChanged (m_selection->boundingRect ()); + } + else { + emit contentsChanged (m_selection->boundingRect ()); + } + + + if (!hadSelection) { + emit selectionEnabled (true); + } + + if (isTextChanged) { + emit selectionIsTextChanged (textSelection ()); + } + } + d->environ->restoreQueueViewUpdates (); + +#if DEBUG_KP_DOCUMENT && 1 + qCDebug(kpLogDocument) << "\tkpDocument::setSelection() ended"; +#endif +} + +//--------------------------------------------------------------------- + +// public +kpImage kpDocument::getSelectedBaseImage () const +{ + auto *imageSel = imageSelection (); + Q_ASSERT (imageSel); + + // Easy if we already have it :) + const auto image = imageSel->baseImage (); + if (!image.isNull ()) { + return image; + } + + + const auto boundingRect = imageSel->boundingRect (); + Q_ASSERT (boundingRect.isValid ()); + + // OPT: This is very slow. Image / More Effects ... calls us twice + // unnecessarily. + return imageSel->givenImageMaskedByShape (getImageAt (boundingRect)); +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::imageSelectionPullFromDocument (const kpColor &backgroundColor) +{ + auto *imageSel = imageSelection (); + Q_ASSERT (imageSel); + + // Should not already have an image or we would not be pulling. + Q_ASSERT (!imageSel->hasContent ()); + + const auto boundingRect = imageSel->boundingRect (); + Q_ASSERT (boundingRect.isValid ()); + + // + // Get selection image from document + // + + auto selectedImage = getSelectedBaseImage (); + + d->environ->setQueueViewUpdates (); + + imageSel->setBaseImage (selectedImage); + + // + // Fill opaque bits of the hole in the document + // + +#if !defined (QT_NO_DEBUG) && !defined (NDEBUG) + if (imageSel->transparency ().isTransparent ()) + { + Q_ASSERT (backgroundColor == imageSel->transparency ().transparentColor ()); + } + else + { + // If this method is begin called by a tool, the assert does not + // have to hold since transparentColor() might not be defined in Opaque + // Mode. + // + // If we were called by a tricky sequence of undo/redo commands, the assert + // does not have to hold for additional reason, which is that + // kpMainWindow::setImageSelectionTransparency() does not have to + // set in Opaque Mode. + // + // In practice, it probably does hold but I wouldn't bet on it. + } +#endif + + kpImage eraseImage(boundingRect.size(), QImage::Format_ARGB32_Premultiplied); + eraseImage.fill(backgroundColor.toQRgb()); + + // only paint the region of the shape of the selection + QPainter painter(m_image); + painter.setClipRegion(imageSel->shapeRegion()); + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.drawImage(boundingRect.topLeft(), eraseImage); + slotContentsChanged(boundingRect); + + d->environ->restoreQueueViewUpdates (); +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::selectionDelete () +{ + if ( !m_selection ) { + return; + } + + const auto boundingRect = m_selection->boundingRect (); + Q_ASSERT (boundingRect.isValid ()); + + const auto selectionHadContent = m_selection->hasContent (); + + delete m_selection; + m_selection = nullptr; + + + // HACK to prevent document from being modified when + // user cancels dragging out a new selection + // REFACTOR: Extract this out into a method. + if (selectionHadContent) { + slotContentsChanged (boundingRect); + } + else { + emit contentsChanged (boundingRect); + } + + emit selectionEnabled (false); +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::selectionCopyOntoDocument (bool applySelTransparency) +{ + // Empty selection, just doing nothing + if ( !m_selection || !m_selection->hasContent() ) { + return; + } + + const QRect boundingRect = m_selection->boundingRect (); + Q_ASSERT (boundingRect.isValid ()); + + if (imageSelection ()) + { + if (applySelTransparency) { + imageSelection ()->paint (m_image, rect ()); + } + else { + imageSelection ()->paintWithBaseImage (m_image, rect ()); + } + } + else + { + // (for antialiasing with background) + m_selection->paint (m_image, rect ()); + } + + slotContentsChanged (boundingRect); +} + +//--------------------------------------------------------------------- + +// public +void kpDocument::selectionPushOntoDocument (bool applySelTransparency) +{ + selectionCopyOntoDocument (applySelTransparency); + selectionDelete (); +} + +//--------------------------------------------------------------------- + +// public +kpImage kpDocument::imageWithSelection () const +{ +#if DEBUG_KP_DOCUMENT && 1 + qCDebug(kpLogDocument) << "kpDocument::imageWithSelection()"; +#endif + + // Have selection? + // + // It need not have any content because e.g. a text box with an opaque + // background, but no content, is still visually there. + if (m_selection) + { + #if DEBUG_KP_DOCUMENT && 1 + qCDebug(kpLogDocument) << "\tselection @ " << m_selection->boundingRect (); + #endif + kpImage output = *m_image; + + // (this is a NOP for image selections without content) + m_selection->paint (&output, rect ()); + + return output; + } + else + { + #if DEBUG_KP_DOCUMENT && 1 + qCDebug(kpLogDocument) << "\tno selection"; + #endif + return *m_image; + } +} + +//--------------------------------------------------------------------- diff --git a/environments/commands/kpCommandEnvironment.cpp b/environments/commands/kpCommandEnvironment.cpp new file mode 100644 index 0000000..940aa65 --- /dev/null +++ b/environments/commands/kpCommandEnvironment.cpp @@ -0,0 +1,102 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "environments/commands/kpCommandEnvironment.h" + +#include "widgets/toolbars/kpColorToolBar.h" +#include "document/kpDocument.h" +#include "mainWindow/kpMainWindow.h" +#include "layers/selections/image/kpImageSelectionTransparency.h" +#include "layers/selections/text/kpTextStyle.h" +#include "tools/kpTool.h" + + +struct kpCommandEnvironmentPrivate +{ +}; + +kpCommandEnvironment::kpCommandEnvironment (kpMainWindow *mainWindow) + : kpEnvironmentBase (mainWindow), + d (new kpCommandEnvironmentPrivate ()) +{ +} + +kpCommandEnvironment::~kpCommandEnvironment () +{ + delete d; +} + + +// public +void kpCommandEnvironment::setColor (int which, const kpColor &color) const +{ + kpColorToolBar *toolBar = mainWindow ()->colorToolBar (); + Q_ASSERT (toolBar); + + toolBar->setColor (which, color); +} + + +// public +void kpCommandEnvironment::somethingBelowTheCursorChanged () const +{ + kpTool *tool = mainWindow ()->tool (); + Q_ASSERT (tool); + + tool->somethingBelowTheCursorChanged (); +} + + +// public +kpImageSelectionTransparency kpCommandEnvironment::imageSelectionTransparency () const +{ + return mainWindow ()->imageSelectionTransparency (); +} + +// public +void kpCommandEnvironment::setImageSelectionTransparency ( + const kpImageSelectionTransparency &transparency, + bool forceColorChange) +{ + mainWindow ()->setImageSelectionTransparency (transparency, forceColorChange); +} + + +// public +kpTextStyle kpCommandEnvironment::textStyle () const +{ + return mainWindow ()->textStyle (); +} + +// public +void kpCommandEnvironment::setTextStyle (const kpTextStyle &textStyle) +{ + mainWindow ()->setTextStyle (textStyle); +} + + diff --git a/environments/commands/kpCommandEnvironment.h b/environments/commands/kpCommandEnvironment.h new file mode 100644 index 0000000..fb40fbd --- /dev/null +++ b/environments/commands/kpCommandEnvironment.h @@ -0,0 +1,79 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpCommandEnvironment_H +#define kpCommandEnvironment_H + + +#include "environments/kpEnvironmentBase.h" + + +class kpMainWindow; +class kpImageSelectionTransparency; +class kpTextStyle; + + +// Facade for kpCommand clients. +class kpCommandEnvironment : public kpEnvironmentBase +{ +Q_OBJECT + +public: + // Note: Our interface must never publicly leak or any other + // classes we are trying to hide as that would defeat the point of + // the facade. + kpCommandEnvironment (kpMainWindow *mainWindow); + ~kpCommandEnvironment () override; + + + void somethingBelowTheCursorChanged () const; + + + // Sets the foreground and background drawing colors in the UI. + void setColor (int which, const kpColor &color) const; + + + // + // Selections + // + + kpImageSelectionTransparency imageSelectionTransparency () const; + void setImageSelectionTransparency (const kpImageSelectionTransparency &transparency, + bool forceColorChange = false); + + kpTextStyle textStyle () const; + void setTextStyle (const kpTextStyle &textStyle); + + +private: + struct kpCommandEnvironmentPrivate * const d; +}; + + +#endif // kpCommandEnvironment_H + diff --git a/environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.cpp b/environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.cpp new file mode 100644 index 0000000..5978d33 --- /dev/null +++ b/environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.cpp @@ -0,0 +1,42 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.h" + +#include "mainWindow/kpMainWindow.h" + + +//--------------------------------------------------------------------- + +kpTransformDialogEnvironment::kpTransformDialogEnvironment(kpMainWindow *mainWindow) + : kpEnvironmentBase(mainWindow) +{ +} + +//--------------------------------------------------------------------- + diff --git a/environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.h b/environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.h new file mode 100644 index 0000000..2b9d5b9 --- /dev/null +++ b/environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.h @@ -0,0 +1,52 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTransformDialogEnvironment_H +#define kpTransformDialogEnvironment_H + + +#include "environments/kpEnvironmentBase.h" + + + + +// Facade for kpTransformDialog* clients. +class kpTransformDialogEnvironment : public kpEnvironmentBase +{ +Q_OBJECT + +public: + // Note: Our interface must never publicly leak or any other + // classes we are trying to hide as that would defeat the point of + // the facade. + kpTransformDialogEnvironment (kpMainWindow *mainWindow); +}; + + +#endif // kpTransformDialogEnvironment_H + diff --git a/environments/document/kpDocumentEnvironment.cpp b/environments/document/kpDocumentEnvironment.cpp new file mode 100644 index 0000000..21ac912 --- /dev/null +++ b/environments/document/kpDocumentEnvironment.cpp @@ -0,0 +1,211 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_DOCUMENT_ENVIRONMENT 0 + + +#include "environments/document/kpDocumentEnvironment.h" + +#include "kpLogCategories.h" + +#include "mainWindow/kpMainWindow.h" +#include "layers/selections/kpAbstractSelection.h" +#include "document/kpDocument.h" +#include "layers/selections/image/kpEllipticalImageSelection.h" +#include "layers/selections/image/kpFreeFormImageSelection.h" +#include "layers/selections/image/kpImageSelectionTransparency.h" +#include "layers/selections/image/kpRectangularImageSelection.h" +#include "layers/selections/text/kpTextSelection.h" +#include "layers/selections/text/kpTextStyle.h" +#if DEBUG_KP_DOCUMENT_ENVIRONMENT + #include "tools/kpTool.h" +#endif +#include "views/manager/kpViewManager.h" + + +struct kpDocumentEnvironmentPrivate +{ +}; + +kpDocumentEnvironment::kpDocumentEnvironment (kpMainWindow *mainWindow) + : kpEnvironmentBase (mainWindow), + d (new kpDocumentEnvironmentPrivate ()) +{ +} + +kpDocumentEnvironment::~kpDocumentEnvironment () +{ + delete d; +} + + +// public +QWidget *kpDocumentEnvironment::dialogParent () const +{ + return mainWindow (); +} + + +static kpViewManager *ViewManager (kpMainWindow *mw) +{ + return mw->viewManager (); +} + +// public +void kpDocumentEnvironment::setQueueViewUpdates () const +{ + ::ViewManager (mainWindow ())->setQueueUpdates (); +} + +// public +void kpDocumentEnvironment::restoreQueueViewUpdates () const +{ + ::ViewManager (mainWindow ())->restoreQueueUpdates (); +} + +//--------------------------------------------------------------------- + +// public +void kpDocumentEnvironment::switchToCompatibleTool (const kpAbstractSelection &selection, + bool *isTextChanged) const +{ +#if DEBUG_KP_DOCUMENT_ENVIRONMENT + qCDebug(kpLogEnvironments) << "kpDocumentEnvironment::switchToCompatibleTool(" + << &selection << ")" + << " mainwindow.tool=" + << (mainWindow ()->tool () ? mainWindow ()->tool ()->objectName () : nullptr) + << " mainWindow.toolIsTextTool=" << mainWindow ()->toolIsTextTool () + << " current selection=" + << document ()->selection () + << " new selection is text=" + << dynamic_cast (&selection); +#endif + + *isTextChanged = (mainWindow ()->toolIsTextTool () != + (dynamic_cast (&selection) != nullptr)); + + // We don't change the Selection Tool if the new selection's + // shape is merely different to the current tool's (e.g. rectangular + // vs elliptical) because: + // + // 1. All image selection tools support editing selections of all the + // different shapes anyway. + // 2. Suppose the user is trying out different drags of selection borders + // and then decides to paste a differently shaped selection before continuing + // to try out different borders. If the pasting were to switch to + // a differently shaped tool, the borders drawn after the paste would + // be using a new shape rather than the shape before the paste. This + // could get irritating so we don't do the switch. + if (!mainWindow ()->toolIsASelectionTool () || *isTextChanged) + { + // See kpDocument::setSelection() APIDoc for this assumption. + Q_ASSERT (!document ()->selection ()); + + // Switch to the appropriately shaped selection tool + // _before_ we change the selection + // (all selection tool's ::end() functions nuke the current selection) + if (dynamic_cast (&selection)) + { + #if DEBUG_KP_DOCUMENT_ENVIRONMENT + qCDebug(kpLogEnvironments) << "\tswitch to rect selection tool"; + #endif + mainWindow ()->slotToolRectSelection (); + } + else if (dynamic_cast (&selection)) + { + #if DEBUG_KP_DOCUMENT_ENVIRONMENT + qCDebug(kpLogEnvironments) << "\tswitch to elliptical selection tool"; + #endif + mainWindow ()->slotToolEllipticalSelection (); + } + else if (dynamic_cast (&selection)) + { + #if DEBUG_KP_DOCUMENT_ENVIRONMENT + qCDebug(kpLogEnvironments) << "\tswitch to free form selection tool"; + #endif + mainWindow ()->slotToolFreeFormSelection (); + } + else if (dynamic_cast (&selection)) + { + #if DEBUG_KP_DOCUMENT_ENVIRONMENT + qCDebug(kpLogEnvironments) << "\tswitch to text selection tool"; + #endif + mainWindow ()->slotToolText (); + } + else { + Q_ASSERT (!"Unknown selection type"); + } + } + +#if DEBUG_KP_DOCUMENT_ENVIRONMENT + qCDebug(kpLogEnvironments) << "kpDocumentEnvironment::switchToCompatibleTool(" << &selection << ") finished"; +#endif +} + +//--------------------------------------------------------------------- + +// public +void kpDocumentEnvironment::assertMatchingUIState (const kpAbstractSelection &selection) const +{ + // Trap and try to recover from bugs. + // TODO: See kpDocument::setSelection() API comment and determine best fix. + const auto *imageSelection = dynamic_cast (&selection); + const auto *textSelection = dynamic_cast (&selection); + + if (imageSelection) + { + if (imageSelection->transparency () != mainWindow ()->imageSelectionTransparency ()) + { + qCCritical(kpLogEnvironments) << "kpDocument::setSelection() sel's transparency differs " + "from mainWindow's transparency - setting mainWindow's transparency " + "to sel"; + + qCCritical(kpLogEnvironments) << "\tisOpaque: sel=" << imageSelection->transparency ().isOpaque () + << " mainWindow=" << mainWindow ()->imageSelectionTransparency ().isOpaque (); + + mainWindow ()->setImageSelectionTransparency (imageSelection->transparency ()); + } + } + else if (textSelection) + { + if (textSelection->textStyle () != mainWindow ()->textStyle ()) + { + qCCritical(kpLogEnvironments) << "kpDocument::setSelection() sel's textStyle differs " + "from mainWindow's textStyle - setting mainWindow's textStyle " + "to sel"; + + mainWindow ()->setTextStyle (textSelection->textStyle ()); + } + } + else + { + Q_ASSERT (!"Unknown selection type"); + } +} + + diff --git a/environments/document/kpDocumentEnvironment.h b/environments/document/kpDocumentEnvironment.h new file mode 100644 index 0000000..34450f3 --- /dev/null +++ b/environments/document/kpDocumentEnvironment.h @@ -0,0 +1,71 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpDocumentEnvironment_H +#define kpDocumentEnvironment_H + + +#include "environments/kpEnvironmentBase.h" + + +class QWidget; + +class kpAbstractSelection; + + +// Facade for kpDocument clients. +class kpDocumentEnvironment : public kpEnvironmentBase +{ +Q_OBJECT + +public: + // Note: Our interface must never publicly leak or any other + // classes we are trying to hide as that would defeat the point of + // the facade. + kpDocumentEnvironment (kpMainWindow *mainWindow); + ~kpDocumentEnvironment () override; + + + QWidget *dialogParent () const; + + void setQueueViewUpdates () const; + void restoreQueueViewUpdates () const; + + void switchToCompatibleTool (const kpAbstractSelection &selection, + // REFACTOR: This is horrible API and terribly named. + bool *isTextChanged) const; + void assertMatchingUIState (const kpAbstractSelection &selection) const; + + +private: + struct kpDocumentEnvironmentPrivate * const d; +}; + + +#endif // kpDocumentEnvironment_H + diff --git a/environments/kpEnvironmentBase.cpp b/environments/kpEnvironmentBase.cpp new file mode 100644 index 0000000..602b78a --- /dev/null +++ b/environments/kpEnvironmentBase.cpp @@ -0,0 +1,120 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "environments/kpEnvironmentBase.h" + +#include "widgets/toolbars/kpColorToolBar.h" +#include "document/kpDocument.h" +#include "mainWindow/kpMainWindow.h" +#include "layers/selections/text/kpTextStyle.h" +#include "tools/kpTool.h" + + +struct kpEnvironmentBasePrivate +{ + kpMainWindow *mainWindow; +}; + +kpEnvironmentBase::kpEnvironmentBase (kpMainWindow *mainWindow) + : QObject (mainWindow), + d (new kpEnvironmentBasePrivate ()) +{ + Q_ASSERT (mainWindow); + + d->mainWindow = mainWindow; +} + +kpEnvironmentBase::~kpEnvironmentBase () +{ + delete d; +} + + +// public +kpDocument *kpEnvironmentBase::document () const +{ + return d->mainWindow->document (); +} + + +// public +kpAbstractSelection *kpEnvironmentBase::selection () const +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + return doc->selection (); +} + +// public +kpAbstractImageSelection *kpEnvironmentBase::imageSelection () const +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + return doc->imageSelection (); +} + +// public +kpTextSelection *kpEnvironmentBase::textSelection () const +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + + return doc->textSelection (); +} + + +// public +kpViewManager *kpEnvironmentBase::viewManager () const +{ + return mainWindow ()->viewManager (); +} + + +// public +kpCommandEnvironment *kpEnvironmentBase::commandEnvironment () const +{ + return mainWindow ()->commandEnvironment (); +} + + +// public +kpColor kpEnvironmentBase::backgroundColor (bool ofSelection) const +{ + return d->mainWindow->backgroundColor (ofSelection); +} + + +// protected +kpMainWindow *kpEnvironmentBase::mainWindow () const +{ + return d->mainWindow; +} + + diff --git a/environments/kpEnvironmentBase.h b/environments/kpEnvironmentBase.h new file mode 100644 index 0000000..e03507a --- /dev/null +++ b/environments/kpEnvironmentBase.h @@ -0,0 +1,99 @@ + +// LOREFACTOR: The files in this folder duplicate too much code. +// Use mixin multiple inheritance to get around this. +// +// LOREFACTOR: Move as much kpMainWindow code as possible into these classes +// to reduce the size of kpMainWindow. But do not split concerns / +// "aspects" in kpMainWindow, with half the code there and half here, +// as that would be confusing. + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEnvironmentBase_H +#define kpEnvironmentBase_H + + +#include + + +class kpAbstractImageSelection; +class kpAbstractSelection; +class kpColor; +class kpCommandEnvironment; +class kpDocument; +class kpMainWindow; +class kpTextSelection; +class kpViewManager; + + +// Abstract facade bridging kpMainWindow and other suppliers (e.g. kpTool, +// kpToolToolBar, kpColorToolBar) to clients. +// +// This decouples as many classes as possible from clients, for maintainability. +// If adding new methods to this class, it is preferable to expose additional +// functionality, rather than expose additional classes. +// +// This class also avoids cramming excessive functionality into kpMainWindow. +// +// If this interface gets too bloated, push down the specialized methods into +// a new class. +class kpEnvironmentBase : public QObject +{ +Q_OBJECT + +// (must derive from) +protected: + // Note: Our interface must never publicly leak or any other + // classes we are trying to hide as that would defeat the point of + // the facade. + kpEnvironmentBase (kpMainWindow *mainWindow); + ~kpEnvironmentBase () override; + +public: + kpDocument *document () const; + + kpAbstractSelection *selection () const; + kpAbstractImageSelection *imageSelection () const; + kpTextSelection *textSelection () const; + + kpViewManager *viewManager () const; + + kpCommandEnvironment *commandEnvironment () const; + + kpColor backgroundColor (bool ofSelection = false) const; + +protected: + kpMainWindow *mainWindow () const; + +private: + struct kpEnvironmentBasePrivate * const d; +}; + + +#endif // kpEnvironmentBase_H + diff --git a/environments/tools/kpToolEnvironment.cpp b/environments/tools/kpToolEnvironment.cpp new file mode 100644 index 0000000..f0346fc --- /dev/null +++ b/environments/tools/kpToolEnvironment.cpp @@ -0,0 +1,217 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "kpToolEnvironment.h" + +#include "imagelib/kpColor.h" +#include "widgets/toolbars/kpColorToolBar.h" +#include "mainWindow/kpMainWindow.h" +#include "widgets/toolbars/kpToolToolBar.h" + +#include + +#include +#include +#include + +//-------------------------------------------------------------------------------- + +bool kpToolEnvironment::drawAntiAliased = true; + +//-------------------------------------------------------------------------------- + +struct kpToolEnvironmentPrivate +{ +}; + +kpToolEnvironment::kpToolEnvironment (kpMainWindow *mainWindow) + : kpEnvironmentBase (mainWindow), + d (new kpToolEnvironmentPrivate ()) +{ +} + +kpToolEnvironment::~kpToolEnvironment () +{ + delete d; +} + + +// public +KActionCollection *kpToolEnvironment::actionCollection () const +{ + return mainWindow ()->actionCollection (); +} + +// public +kpCommandHistory *kpToolEnvironment::commandHistory () const +{ + return mainWindow ()->commandHistory (); +} + + +// public +QActionGroup *kpToolEnvironment::toolsActionGroup () const +{ + return mainWindow ()->toolsActionGroup (); +} + +// public +kpToolToolBar *kpToolEnvironment::toolToolBar () const +{ + return mainWindow ()->toolToolBar (); +} + +// public +void kpToolEnvironment::hideAllToolWidgets () const +{ + toolToolBar ()->hideAllToolWidgets (); +} + +// public +bool kpToolEnvironment::selectPreviousTool () const +{ + kpToolToolBar *tb = toolToolBar (); + + // (don't end up with no tool selected) + if (!tb->previousTool ()) { + return false; + } + + // endInternal() will be called by kpMainWindow (thanks to this line) + // so we won't have the view anymore + // TODO: Update comment. + tb->selectPreviousTool (); + return true; +} + + +static kpColorToolBar *ColorToolBar (kpMainWindow *mw) +{ + return mw->colorToolBar (); +} + +// public +kpColor kpToolEnvironment::color (int which) const +{ + return ::ColorToolBar (mainWindow ())->color (which); +} + +// public +double kpToolEnvironment::colorSimilarity () const +{ + return ::ColorToolBar (mainWindow ())->colorSimilarity (); +} + +// public +int kpToolEnvironment::processedColorSimilarity () const +{ + return ::ColorToolBar (mainWindow ())->processedColorSimilarity (); +} + +// public +kpColor kpToolEnvironment::oldForegroundColor () const +{ + return ::ColorToolBar (mainWindow ())->oldForegroundColor (); +} + +// public +kpColor kpToolEnvironment::oldBackgroundColor () const +{ + return ::ColorToolBar (mainWindow ())->oldBackgroundColor (); +} + +// public +double kpToolEnvironment::oldColorSimilarity () const +{ + return ::ColorToolBar (mainWindow ())->oldColorSimilarity (); +} + + +// public +void kpToolEnvironment::flashColorSimilarityToolBarItem () const +{ + ::ColorToolBar (mainWindow ())->flashColorSimilarityToolBarItem (); +} + + +// public +void kpToolEnvironment::setColor (int which, const kpColor &color) const +{ + kpColorToolBar *toolBar = mainWindow ()->colorToolBar (); + Q_ASSERT (toolBar); + + toolBar->setColor (which, color); +} + + +// public +void kpToolEnvironment::deleteSelection () const +{ + mainWindow ()->slotDelete (); +} + +// public +void kpToolEnvironment::pasteTextAt (const QString &text, const QPoint &point, + bool allowNewTextSelectionPointShift) const +{ + mainWindow ()->pasteTextAt (text, point, allowNewTextSelectionPointShift); +} + + +// public +void kpToolEnvironment::zoomIn (bool centerUnderCursor) const +{ + mainWindow ()->zoomIn (centerUnderCursor); +} + +// public +void kpToolEnvironment::zoomOut (bool centerUnderCursor) const +{ + mainWindow ()->zoomOut (centerUnderCursor); +} + + +// public +void kpToolEnvironment::zoomToRect ( + const QRect &normalizedDocRect, + bool accountForGrips, + bool careAboutWidth, bool careAboutHeight) const +{ + mainWindow ()->zoomToRect ( + normalizedDocRect, + accountForGrips, + careAboutWidth, careAboutHeight); +} + +// public +void kpToolEnvironment::fitToPage () const +{ + mainWindow ()->slotFitToPage (); +} + + diff --git a/environments/tools/kpToolEnvironment.h b/environments/tools/kpToolEnvironment.h new file mode 100644 index 0000000..4bd6348 --- /dev/null +++ b/environments/tools/kpToolEnvironment.h @@ -0,0 +1,163 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolEnvironment_H +#define kpToolEnvironment_H + + +#include "environments/kpEnvironmentBase.h" + + +class QActionGroup; +class QPoint; +class QRect; +class QString; + +class KActionCollection; + +class kpColor; +class kpCommandHistory; +class kpToolToolBar; + + +// Facade for kpTool clients. +class kpToolEnvironment : public kpEnvironmentBase +{ +Q_OBJECT + +public: + // Note: Our interface must never publicly leak or any other + // classes we are trying to hide as that would defeat the point of + // the facade. + kpToolEnvironment (kpMainWindow *mainWindow); + ~kpToolEnvironment () override; + + + KActionCollection *actionCollection () const; + + kpCommandHistory *commandHistory () const; + + QActionGroup *toolsActionGroup () const; + kpToolToolBar *toolToolBar () const; + void hideAllToolWidgets () const; + bool selectPreviousTool () const; + + kpColor color (int which) const; + double colorSimilarity () const; + int processedColorSimilarity () const; + + // (only valid in kpTool::slotForegroundColorChanged()) + kpColor oldForegroundColor () const; + // (only valid in kpTool::slotBackgroundColorChanged()) + kpColor oldBackgroundColor () const; + + // (only valid in kpTool::slotColorSimilarityChanged()) + double oldColorSimilarity () const; + + // Flashes the Color Similarity Tool Bar Item to highlight to the user, + // the existence of the Color Similarity feature. + // + // This should be used in only 3 circumstances: + // + // 1. Tools not acting on the selection but using Color Similarity + // e.g. Color Eraser, Flood Fill, Auto Crop. Note that Auto Crop + // becomes Remove Internal Border when a selection is active but + // the flashing still occurs because the extent of the effect is + // directly dependent on Color Similarity. + // + // 2. A change to selection transparency (background color, color similarity + // percentage, change from opaque to transparent). + // + // It is not used when the transparency is opaque or when changing from + // transparent to opaque, as you're not using Color Similarity in these + // cases. + // + // Similarly, it is not used when there is no image selection or the + // image selection is just a border, as changing the selection + // transparency also does nothing in these cases. + // + // 3. Pulling a selection from the document, when selection transparency + // is transparent. + // + // Except for any pulling phase, it is not used for effects that use + // the "transparent image" of a selection (e.g. smearing a transparent + // selection). This is to minimize distractions and because the flashing + // has already been done by pulling. + // + // This should always be called _before_ an operation related to Color + // Similarity, to indicate that the Color Similarity was applied throughout + // the operation -- not at the end. This aspect is most noticeable for + // the time-consuming Autocrop feature, which may fail with a dialog -- + // it would look wrong to only flash the Color Similarity Tool Bar Item when + // the dialog comes up after the failed operation, as it implies that Color + // Similarity was not used during the operation. Having said all this, + // currently the flashing still happens afterwards because the flashing is + // not done in a separate thread. However, assume that the implementation + // will be fixed later. + // + // It is tempting to simply disable the Color Similarity Tool Bar Item + // if the current tool does not use Color Similarity - this change in + // enabled state would be enough to highlight the existence of Color + // Similarity, without the need for this flashing. However, Auto Crop + // is always available - independent of the current tool - and it uses + // Color Similarity, so we can never disable the Tool Bar Item. + // + // We flash in tools but not commands as else it would be very distracting + // Undo/Redo - this flashing in the tools is distracting enough already :) + void flashColorSimilarityToolBarItem () const; + + void setColor (int which, const kpColor &color) const; + + void deleteSelection () const; + void pasteTextAt (const QString &text, const QPoint &point, + // Allow tiny adjustment of so that mouse + // pointer is not exactly on top of the topLeft of + // any new text selection (so that it doesn't look + // weird by being on top of a resize handle just after + // a paste). + bool allowNewTextSelectionPointShift = false) const; + + void zoomIn (bool centerUnderCursor = false) const; + void zoomOut (bool centerUnderCursor = false) const; + + void zoomToRect (const QRect &normalizedDocRect, + bool accountForGrips, + bool careAboutWidth, bool careAboutHeight) const; + + void fitToPage () const; + + static bool drawAntiAliased; + + +private: + struct kpToolEnvironmentPrivate * const d; +}; + + +#endif // kpToolEnvironment_H + diff --git a/environments/tools/selection/kpToolSelectionEnvironment.cpp b/environments/tools/selection/kpToolSelectionEnvironment.cpp new file mode 100644 index 0000000..4513e9d --- /dev/null +++ b/environments/tools/selection/kpToolSelectionEnvironment.cpp @@ -0,0 +1,96 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "kpToolSelectionEnvironment.h" + +#include "mainWindow/kpMainWindow.h" +#include "layers/selections/image/kpImageSelectionTransparency.h" +#include "layers/selections/text/kpTextStyle.h" + + +struct kpToolSelectionEnvironmentPrivate +{ +}; + +kpToolSelectionEnvironment::kpToolSelectionEnvironment (kpMainWindow *mainWindow) + : kpToolEnvironment (mainWindow), + d (new kpToolSelectionEnvironmentPrivate ()) +{ +} + +kpToolSelectionEnvironment::~kpToolSelectionEnvironment () +{ + delete d; +} + + +// public +kpImageSelectionTransparency kpToolSelectionEnvironment::imageSelectionTransparency () const +{ + return mainWindow ()->imageSelectionTransparency (); +} + +// public +int kpToolSelectionEnvironment::settingImageSelectionTransparency () const +{ + return mainWindow ()->settingImageSelectionTransparency (); +} + + +// public +void kpToolSelectionEnvironment::deselectSelection () const +{ + mainWindow ()->slotDeselect (); +} + +// public +QMenu *kpToolSelectionEnvironment::selectionToolRMBMenu () const +{ + return mainWindow ()->selectionToolRMBMenu (); +} + + +// public +void kpToolSelectionEnvironment::enableTextToolBarActions (bool enable) const +{ + mainWindow ()->enableTextToolBarActions (enable); +} + +// public +kpTextStyle kpToolSelectionEnvironment::textStyle () const +{ + return mainWindow ()->textStyle (); +} + +// public +int kpToolSelectionEnvironment::settingTextStyle () const +{ + return mainWindow ()->settingTextStyle (); +} + + diff --git a/environments/tools/selection/kpToolSelectionEnvironment.h b/environments/tools/selection/kpToolSelectionEnvironment.h new file mode 100644 index 0000000..18ff1c0 --- /dev/null +++ b/environments/tools/selection/kpToolSelectionEnvironment.h @@ -0,0 +1,72 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpToolSelectionEnvironment_H +#define kpToolSelectionEnvironment_H + + +#include "environments/tools/kpToolEnvironment.h" + + +class QMenu; + +class kpImageSelectionTransparency; +class kpTextStyle; + + +// Facade for kpToolSelection clients. +class kpToolSelectionEnvironment : public kpToolEnvironment +{ +Q_OBJECT + +public: + // Note: Our interface must never publicly leak or any other + // classes we are trying to hide as that would defeat the point of + // the facade. + kpToolSelectionEnvironment (kpMainWindow *mainWindow); + ~kpToolSelectionEnvironment () override; + + kpImageSelectionTransparency imageSelectionTransparency () const; + int settingImageSelectionTransparency () const; + + void deselectSelection () const; + + QMenu *selectionToolRMBMenu () const; + + void enableTextToolBarActions (bool enable = true) const; + + kpTextStyle textStyle () const; + int settingTextStyle () const; + +private: + struct kpToolSelectionEnvironmentPrivate * const d; +}; + + +#endif // kpToolSelectionEnvironment_H + diff --git a/gen_cmake_include_dirs b/gen_cmake_include_dirs new file mode 100755 index 0000000..df6260a --- /dev/null +++ b/gen_cmake_include_dirs @@ -0,0 +1,7 @@ +#!/bin/bash +# Recalculates the KolourPaint-specific part of CMakeLists.txt's "include_directories()" + +for f in `find -type d | fgrep -v .git | egrep -v '^\.$' | egrep -v '^\./pics ^\./patches$ ^\./tests$' | cut -c3- | sort` +do + echo '${CMAKE_CURRENT_SOURCE_DIR}/'$f +done diff --git a/gen_cmake_srcs b/gen_cmake_srcs new file mode 100755 index 0000000..db6884e --- /dev/null +++ b/gen_cmake_srcs @@ -0,0 +1,7 @@ +#!/bin/bash +# Recalculates the KolourPaint-specific part of CMakeLists.txt's "set(kolourpaint_SRCS" + +for f in `find -name \*.cpp | cut -c3- | sort` +do + echo '${CMAKE_CURRENT_SOURCE_DIR}/'$f +done diff --git a/generic/kpSetOverrideCursorSaver.cpp b/generic/kpSetOverrideCursorSaver.cpp new file mode 100644 index 0000000..0f7cc9f --- /dev/null +++ b/generic/kpSetOverrideCursorSaver.cpp @@ -0,0 +1,43 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "generic/kpSetOverrideCursorSaver.h" + +#include + + +kpSetOverrideCursorSaver::kpSetOverrideCursorSaver (const QCursor &cursor) +{ + QApplication::setOverrideCursor (cursor); +} + +kpSetOverrideCursorSaver::~kpSetOverrideCursorSaver () +{ + QApplication::restoreOverrideCursor (); +} + diff --git a/generic/kpSetOverrideCursorSaver.h b/generic/kpSetOverrideCursorSaver.h new file mode 100644 index 0000000..cdc0d01 --- /dev/null +++ b/generic/kpSetOverrideCursorSaver.h @@ -0,0 +1,107 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpSetOverrideCursorSaver_H +#define kpSetOverrideCursorSaver_H + + +class QCursor; + + +// +// A less error-prone way of setting the override cursor, compared to +// the QApplication::{set,restore}OverrideCursor() pair. +// +// To use this class, allocate it on the stack with the desired cursor. +// +// This class sets the application's override cursor to on +// construction. It restores the cursor on destruction. +// +// Example Usage 1 - Cursor active during the entire method: +// +// void method () +// { +// kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); +// +// +// +// } // Stack unwinds, calling cursorSaver's destructor, +// // which restores the cursor +// +// Example Usage 2 - Cursor active during part of the method: +// +// void method () +// { +// +// +// { +// kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); +// +// +// +// } // Stack unwinds, calling cursorSaver's destructor, +// // which restores the cursor +// +// // (cursor is restored before this code executes) +// +// } +// +// Note that the kpSetOverrideCursorSaver must be defined as a local +// variable inside -- not outside --- the braces containing the code the +// cursor can be active over, else the destruction and cursor restoration +// might not occur at the right time. In other words, the following usage +// is wrong: +// +// INCORRECT Example Usage 2: +// +// void method () +// { +// +// +// kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); +// // BUG: These braces do nothing to limit the effect of cursorSaver. +// { +// +// } +// +// // BUG: cursorSaver has not yet restored the cursor before this +// // code executes. +// +// +// } // Stack unwinds, calling cursorSaver's destructor, +// // which restores the cursor +// +class kpSetOverrideCursorSaver +{ +public: + kpSetOverrideCursorSaver (const QCursor &cursor); + ~kpSetOverrideCursorSaver (); +}; + + +#endif // kpSetOverrideCursorSaver_H diff --git a/generic/kpWidgetMapper.cpp b/generic/kpWidgetMapper.cpp new file mode 100644 index 0000000..3483665 --- /dev/null +++ b/generic/kpWidgetMapper.cpp @@ -0,0 +1,80 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "kpWidgetMapper.h" + +#include +#include +#include + + +namespace kpWidgetMapper +{ + + +QPoint fromGlobal (const QWidget *widget, const QPoint &point) +{ + if (!widget) { + return point; + } + + return widget->mapFromGlobal (point); +} + +QRect fromGlobal (const QWidget *widget, const QRect &rect) +{ + if (!widget || !rect.isValid ()) { + return rect; + } + + auto topLeft = fromGlobal (widget, rect.topLeft ()); + return {topLeft.x (), topLeft.y (), rect.width (), rect.height ()}; +} + + +QPoint toGlobal (const QWidget *widget, const QPoint &point) +{ + if (!widget) { + return point; + } + + return widget->mapToGlobal (point); +} + +QRect toGlobal (const QWidget *widget, const QRect &rect) +{ + if (!widget || !rect.isValid ()) { + return rect; + } + + auto topLeft = toGlobal (widget, rect.topLeft ()); + return {topLeft.x (), topLeft.y (), rect.width (), rect.height ()}; +} + + +} // namespace kpWidgetMapper diff --git a/generic/kpWidgetMapper.h b/generic/kpWidgetMapper.h new file mode 100644 index 0000000..727f90d --- /dev/null +++ b/generic/kpWidgetMapper.h @@ -0,0 +1,48 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_WIDGET_MAPPER_H +#define KP_WIDGET_MAPPER_H + + +class QWidget; +class QPoint; +class QRect; + + +namespace kpWidgetMapper +{ + QPoint fromGlobal (const QWidget *widget, const QPoint &point); + QRect fromGlobal (const QWidget *widget, const QRect &rect); + + QPoint toGlobal (const QWidget *widget, const QPoint &point); + QRect toGlobal (const QWidget *widget, const QRect &rect); +} + + +#endif // KP_WIDGET_MAPPER_H diff --git a/generic/widgets/kpResizeSignallingLabel.cpp b/generic/widgets/kpResizeSignallingLabel.cpp new file mode 100644 index 0000000..6793bde --- /dev/null +++ b/generic/widgets/kpResizeSignallingLabel.cpp @@ -0,0 +1,65 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_RESIZE_SIGNALLING_LABEL 0 + + +#include "generic/widgets/kpResizeSignallingLabel.h" + +#include + +#include "kpLogCategories.h" + + +kpResizeSignallingLabel::kpResizeSignallingLabel (const QString &string, + QWidget *parent ) + : QLabel (string, parent) +{ +} + +kpResizeSignallingLabel::kpResizeSignallingLabel (QWidget *parent ) + : QLabel (parent) +{ +} + +kpResizeSignallingLabel::~kpResizeSignallingLabel () = default; + + +// protected virtual [base QLabel] +void kpResizeSignallingLabel::resizeEvent (QResizeEvent *e) +{ +#if DEBUG_KP_RESIZE_SIGNALLING_LABEL + qCDebug(kpLogMisc) << "kpResizeSignallingLabel::resizeEvent() newSize=" << e->size () + << " oldSize=" << e->oldSize (); +#endif + QLabel::resizeEvent (e); + + emit resized (); +} + + diff --git a/generic/widgets/kpResizeSignallingLabel.h b/generic/widgets/kpResizeSignallingLabel.h new file mode 100644 index 0000000..a8eb9a3 --- /dev/null +++ b/generic/widgets/kpResizeSignallingLabel.h @@ -0,0 +1,56 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_RESIZE_SIGNALLING_LABEL_H +#define KP_RESIZE_SIGNALLING_LABEL_H + + +#include + + +class QResizeEvent; + + +class kpResizeSignallingLabel : public QLabel +{ +Q_OBJECT + +public: + kpResizeSignallingLabel (const QString &string, QWidget *parent); + kpResizeSignallingLabel (QWidget *parent); + ~kpResizeSignallingLabel () override; + +signals: + void resized (); + +protected: + void resizeEvent (QResizeEvent *e) override; +}; + + +#endif // KP_RESIZE_SIGNALLING_LABEL_H diff --git a/generic/widgets/kpSubWindow.cpp b/generic/widgets/kpSubWindow.cpp new file mode 100644 index 0000000..330d138 --- /dev/null +++ b/generic/widgets/kpSubWindow.cpp @@ -0,0 +1,35 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "kpSubWindow.h" + + +kpSubWindow::kpSubWindow (QWidget *parent) + : QDialog (parent) +{ +} diff --git a/generic/widgets/kpSubWindow.h b/generic/widgets/kpSubWindow.h new file mode 100644 index 0000000..e81e927 --- /dev/null +++ b/generic/widgets/kpSubWindow.h @@ -0,0 +1,57 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpSubWindow_H +#define kpSubWindow_H + + +#include + +// +// A tool window with the following properties, in order of importance: +// +// 1. Stays on top of its parent window, but not on top of any other +// window (this rules out Qt::WindowStaysOnTop) +// 2. Does not auto-hide when its parent window loses focus +// (this rules out Qt::Tool). +// 3. Does not have a taskbar entry. +// 4. TODO: Does not take keyboard focus away from the parent window, +// when it is shown. +// 5. TODO: Is not in the Alt+Tab list +// +// We mean "tool window" in the window system sense. It has nothing to do +// with kpTool so to avoid confusion, we do not name it "kpToolWindow". +// +class kpSubWindow : public QDialog +{ + public: + kpSubWindow(QWidget *parent); +}; + + +#endif // kpSubWindow_H diff --git a/imagelib/effects/blitz.cpp b/imagelib/effects/blitz.cpp new file mode 100644 index 0000000..56e9dcd --- /dev/null +++ b/imagelib/effects/blitz.cpp @@ -0,0 +1,715 @@ +// functions taken from qimageblitz (no longer maintained) + +/* + Copyright (C) 1998, 1999, 2001, 2002, 2004, 2005, 2007 + Daniel M. Duley + (C) 2004 Zack Rusin + (C) 2000 Josef Weidendorfer + (C) 1999 Geert Jansen + (C) 1998, 1999 Christian Tibirna + (C) 1998, 1999 Dirk Mueller + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +/* + Portions of this software were originally based on ImageMagick's + algorithms. ImageMagick is copyrighted under the following conditions: + +Copyright (C) 2003 ImageMagick Studio, a non-profit organization dedicated to +making software imaging solutions freely available. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files ("ImageMagick"), to deal +in ImageMagick without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of ImageMagick, and to permit persons to whom the ImageMagick is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of ImageMagick. + +The software is provided "as is", without warranty of any kind, express or +implied, including but not limited to the warranties of merchantability, +fitness for a particular purpose and noninfringement. In no event shall +ImageMagick Studio be liable for any claim, damages or other liability, +whether in an action of contract, tort or otherwise, arising from, out of or +in connection with ImageMagick or the use or other dealings in ImageMagick. + +Except as contained in this notice, the name of the ImageMagick Studio shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in ImageMagick without prior written authorization from the +ImageMagick Studio. +*/ + +#include "blitz.h" + +#include +#include + +#define M_SQ2PI 2.50662827463100024161235523934010416269302368164062 +#define M_EPSILON 1.0e-6 + +#define CONVOLVE_ACC(weight, pixel) \ + r+=((weight))*(qRed((pixel))); g+=((weight))*(qGreen((pixel))); \ + b+=((weight))*(qBlue((pixel))); + +//-------------------------------------------------------------------------------- + +inline QRgb convertFromPremult(QRgb p) +{ + int alpha = qAlpha(p); + return(!alpha ? 0 : qRgba(255*qRed(p)/alpha, + 255*qGreen(p)/alpha, + 255*qBlue(p)/alpha, + alpha)); +} + +//-------------------------------------------------------------------------------- + +inline QRgb convertToPremult(QRgb p) +{ + unsigned int a = p >> 24; + unsigned int t = (p & 0xff00ff) * a; + t = (t + ((t >> 8) & 0xff00ff) + 0x800080) >> 8; + t &= 0xff00ff; + + p = ((p >> 8) & 0xff) * a; + p = (p + ((p >> 8) & 0xff) + 0x80); + p &= 0xff00; + p |= t | (a << 24); + return(p); +} + +//-------------------------------------------------------------------------------- +// These are used as accumulators + +typedef struct +{ + quint32 red, green, blue, alpha; +} IntegerPixel; + +typedef struct +{ + // Yes, a normal pixel can be used instead but this is easier to read + // and no shifts to get components. + quint8 red, green, blue, alpha; +} CharPixel; + +typedef struct +{ + quint32 red, green, blue, alpha; +} HistogramListItem; + + +bool equalize(QImage &img) +{ + if(img.isNull()) { + return(false); + } + + HistogramListItem *histogram; + IntegerPixel *map; + IntegerPixel intensity, high, low; + CharPixel *equalize_map; + int i, count; + QRgb pixel, *dest; + unsigned char r, g, b; + + if(img.depth() < 32){ + img = img.convertToFormat(img.hasAlphaChannel() ? + QImage::Format_ARGB32 : + QImage::Format_RGB32); + } + count = img.width()*img.height(); + + map = new IntegerPixel[256]; + histogram = new HistogramListItem[256]; + equalize_map = new CharPixel[256]; + + // form histogram + memset(histogram, 0, 256*sizeof(HistogramListItem)); + dest = reinterpret_cast(img.bits()); + + if(img.format() == QImage::Format_ARGB32_Premultiplied){ + for(i=0; i < count; ++i, ++dest){ + pixel = convertFromPremult(*dest); + histogram[qRed(pixel)].red++; + histogram[qGreen(pixel)].green++; + histogram[qBlue(pixel)].blue++; + histogram[qAlpha(pixel)].alpha++; + } + } + else{ + for(i=0; i < count; ++i){ + pixel = *dest++; + histogram[qRed(pixel)].red++; + histogram[qGreen(pixel)].green++; + histogram[qBlue(pixel)].blue++; + histogram[qAlpha(pixel)].alpha++; + } + } + + // integrate the histogram to get the equalization map + memset(&intensity, 0, sizeof(IntegerPixel)); + for(i=0; i < 256; ++i){ + intensity.red += histogram[i].red; + intensity.green += histogram[i].green; + intensity.blue += histogram[i].blue; + map[i] = intensity; + } + + low = map[0]; + high = map[255]; + memset(equalize_map, 0, 256*sizeof(CharPixel)); + for(i=0; i < 256; ++i){ + if(high.red != low.red) { + equalize_map[i].red = static_cast + ((255*(map[i].red-low.red))/(high.red-low.red)); + } + if(high.green != low.green) { + equalize_map[i].green = static_cast + ((255*(map[i].green-low.green))/(high.green-low.green)); + } + if(high.blue != low.blue) { + equalize_map[i].blue = static_cast + ((255*(map[i].blue-low.blue))/(high.blue-low.blue)); + } + } + + // stretch the histogram and write + dest = reinterpret_cast(img.bits()); + if(img.format() == QImage::Format_ARGB32_Premultiplied){ + for(i=0; i < count; ++i, ++dest){ + pixel = convertFromPremult(*dest); + r = static_cast ((low.red != high.red) ? + equalize_map[qRed(pixel)].red : qRed(pixel)); + + g = static_cast ((low.green != high.green) ? + equalize_map[qGreen(pixel)].green : qGreen(pixel)); + + b = static_cast ((low.blue != high.blue) ? + equalize_map[qBlue(pixel)].blue : qBlue(pixel)); + + *dest = convertToPremult(qRgba(r, g, b, qAlpha(pixel))); + } + } + else{ + for(i=0; i < count; ++i){ + pixel = *dest; + r = static_cast ((low.red != high.red) ? + equalize_map[qRed(pixel)].red : qRed(pixel)); + + g = static_cast ((low.green != high.green) ? + equalize_map[qGreen(pixel)].green : qGreen(pixel)); + + b = static_cast ((low.blue != high.blue) ? + equalize_map[qBlue(pixel)].blue : qBlue(pixel)); + + *dest++ = qRgba(r, g, b, qAlpha(pixel)); + } + } + + delete[] histogram; + delete[] map; + delete[] equalize_map; + return(true); +} + +//-------------------------------------------------------------------------------- + +QImage Blitz::blur(QImage &img, int radius) +{ + if (img.isNull()) { + return (img); + } + + if (img.depth() < 8) { + img = img.convertToFormat(QImage::Format_Indexed8); + } + + QVector colorTable; + if (img.format() == QImage::Format_Indexed8) { + colorTable = img.colorTable(); + } + + auto width = img.width(); + auto height = img.height(); + + QImage buffer(width, height, img.hasAlphaChannel() ? QImage::Format_ARGB32 : QImage::Format_RGB32); + + const auto img_format = img.format(); + + int *as = new int[width]; + int *rs = new int[width]; + int *gs = new int[width]; + int *bs = new int[width]; + + QRgb *p1 , *p2; + + for (auto y = 0; y < height; ++y) { + auto my = y - radius; + auto mh = (radius << 1) + 1; + + if (my < 0) { + mh += my; + my = 0; + } + + if ((my + mh) > height) { + mh = height - my; + } + + p1 = reinterpret_cast(buffer.scanLine(y)); + + memset(as, 0, static_cast(width) * sizeof(int)); + memset(rs, 0, static_cast(width) * sizeof(int)); + memset(gs, 0, static_cast(width) * sizeof(int)); + memset(bs, 0, static_cast(width) * sizeof(int)); + + + switch (img_format) { + case QImage::Format_ARGB32_Premultiplied: { + QRgb pixel; + for (auto i = 0; i < mh; i++) { + p2 = reinterpret_cast(img.scanLine(i + my)); + for (auto j = 0; j < width; ++j) { + p2++; + pixel = convertFromPremult(*p2); + as[j] += qAlpha(pixel); + rs[j] += qRed(pixel) * qRed(pixel); + gs[j] += qGreen(pixel) * qGreen(pixel); + bs[j] += qBlue(pixel) * qBlue(pixel); + } + } + break; + } + + case QImage::Format_Indexed8: { + QRgb pixel; + unsigned char *ptr; + for (auto i = 0; i < mh; ++i) { + ptr = img.scanLine(i + my); + for (auto j = 0; j < width; ++j) { + ptr++; + pixel = colorTable[*ptr]; + as[j] += qAlpha(pixel); + rs[j] += qRed(pixel) * qRed(pixel); + gs[j] += qGreen(pixel) * qGreen(pixel); + bs[j] += qBlue(pixel) * qBlue(pixel); + } + } + break; + } + + default: { + for (auto i = 0; i < mh; ++i) { + p2 = reinterpret_cast(img.scanLine(i + my)); + for (auto j = 0; j < width; j++) { + p2++; + as[j] += qAlpha(*p2); + rs[j] += qRed(*p2); + gs[j] += qGreen(*p2); + bs[j] += qBlue(*p2); + } + } + break; + } + } + + for (auto i = 0; i < width; ++i) { + auto a{0}; + auto r{0}; + auto g{0}; + auto b{0}; + + auto mx = i - radius; + auto mw = (radius << 1) + 1; + + if (mx < 0) { + mw += mx; + mx = 0; + } + + if ((mx + mw) > width) { + mw = width - mx; + } + + for (auto j = mx; j < (mw + mx); ++j) { + a += as[j]; + r += rs[j]; + g += gs[j]; + b += bs[j]; + } + + auto mt = mw * mh; + + a = a / mt; + r = r / mt; + g = g / mt; + b = b / mt; + + *p1++ = qRgba(std::sqrt(r), std::sqrt(g), std::sqrt(b), a); + } + } + + delete[] as; + delete[] rs; + delete[] gs; + delete[] bs; + + return (buffer); +} + +//-------------------------------------------------------------------------------- + +int defaultConvolveMatrixSize(float radius, float sigma, bool quality) +{ + int i, matrix_size; + float normalize, value; + float sigma2 = sigma*sigma*2.0f; + float sigmaSQ2PI = static_cast(M_SQ2PI) * sigma; + int max = quality ? 65535 : 255; + + if(sigma == 0.0f){ + qWarning("Blitz::defaultConvolveMatrixSize(): Zero sigma is invalid!"); + return(5); + } + + if(radius > 0.0f) { + return(static_cast(2.0f * std::ceil(radius) + 1.0f)); + } + + matrix_size = 5; + do{ + normalize = 0.0; + for(i=(-matrix_size/2); i <= (matrix_size/2); ++i) { + normalize += std::exp(-(static_cast (i*i))/sigma2) / sigmaSQ2PI; + } + i = matrix_size/2; + value = std::exp(-(static_cast (i*i))/sigma2) / sigmaSQ2PI / normalize; + matrix_size += 2; + } while(static_cast(max*value) > 0); + + matrix_size-=4; + return(matrix_size); +} + +//-------------------------------------------------------------------------------- + +QImage convolve(QImage &img, int matrix_size, float *matrix) +{ + int i, x, y, w, h, matrix_x, matrix_y; + int edge = matrix_size/2; + QRgb *dest, *src, *s, **scanblock; + float *m, *normalize_matrix, normalize; + + if(!(matrix_size % 2)){ + qWarning("Blitz::convolve(): kernel width must be an odd number!"); + return(img); + } + + w = img.width(); + h = img.height(); + if(w < 3 || h < 3){ + qWarning("Blitz::convolve(): Image is too small!"); + return(img); + } + + if(img.format() == QImage::Format_ARGB32_Premultiplied) { + img = img.convertToFormat(QImage::Format_ARGB32); + } + else if(img.depth() < 32){ + img = img.convertToFormat(img.hasAlphaChannel() ? + QImage::Format_ARGB32 : + QImage::Format_RGB32); + } + QImage buffer(w, h, img.format()); + + scanblock = new QRgb* [matrix_size]; + normalize_matrix = new float[matrix_size*matrix_size]; + + // create normalized matrix + normalize = 0.0; + for(i=0; i < matrix_size*matrix_size; ++i) { + normalize += matrix[i]; + } + if(std::abs(normalize) <= static_cast (M_EPSILON)) { + normalize = 1.0f; + } + normalize = 1.0f/normalize; + for(i=0; i < matrix_size*matrix_size; ++i){ + normalize_matrix[i] = normalize*matrix[i]; + } + + // apply + + { + // + // + // Non-MMX version + // + // + + float r, g, b; + for(y=0; y < h; ++y){ + src = reinterpret_cast(img.scanLine(y)); + dest = reinterpret_cast(buffer.scanLine(y)); + // Read in scanlines to pixel neighborhood. If the scanline is outside + // the image use the top or bottom edge. + for(x=y-edge, i=0; x <= y+edge; ++i, ++x){ + scanblock[i] = reinterpret_cast( + img.scanLine((x < 0) ? 0 : (x > h-1) ? h-1 : x)); + } + // Now we are about to start processing scanlines. First handle the + // part where the pixel neighborhood extends off the left edge. + for(x=0; x-edge < 0 ; ++x){ + r = g = b = 0.0; + m = normalize_matrix; + for(matrix_y = 0; matrix_y < matrix_size; ++matrix_y){ + s = scanblock[matrix_y]; + matrix_x = -edge; + while(x+matrix_x < 0){ + CONVOLVE_ACC(*m, *s); + ++matrix_x; ++m; + } + while(matrix_x <= edge){ + CONVOLVE_ACC(*m, *s); + ++matrix_x; ++m; ++s; + } + } + r = r < 0.0f ? 0.0f : r > 255.0f ? 255.0f : r + 0.5f; + g = g < 0.0f ? 0.0f : g > 255.0f ? 255.0f : g + 0.5f; + b = b < 0.0f ? 0.0f : b > 255.0f ? 255.0f : b + 0.5f; + *dest++ = qRgba(static_cast (r), static_cast (g), + static_cast (b), qAlpha(*src++)); + } + // Okay, now process the middle part where the entire neighborhood + // is on the image. + for(; x+edge < w; ++x){ + m = normalize_matrix; + r = g = b = 0.0; + for(matrix_y = 0; matrix_y < matrix_size; ++matrix_y){ + s = scanblock[matrix_y] + (x-edge); + for(matrix_x = -edge; matrix_x <= edge; ++matrix_x, ++m, ++s){ + CONVOLVE_ACC(*m, *s); + } + } + r = r < 0.0f ? 0.0f : r > 255.0f ? 255.0f : r + 0.5f; + g = g < 0.0f ? 0.0f : g > 255.0f ? 255.0f : g + 0.5f; + b = b < 0.0f ? 0.0f : b > 255.0f ? 255.0f : b + 0.5f; + *dest++ = qRgba(static_cast (r), static_cast (g), + static_cast (b), qAlpha(*src++)); + } + // Finally process the right part where the neighborhood extends off + // the right edge of the image + for(; x < w; ++x){ + r = g = b = 0.0; + m = normalize_matrix; + for(matrix_y = 0; matrix_y < matrix_size; ++matrix_y){ + s = scanblock[matrix_y]; + s += x-edge; + matrix_x = -edge; + while(x+matrix_x < w){ + CONVOLVE_ACC(*m, *s); + ++matrix_x; + ++m; + ++s; + } + --s; + while(matrix_x <= edge){ + CONVOLVE_ACC(*m, *s); + ++matrix_x; + ++m; + } + } + r = r < 0.0f ? 0.0f : r > 255.0f ? 255.0f : r + 0.5f; + g = g < 0.0f ? 0.0f : g > 255.0f ? 255.0f : g + 0.5f; + b = b < 0.0f ? 0.0f : b > 255.0f ? 255.0f : b + 0.5f; + *dest++ = qRgba(static_cast (r), static_cast (g), + static_cast (b), qAlpha(*src++)); + } + } + } + + delete[] scanblock; + delete[] normalize_matrix; + return(buffer); +} + +//-------------------------------------------------------------------------------- + +QImage Blitz::gaussianSharpen(QImage &img, float radius, float sigma) +{ + if(sigma == 0.0f){ + qWarning("Blitz::gaussianSharpen(): Zero sigma is invalid!"); + return(img); + } + + int matrix_size = defaultConvolveMatrixSize(radius, sigma, true); + int len = matrix_size*matrix_size; + float alpha, *matrix = new float[len]; + float sigma2 = sigma*sigma*2.0f; + float sigmaPI2 = 2.0f*static_cast (M_PI)*sigma*sigma; + + int half = matrix_size/2; + int x, y, i=0, j=half; + float normalize=0.0; + for(y=(-half); y <= half; ++y, --j){ + for(x=(-half); x <= half; ++x, ++i){ + alpha = std::exp(-(static_cast (x*x+y*y))/sigma2); + matrix[i] = alpha/sigmaPI2; + normalize += matrix[i]; + } + } + + matrix[i/2]=(-2.0f)*normalize; + QImage result(convolve(img, matrix_size, matrix)); + delete[] matrix; + return(result); +} + +//-------------------------------------------------------------------------------- + +QImage Blitz::emboss(QImage &img, float radius, float sigma) +{ + if(sigma == 0.0f){ + qWarning("Blitz::emboss(): Zero sigma is invalid!"); + return(img); + } + + int matrix_size = defaultConvolveMatrixSize(radius, sigma, true); + int len = matrix_size*matrix_size; + + float alpha, *matrix = new float[len]; + float sigma2 = sigma*sigma*2.0f; + float sigmaPI2 = 2.0f*static_cast (M_PI)*sigma*sigma; + + int half = matrix_size/2; + int x, y, i=0, j=half; + for(y=(-half); y <= half; ++y, --j){ + for(x=(-half); x <= half; ++x, ++i){ + alpha = std::exp(-(static_cast (x*x+y*y))/sigma2); + matrix[i]=((x < 0) || (y < 0) ? -8.0f : 8.0f)*alpha/sigmaPI2; + if(x == j) { + matrix[i]=0.0; + } + } + } + QImage result(convolve(img, matrix_size, matrix)); + delete[] matrix; + equalize(result); + return(result); +} + +//-------------------------------------------------------------------------------- + +QImage& Blitz::flatten(QImage &img, const QColor &ca, const QColor &cb) +{ + if(img.isNull()) { + return(img); + } + + if(img.depth() == 1) { + img.setColor(0, ca.rgb()); + img.setColor(1, cb.rgb()); + return(img); + } + + int r1 = ca.red(); int r2 = cb.red(); + int g1 = ca.green(); int g2 = cb.green(); + int b1 = ca.blue(); int b2 = cb.blue(); + int min = 0, max = 255; + + QRgb *data, *end; + QVector cTable; + if(img.format() == QImage::Format_Indexed8){ + cTable = img.colorTable(); + data = static_cast (cTable.data()); + end = data + img.colorCount(); + + } + else{ + data = reinterpret_cast(img.scanLine(0)); + end = data + (img.width()*img.height()); + } + + // get minimum and maximum graylevel + QRgb *ptr = data; + int mean; + + if(img.format() != QImage::Format_ARGB32_Premultiplied){ + while(ptr != end){ + mean = (qRed(*ptr) + qGreen(*ptr) + qBlue(*ptr)) / 3; + min = qMin(min, mean); + max = qMax(max, mean); + ++ptr; + } + } + else{ + QRgb pixel; + while(ptr != end){ + pixel = convertFromPremult(*ptr); + mean = (qRed(pixel) + qGreen(pixel) + qBlue(pixel)) / 3; + min = qMin(min, mean); + max = qMax(max, mean); + ++ptr; + } + } + + // conversion factors + float sr = (static_cast (r2 - r1) / (max - min)); + float sg = (static_cast (g2 - g1) / (max - min)); + float sb = (static_cast (b2 - b1) / (max - min)); + + if(img.format() != QImage::Format_ARGB32_Premultiplied){ + while(data != end){ + mean = (qRed(*data) + qGreen(*data) + qBlue(*data)) / 3; + *data = qRgba(static_cast (sr * (mean - min) + r1 + 0.5f), + static_cast (sg * (mean - min) + g1 + 0.5f), + static_cast (sb * (mean - min) + b1 + 0.5f), + qAlpha(*data)); + ++data; + } + } + else{ + QRgb pixel; + while(data != end){ + pixel = convertFromPremult(*data); + mean = (qRed(pixel) + qGreen(pixel) + qBlue(pixel)) / 3; + *data = + convertToPremult(qRgba(static_cast (sr * (mean - min) + r1 + 0.5f), + static_cast (sg * (mean - min) + g1 + 0.5f), + static_cast (sb * (mean - min) + b1 + 0.5f), + qAlpha(*data))); + ++data; + } + } + + if(img.format() == QImage::Format_Indexed8) { + img.setColorTable(cTable); + } + return(img); +} + +//-------------------------------------------------------------------------------- diff --git a/imagelib/effects/blitz.h b/imagelib/effects/blitz.h new file mode 100644 index 0000000..bb6a44f --- /dev/null +++ b/imagelib/effects/blitz.h @@ -0,0 +1,42 @@ +#ifndef BLITZ_H +#define BLITZ_H + +//************************************************************************** +/* + (c) 2016 Martin Koller, kollix@aon.at + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +//************************************************************************** + +#include + +namespace Blitz +{ + QImage blur(QImage &img, int radius); + QImage gaussianSharpen(QImage &img, float radius, float sigma); + QImage emboss(QImage &img, float radius, float sigma); + QImage &flatten(QImage &img, const QColor &ca, const QColor &cb); +}; + +#endif diff --git a/imagelib/effects/kpEffectBalance.cpp b/imagelib/effects/kpEffectBalance.cpp new file mode 100644 index 0000000..16f727e --- /dev/null +++ b/imagelib/effects/kpEffectBalance.cpp @@ -0,0 +1,187 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_BALANCE 0 + + +#include "kpEffectBalance.h" + +#include + +#include + +#include "kpLogCategories.h" + +#include "pixmapfx/kpPixmapFX.h" + + +#if DEBUG_KP_EFFECT_BALANCE + #include +#endif + + +static inline int between0And255 (int val) +{ + if (val < 0) { + return 0; + } + + if (val > 255) { + return 255; + } + + return val; +} + + +static inline int brightness (int base, int strength) +{ + return between0And255 (base + strength * 255 / 50); +} + +static inline int contrast (int base, int strength) +{ + return between0And255 ((base - 127) * (strength + 50) / 50 + 127); +} + +static inline int gamma (int base, int strength) +{ + return between0And255 (qRound (255.0 * std::pow (base / 255.0, 1.0 / std::pow (10., strength / 50.0)))); +} + + +static inline int brightnessContrastGamma (int base, + int newBrightness, + int newContrast, + int newGamma) +{ + return gamma (contrast (brightness (base, newBrightness), + newContrast), + newGamma); +} + +// public static +kpImage kpEffectBalance::applyEffect (const kpImage &image, + int channels, + int brightness, int contrast, int gamma) +{ +#if DEBUG_KP_EFFECT_BALANCE + qCDebug(kpLogImagelib) << "kpEffectBalance::applyEffect(" + << "channels=" << channels + << ",brightness=" << brightness + << ",contrast=" << contrast + << ",gamma=" << gamma + << ")"; + QTime timer; timer.start (); +#endif + + QImage qimage = image; +#if DEBUG_KP_EFFECT_BALANCE + qCDebug(kpLogImagelib) << "\tconvertToImage=" << timer.restart (); +#endif + + + quint8 transformRed [256], + transformGreen [256], + transformBlue [256]; + + for (int i = 0; i < 256; i++) + { + auto applied = static_cast (brightnessContrastGamma (i, brightness, contrast, gamma)); + + if (channels & kpEffectBalance::Red) { + transformRed [i] = applied; + } + else { + transformRed [i] = static_cast (i); + } + + if (channels & kpEffectBalance::Green) { + transformGreen [i] = applied; + } + else { + transformGreen [i] = static_cast (i); + } + + if (channels & kpEffectBalance::Blue) { + transformBlue [i] = applied; + } + else { + transformBlue [i] = static_cast (i); + } + } + +#if DEBUG_KP_EFFECT_BALANCE + qCDebug(kpLogImagelib) << "\tbuild lookup=" << timer.restart (); +#endif + + + if (qimage.depth () > 8) + { + for (int y = 0; y < qimage.height (); y++) + { + for (int x = 0; x < qimage.width (); x++) + { + const QRgb rgb = qimage.pixel (x, y); + + const auto red = static_cast (qRed (rgb)); + const auto green = static_cast (qGreen (rgb)); + const auto blue = static_cast (qBlue (rgb)); + const auto alpha = static_cast (qAlpha (rgb)); + + qimage.setPixel (x, y, + qRgba (transformRed [red], + transformGreen [green], + transformBlue [blue], + alpha)); + } + } + } + else + { + for (int i = 0; i < qimage.colorCount (); i++) + { + const QRgb rgb = qimage.color (i); + + const auto red = static_cast (qRed (rgb)); + const auto green = static_cast (qGreen (rgb)); + const auto blue = static_cast (qBlue (rgb)); + const auto alpha = static_cast (qAlpha (rgb)); + + qimage.setColor (i, + qRgba (transformRed [red], + transformGreen [green], + transformBlue [blue], + alpha)); + } + + } + + return qimage; +} + diff --git a/imagelib/effects/kpEffectBalance.h b/imagelib/effects/kpEffectBalance.h new file mode 100644 index 0000000..d2f1911 --- /dev/null +++ b/imagelib/effects/kpEffectBalance.h @@ -0,0 +1,53 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectBalance_H +#define kpEffectBalance_H + + +#include "imagelib/kpImage.h" + + +class kpEffectBalance +{ +public: + enum Channel + { + None = 0, + Red = 1, Green = 2, Blue = 4, + RGB = Red | Green | Blue + }; + + // (, & are from -50 to 50) + static kpImage applyEffect (const kpImage &image, + int channels, + int brightness, int contrast, int gamma); +}; + + +#endif // kpEffectBalance_H diff --git a/imagelib/effects/kpEffectBlurSharpen.cpp b/imagelib/effects/kpEffectBlurSharpen.cpp new file mode 100644 index 0000000..6cabf9a --- /dev/null +++ b/imagelib/effects/kpEffectBlurSharpen.cpp @@ -0,0 +1,184 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_BLUR_SHARPEN 0 + + +#include "kpEffectBlurSharpen.h" +#include "blitz.h" + +#include "kpLogCategories.h" + +#include "pixmapfx/kpPixmapFX.h" + + +#if DEBUG_KP_EFFECT_BLUR_SHARPEN + #include +#endif + + +//--------------------------------------------------------------------- + +// +// For info on "radius" and "sigma", see https://redskiesatnight.com/2005/04/06/sharpening-using-image-magick/ +// +// Daniel Duley says: +// +// +// I don't think I can describe it any better than the article: The radius +// controls many how pixels are taken into account when determining the value +// of the center pixel. This controls the quality [and speed] of the result but not +// necessarily the strength. The sigma controls how those neighboring pixels +// are weighted depending on how far the are from the center one. This is +// closer to strength, but not exactly >:) +// +// + + +static QImage BlurQImage(const QImage &qimage, int strength) +{ + if (strength == 0) { + return qimage; + } + + // The numbers that follow were picked by experimentation to try to get + // an effect linearly proportional to and at the same time, + // be fast enough. + // + // I still have no idea what "radius" means. + + const double RadiusMin = 1; + const double RadiusMax = 10; + const double radius = RadiusMin + + (strength - 1) * + (RadiusMax - RadiusMin) / + (kpEffectBlurSharpen::MaxStrength - 1); + +#if DEBUG_KP_EFFECT_BLUR_SHARPEN + qCDebug(kpLogImagelib) << "kpEffectBlurSharpen.cpp:BlurQImage(strength=" << strength << ")" + << " radius=" << radius; +#endif + + QImage img(qimage); + return Blitz::blur(img, qRound(radius)); +} + +//--------------------------------------------------------------------- + +static QImage SharpenQImage (const QImage &qimage_, int strength) +{ + QImage qimage = qimage_; + if (strength == 0) { + return qimage; + } + + + // The numbers that follow were picked by experimentation to try to get + // an effect linearly proportional to and at the same time, + // be fast enough. + // + // I still have no idea what "radius" and "sigma" mean. + + const double RadiusMin = 0.1; + const double RadiusMax = 2.5; + const double radius = RadiusMin + + (strength - 1) * + (RadiusMax - RadiusMin) / + (kpEffectBlurSharpen::MaxStrength - 1); + + const double SigmaMin = 0.5; + const double SigmaMax = 3.0; + const double sigma = SigmaMin + + (strength - 1) * + (SigmaMax - SigmaMin) / + (kpEffectBlurSharpen::MaxStrength - 1); + + const double RepeatMin = 1; + const double RepeatMax = 2; + const double repeat = qRound (RepeatMin + + (strength - 1) * + (RepeatMax - RepeatMin) / + (kpEffectBlurSharpen::MaxStrength - 1)); + + +#if DEBUG_KP_EFFECT_BLUR_SHARPEN + qCDebug(kpLogImagelib) << "kpEffectBlurSharpen.cpp:SharpenQImage(strength=" << strength << ")" + << " radius=" << radius + << " sigma=" << sigma + << " repeat=" << repeat; +#endif + + + for (int i = 0; i < repeat; i++) + { + #if DEBUG_KP_EFFECT_BLUR_SHARPEN + QTime timer; timer.start (); + #endif + qimage = Blitz::gaussianSharpen (qimage, static_cast (radius), + static_cast (sigma)); + #if DEBUG_KP_EFFECT_BLUR_SHARPEN + qCDebug(kpLogImagelib) << "\titeration #" + QString::number (i) + << ": " + QString::number (timer.elapsed ()) << "ms"; + #endif + } + + + return qimage; +} + +//--------------------------------------------------------------------- + +// public static +kpImage kpEffectBlurSharpen::applyEffect (const kpImage &image, + Type type, int strength) +{ +#if DEBUG_KP_EFFECT_BLUR_SHARPEN + qCDebug(kpLogImagelib) << "kpEffectBlurSharpen::applyEffect(image.rect=" << image.rect () + << ",type=" << int (type) + << ",strength=" << strength + << ")"; +#endif + + Q_ASSERT (strength >= MinStrength && strength <= MaxStrength); + + if (type == Blur) { + return ::BlurQImage (image, strength); + } + + if (type == Sharpen) { + return ::SharpenQImage (image, strength); + } + + if (type == MakeConfidential) { + QImage img(image); + return Blitz::blur(img, qMin(20, img.width() / 2)); + } + + return kpImage(); +} + +//--------------------------------------------------------------------- diff --git a/imagelib/effects/kpEffectBlurSharpen.h b/imagelib/effects/kpEffectBlurSharpen.h new file mode 100644 index 0000000..a79b974 --- /dev/null +++ b/imagelib/effects/kpEffectBlurSharpen.h @@ -0,0 +1,57 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectBlurSharpen_H +#define kpEffectBlurSharpen_H + + +#include "imagelib/kpImage.h" + + +class kpEffectBlurSharpen +{ +public: + enum Type + { + None = 0, Blur, Sharpen, MakeConfidential + }; + + // Blur or Sharpen with this strength is the same as None. + // This will always be 0 - this constant will not change. + static const int MinStrength = 0; + + static const int MaxStrength = 10; + + // = strength of the effect + // (must be between MinStrength and MaxStrength inclusive) + static kpImage applyEffect (const kpImage &image, + Type type, int strength); +}; + + +#endif // kpEffectBlurSharpen_H diff --git a/imagelib/effects/kpEffectEmboss.cpp b/imagelib/effects/kpEffectEmboss.cpp new file mode 100644 index 0000000..72b62ec --- /dev/null +++ b/imagelib/effects/kpEffectEmboss.cpp @@ -0,0 +1,81 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_EMBOSS 0 + + +#include "kpEffectEmboss.h" +#include "blitz.h" + +#include "kpLogCategories.h" + +#include "pixmapfx/kpPixmapFX.h" + + +static QImage EmbossQImage (const QImage &qimage_, int strength) +{ + QImage qimage = qimage_; + if (strength == 0) { + return qimage; + } + + + // The numbers that follow were picked by experimentation to try to get + // an effect linearly proportional to and at the same time, + // be fast enough. + // + // I still have no idea what "radius" and "sigma" mean. + + const auto radius = 0.0; + + const auto sigma = 1.0; + + const auto repeat = 1; + + + for (int i = 0; i < repeat; i++) + { + qimage = Blitz::emboss (qimage, radius, sigma); + } + + + return qimage; +} + + +// public static +kpImage kpEffectEmboss::applyEffect (const kpImage &image, int strength) +{ +#if DEBUG_KP_EFFECT_EMBOSS + qCDebug(kpLogImagelib) << "kpEffectEmboss::applyEffect(strength=" << strength << ")" + << endl; +#endif + + Q_ASSERT (strength >= MinStrength && strength <= MaxStrength); + + return ::EmbossQImage (image, strength); +} diff --git a/imagelib/effects/kpEffectEmboss.h b/imagelib/effects/kpEffectEmboss.h new file mode 100644 index 0000000..6cc83ad --- /dev/null +++ b/imagelib/effects/kpEffectEmboss.h @@ -0,0 +1,52 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectEmboss_H +#define kpEffectEmboss_H + + +#include "imagelib/kpImage.h" + + +class kpEffectEmboss +{ +public: + // This will always be 0 - this constant will not change. + static const int MinStrength = 0; + + static const int MaxStrength = 10; + + // = strength of the effect + // (must be between MinStrength and MaxStrength inclusive) + // + // Currently, all non-zero strengths are the same. + static kpImage applyEffect (const kpImage &image, int strength); +}; + + +#endif // kpEffectEmboss_H diff --git a/imagelib/effects/kpEffectFlatten.cpp b/imagelib/effects/kpEffectFlatten.cpp new file mode 100644 index 0000000..e0c541b --- /dev/null +++ b/imagelib/effects/kpEffectFlatten.cpp @@ -0,0 +1,54 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "kpEffectFlatten.h" +#include "blitz.h" + +//-------------------------------------------------------------------------------- +// public static + +void kpEffectFlatten::applyEffect (QImage *destImagePtr, + const QColor &color1, const QColor &color2) +{ + if (!destImagePtr) { + return; + } + + Blitz::flatten (*destImagePtr/*ref*/, color1, color2); +} + +//-------------------------------------------------------------------------------- +// public static + +QImage kpEffectFlatten::applyEffect (const QImage &img, + const QColor &color1, const QColor &color2) +{ + QImage retImage = img; + applyEffect (&retImage, color1, color2); + return retImage; +} + +//-------------------------------------------------------------------------------- diff --git a/imagelib/effects/kpEffectFlatten.h b/imagelib/effects/kpEffectFlatten.h new file mode 100644 index 0000000..2fb0aaa --- /dev/null +++ b/imagelib/effects/kpEffectFlatten.h @@ -0,0 +1,47 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectFlatten_H +#define kpEffectFlatten_H + + +class QColor; +class QImage; + + +class kpEffectFlatten +{ +public: + static void applyEffect (QImage *destImagePtr, + const QColor &color1, const QColor &color2); + static QImage applyEffect (const QImage &img, + const QColor &color1, const QColor &color2); +}; + + +#endif // kpEffectFlatten_H diff --git a/imagelib/effects/kpEffectGrayscale.cpp b/imagelib/effects/kpEffectGrayscale.cpp new file mode 100644 index 0000000..b657fb3 --- /dev/null +++ b/imagelib/effects/kpEffectGrayscale.cpp @@ -0,0 +1,75 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_PIXMAP_FX 0 + + +#include "kpEffectGrayscale.h" + +#include "pixmapfx/kpPixmapFX.h" + + +static QRgb toGray (QRgb rgb) +{ + // naive way that doesn't preserve brightness + // int gray = (qRed (rgb) + qGreen (rgb) + qBlue (rgb)) / 3; + + // over-exaggerates red & blue + // int gray = qGray (rgb); + + int gray = (212671 * qRed (rgb) + 715160 * qGreen (rgb) + 72169 * qBlue (rgb)) / 1000000; + return qRgba (gray, gray, gray, qAlpha (rgb)); +} + + +// public static +kpImage kpEffectGrayscale::applyEffect (const kpImage &image) +{ + kpImage qimage(image); + + // TODO: Why not just write to the kpImage directly? + if (qimage.depth () > 8) + { + for (int y = 0; y < qimage.height (); y++) + { + for (int x = 0; x < qimage.width (); x++) + { + qimage.setPixel (x, y, toGray (qimage.pixel (x, y))); + } + } + } + else + { + // 1- & 8- bit images use a color table + for (int i = 0; i < qimage.colorCount (); i++) { + qimage.setColor (i, toGray (qimage.color (i))); + } + } + + return qimage; +} diff --git a/imagelib/effects/kpEffectGrayscale.h b/imagelib/effects/kpEffectGrayscale.h new file mode 100644 index 0000000..4f5b7a0 --- /dev/null +++ b/imagelib/effects/kpEffectGrayscale.h @@ -0,0 +1,47 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectGrayscale_H +#define kpEffectGrayscale_H + + +#include "imagelib/kpImage.h" + + +// +// Converts the image to grayscale. +// + +class kpEffectGrayscale +{ +public: + static kpImage applyEffect (const kpImage &image); +}; + + +#endif // kpEffectGrayscale_H diff --git a/imagelib/effects/kpEffectHSV.cpp b/imagelib/effects/kpEffectHSV.cpp new file mode 100644 index 0000000..3d8e01b --- /dev/null +++ b/imagelib/effects/kpEffectHSV.cpp @@ -0,0 +1,189 @@ + +/* + Copyright (c) 2007 Mike Gashler + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// TODO: Clarence's code review + +#include "kpEffectHSV.h" + +#include + +#include +#include + +#include "kpLogCategories.h" + +#include "pixmapfx/kpPixmapFX.h" + + +static void ColorToHSV(unsigned int c, float* pHue, float* pSaturation, float* pValue) +{ + int r = qRed(c); + int g = qGreen(c); + int b = qBlue(c); + int min{}; + if(b >= g && b >= r) + { + // Blue + min = qMin(r, g); + if(b != min) + { + *pHue = static_cast (r - g) / ((b - min) * 6) + static_cast (2) / 3; + *pSaturation = 1.0f - static_cast (min) / static_cast (b); + } + else + { + *pHue = 0; + *pSaturation = 0; + } + *pValue = static_cast (b) / 255; + } + else if(g >= r) + { + // Green + min = qMin(b, r); + if(g != min) + { + *pHue = static_cast (b - r) / ((g - min) * 6) + static_cast (1) / 3; + *pSaturation = 1.0f - static_cast (min) / static_cast (g); + } + else + { + *pHue = 0; + *pSaturation = 0; + } + *pValue = static_cast (g) / 255; + } + else + { + // Red + min = qMin(g, b); + if(r != min) + { + *pHue = static_cast (g - b) / ((r - min) * 6); + if(*pHue < 0) { + (*pHue) += 1.0f; + } + *pSaturation = 1.0f - static_cast (min) / static_cast (r); + } + else + { + *pHue = 0; + *pSaturation = 0; + } + *pValue = static_cast (r) / 255; + } +} + +static unsigned int HSVToColor(int alpha, float hue, float saturation, float value) +{ + hue *= 5.999999f; + int h = static_cast (hue); + float f = hue - h; + float p = value * (1.0 - saturation); + float q = value * (1.0 - ((h & 1) == 0 ? 1.0 - f : f) * saturation); + switch(h) + { + case 0: return qRgba(static_cast (value * 255.999999), + static_cast (q * 255.999999), + static_cast (p * 255.999999), alpha); + + case 1: return qRgba(static_cast (q * 255.999999), + static_cast (value * 255.999999), + static_cast (p * 255.999999), alpha); + + case 2: return qRgba(static_cast (p * 255.999999), + static_cast (value * 255.999999), + static_cast (q * 255.999999), alpha); + + case 3: return qRgba(static_cast (p * 255.999999), + static_cast (q * 255.999999), + static_cast (value * 255.999999), alpha); + + case 4: return qRgba(static_cast (q * 255.999999), + static_cast (p * 255.999999), + static_cast (value * 255.999999), alpha); + + case 5: return qRgba(static_cast (value * 255.999999), + static_cast (p * 255.999999), + static_cast (q * 255.999999), alpha); + } + return qRgba(0, 0, 0, alpha); +} + +static QRgb AdjustHSVInternal (QRgb pix, double hueDiv360, double saturation, double value) +{ + float h, s, v; + ::ColorToHSV(pix, &h, &s, &v); + + const int alpha = qAlpha(pix); + + h += static_cast (hueDiv360); + h -= std::floor(h); + + s = qMax(0.0f, qMin(static_cast(1), s + static_cast (saturation))); + + v = qMax(0.0f, qMin(static_cast(1), v + static_cast (value))); + + return ::HSVToColor(alpha, h, s, v); +} + +static void AdjustHSV (QImage* pImage, double hue, double saturation, double value) +{ + hue /= 360; + + if (pImage->depth () > 8) + { + for (int y = 0; y < pImage->height (); y++) + { + for (int x = 0; x < pImage->width (); x++) + { + QRgb pix = pImage->pixel (x, y); + pix = ::AdjustHSVInternal (pix, hue, saturation, value); + pImage->setPixel (x, y, pix); + } + } + } + else + { + for (int i = 0; i < pImage->colorCount (); i++) + { + QRgb pix = pImage->color (i); + pix = ::AdjustHSVInternal (pix, hue, saturation, value); + pImage->setColor (i, pix); + } + } +} + +// public static +kpImage kpEffectHSV::applyEffect (const kpImage &image, + double hue, double saturation, double value) +{ + QImage qimage(image); + ::AdjustHSV (&qimage, hue, saturation, value); + return qimage; +} + diff --git a/imagelib/effects/kpEffectHSV.h b/imagelib/effects/kpEffectHSV.h new file mode 100644 index 0000000..26ceb8a --- /dev/null +++ b/imagelib/effects/kpEffectHSV.h @@ -0,0 +1,44 @@ + +/* + Copyright (c) 2007 Mike Gashler + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectHSV_H +#define kpEffectHSV_H + + +#include "imagelib/kpImage.h" + + +class kpEffectHSV +{ +public: + static kpImage applyEffect (const kpImage &image, + double hue, double saturation, double value); +}; + + +#endif // kpEffectHSV_H diff --git a/imagelib/effects/kpEffectInvert.cpp b/imagelib/effects/kpEffectInvert.cpp new file mode 100644 index 0000000..db305b4 --- /dev/null +++ b/imagelib/effects/kpEffectInvert.cpp @@ -0,0 +1,89 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_INVERT 0 + + +#include "kpEffectInvert.h" + +#include + +#include "kpLogCategories.h" + +#include "pixmapfx/kpPixmapFX.h" + + +// public static +void kpEffectInvert::applyEffect (QImage *destImagePtr, int channels) +{ + if (channels == kpEffectInvert::RGB) + { + destImagePtr->invertPixels (); + return; + } + + QRgb mask = qRgba ((channels & Red) ? 0xFF : 0, + (channels & Green) ? 0xFF : 0, + (channels & Blue) ? 0xFF : 0, + 0/*don't invert alpha*/); +#if DEBUG_KP_EFFECT_INVERT + qCDebug(kpLogImagelib) << "kpEffectInvert::applyEffect(channels=" << channels + << ") mask=" << (int *) mask; +#endif + + if (destImagePtr->depth () > 8) + { + // Above version works for Qt 3.2 at least. + // But this version will always work (slower, though) and supports + // inverting particular channels. + for (int y = 0; y < destImagePtr->height (); y++) + { + for (int x = 0; x < destImagePtr->width (); x++) + { + destImagePtr->setPixel (x, y, destImagePtr->pixel (x, y) ^ mask); + } + } + } + else + { + for (int i = 0; i < destImagePtr->colorCount (); i++) + { + destImagePtr->setColor (i, destImagePtr->color (i) ^ mask); + } + } +} + +// public static +QImage kpEffectInvert::applyEffect (const QImage &img, int channels) +{ + QImage retImage = img; + applyEffect (&retImage, channels); + return retImage; +} + + diff --git a/imagelib/effects/kpEffectInvert.h b/imagelib/effects/kpEffectInvert.h new file mode 100644 index 0000000..6088843 --- /dev/null +++ b/imagelib/effects/kpEffectInvert.h @@ -0,0 +1,62 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectInvert_H +#define kpEffectInvert_H + + +class QImage; + + +class kpEffectInvert +{ +public: + enum Channel + { + None = 0, + Red = 1, Green = 2, Blue = 4, + RGB = Red | Green | Blue + }; + + // + // Inverts the colours of each pixel in the given image. + // These functions differ from QImage::invertPixels() in the following ways: + // + // 1. for 8-bit images, it inverts the colours of the Colour Table + // (this means that you would get visually similar results to inversion + // at higher bit depths - rather than a "random-looking" inversion + // depending on the contents of the Colour Table) + // 2. never inverts the Alpha Buffer + // + + static void applyEffect (QImage *destImagePtr, int channels = RGB); + static QImage applyEffect (const QImage &img, int channels = RGB); +}; + + +#endif // kpEffectInvert_H diff --git a/imagelib/effects/kpEffectReduceColors.cpp b/imagelib/effects/kpEffectReduceColors.cpp new file mode 100644 index 0000000..40ce0db --- /dev/null +++ b/imagelib/effects/kpEffectReduceColors.cpp @@ -0,0 +1,240 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2011 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_EFFECT_REDUCE_COLORS 0 + + +#include "imagelib/effects/kpEffectReduceColors.h" + +#include "kpLogCategories.h" + +//--------------------------------------------------------------------- + +static QImage::Format DepthToFormat (int depth) +{ + // These values are QImage's supported depths. + switch (depth) + { + case 1: + // (can be MSB instead, I suppose) + return QImage::Format_MonoLSB; + + case 8: + return QImage::Format_Indexed8; + + case 16: + return QImage::Format_ARGB4444_Premultiplied; + + case 24: + return QImage::Format_ARGB6666_Premultiplied; + + case 32: + return QImage::Format_ARGB32_Premultiplied; + + default: + Q_ASSERT (!"unknown depth"); + return QImage::Format_Invalid; + } +} + +//--------------------------------------------------------------------- + +// public static +QImage kpEffectReduceColors::convertImageDepth (const QImage &image, int depth, bool dither) +{ +#if DEBUG_KP_EFFECT_REDUCE_COLORS + qCDebug(kpLogImagelib) << "kpeffectreducecolors.cpp:ConvertImageDepth() changing image (w=" << image.width () + << ",h=" << image.height () + << ") depth from " << image.depth () + << " to " << depth + << " (dither=" << dither << ")" + << endl; +#endif + + if (image.isNull ()) { + return image; + } + + if (depth == image.depth ()) { + return image; + } + + +#if DEBUG_KP_EFFECT_REDUCE_COLORS && 0 + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + fprintf (stderr, " %08X", image.pixel (x, y)); + } + fprintf (stderr, "\n"); + } +#endif + + + // Hack around Qt's braindead QImage::convertToFormat(QImage::Format_MonoLSB, ...) + // (with dithering off) which produces pathetic results with an image that + // only has 2 colors - sometimes it just gives a completely black + // result (try yellow and white as input). Instead, we simply preserve + // the 2 colours. + // + // One use case is resaving a "color monochrome" image (<= 2 colors but + // not necessarily black & white). + if (depth == 1 && !dither) + { + #if DEBUG_KP_EFFECT_REDUCE_COLORS + qCDebug(kpLogImagelib) << "\tinvoking convert-to-depth 1 hack"; + #endif + QRgb color0 = 0, color1 = 0; + bool color0Valid = false, color1Valid = false; + + bool moreThan2Colors = false; + + QImage monoImage (image.width (), image.height (), QImage::Format_MonoLSB); + monoImage.setColorCount (2); + #if DEBUG_KP_EFFECT_REDUCE_COLORS + qCDebug(kpLogImagelib) << "\t\tinitialising output image w=" << monoImage.width () + << ",h=" << monoImage.height () + << ",d=" << monoImage.depth (); + #endif + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + // (this can be transparent) + QRgb imagePixel = image.pixel (x, y); + + if (color0Valid && imagePixel == color0) { + monoImage.setPixel (x, y, 0); + } + else if (color1Valid && imagePixel == color1) { + monoImage.setPixel (x, y, 1); + } + else if (!color0Valid) { + color0 = imagePixel; + color0Valid = true; + monoImage.setPixel (x, y, 0); + #if DEBUG_KP_EFFECT_REDUCE_COLORS + qCDebug(kpLogImagelib) << "\t\t\tcolor0=" << (int *) color0 + << " at x=" << x << ",y=" << y; + #endif + } + else if (!color1Valid) + { + color1 = imagePixel; + color1Valid = true; + monoImage.setPixel (x, y, 1); + #if DEBUG_KP_EFFECT_REDUCE_COLORS + qCDebug(kpLogImagelib) << "\t\t\tcolor1=" << (int *) color1 + << " at x=" << x << ",y=" << y; + #endif + } + else + { + #if DEBUG_KP_EFFECT_REDUCE_COLORS + qCDebug(kpLogImagelib) << "\t\t\timagePixel=" << (int *) imagePixel + << " at x=" << x << ",y=" << y + << " moreThan2Colors - abort hack"; + #endif + moreThan2Colors = true; + + // Dijkstra, this is clearer than double break'ing or + // a check in both loops + goto exit_loop; + } + } + } + exit_loop: + + if (!moreThan2Colors) + { + monoImage.setColor (0, color0Valid ? color0 : 0xFFFFFF); + monoImage.setColor (1, color1Valid ? color1 : 0x000000); + return monoImage; + } + } + + QImage retImage = image.convertToFormat (::DepthToFormat (depth), + Qt::AutoColor | + (dither ? Qt::DiffuseDither : Qt::ThresholdDither) | + Qt::ThresholdAlphaDither | + (dither ? Qt::PreferDither : Qt::AvoidDither)); +#if DEBUG_KP_EFFECT_REDUCE_COLORS + qCDebug(kpLogImagelib) << "\tformat: before=" << image.format () + << "after=" << retImage.format (); +#endif + +#if DEBUG_KP_EFFECT_REDUCE_COLORS && 0 + qCDebug(kpLogImagelib) << "After colour reduction:"; + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + fprintf (stderr, " %08X", image.pixel (x, y)); + } + fprintf (stderr, "\n"); + } +#endif + + return retImage; +} + +//--------------------------------------------------------------------- + +// public static +void kpEffectReduceColors::applyEffect (QImage *destPtr, int depth, bool dither) +{ + if (!destPtr) { + return; + } + + // You can't "reduce" to 32-bit since it's the highest depth. + if (depth != 1 && depth != 8) { + return; + } + + *destPtr = convertImageDepth(*destPtr, depth, dither); + + // internally we always use QImage::Format_ARGB32_Premultiplied and + // this effect is just an "effect" in that it changes the image (the look) somehow + // When one wants a different depth on the file, then he needs to save the image + // in that depth + *destPtr = destPtr->convertToFormat(QImage::Format_ARGB32_Premultiplied); +} + +//--------------------------------------------------------------------- + +QImage kpEffectReduceColors::applyEffect (const QImage &pm, int depth, bool dither) +{ + QImage ret = pm; + applyEffect (&ret, depth, dither); + return ret; +} + +//--------------------------------------------------------------------- diff --git a/imagelib/effects/kpEffectReduceColors.h b/imagelib/effects/kpEffectReduceColors.h new file mode 100644 index 0000000..b137a11 --- /dev/null +++ b/imagelib/effects/kpEffectReduceColors.h @@ -0,0 +1,51 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectReduceColors_H +#define kpEffectReduceColors_H + +#include + +// The specified must be supported by QImage. +class kpEffectReduceColors +{ +public: + // TODO: Why isn't applyEffect() for the public API sufficient? + // Ans: See TODO in kpDocument_Save.cpp. Maybe we should rename + // this method? + // + // Also, this can increase the image depth while applyEffect() + // will not. + static QImage convertImageDepth (const QImage &image, int depth, bool dither); + + static void applyEffect (QImage *destPixmapPtr, int depth, bool dither); + static QImage applyEffect (const QImage &pm, int depth, bool dither); +}; + + +#endif // kpEffectReduceColors_H diff --git a/imagelib/effects/kpEffectToneEnhance.cpp b/imagelib/effects/kpEffectToneEnhance.cpp new file mode 100644 index 0000000..1368020 --- /dev/null +++ b/imagelib/effects/kpEffectToneEnhance.cpp @@ -0,0 +1,308 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2006 Mike Gashler + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +// TODO: Clarence's code review + +#include "kpEffectToneEnhance.h" + +#include + +#include "kpLogCategories.h" + +#include "pixmapfx/kpPixmapFX.h" + + +#define RED_WEIGHT 77 +#define GREEN_WEIGHT 150 +#define BLUE_WEIGHT 29 + +#define MAX_TONE_VALUE ((RED_WEIGHT + GREEN_WEIGHT + BLUE_WEIGHT) * 255) +#define TONE_DROP_BITS 5 +#define TONE_MAP_SIZE ((MAX_TONE_VALUE >> TONE_DROP_BITS) + 1) +#define MAX_GRANULARITY 25 +#define MIN_IMAGE_DIM 3 + +//--------------------------------------------------------------------- + +inline unsigned int ComputeTone(unsigned int color) +{ + return RED_WEIGHT * static_cast (qRed(color)) + + GREEN_WEIGHT * static_cast (qGreen(color)) + + BLUE_WEIGHT * static_cast (qBlue(color)); +} + +//--------------------------------------------------------------------- + +inline unsigned int AdjustTone(unsigned int color, unsigned int oldTone, unsigned int newTone, double amount) +{ + return qRgba( + qMax(0, qMin(255, static_cast (amount * qRed(color) * newTone / oldTone + (1.0 - amount) * qRed(color)))), + qMax(0, qMin(255, static_cast (amount * qGreen(color) * newTone / oldTone + (1.0 - amount) * qGreen(color)))), + qMax(0, qMin(255, static_cast (amount * qBlue(color) * newTone / oldTone + (1.0 - amount) * qBlue(color)))), + qAlpha(color) + ); +} + +//--------------------------------------------------------------------- + +class kpEffectToneEnhanceApplier +{ + public: + kpEffectToneEnhanceApplier (); + ~kpEffectToneEnhanceApplier (); + + void BalanceImageTone(QImage* pImage, double granularity, double amount); + + protected: + int m_nToneMapGranularity, m_areaWid, m_areaHgt; + unsigned int m_nComputedWid, m_nComputedHgt; + // LOTODO: Use less error-prone QTL containers instead. + unsigned int* m_pHistogram; + unsigned int** m_pToneMaps; + + void DeleteToneMaps(); + unsigned int* MakeToneMap(QImage* pImage, int x, int y, int nGranularity); + void ComputeToneMaps(QImage* pImage, int nGranularity); + unsigned int InterpolateNewTone(QImage* pImage, unsigned int oldTone, int x, int y, int nGranularity); +}; + +//--------------------------------------------------------------------- + +kpEffectToneEnhanceApplier::kpEffectToneEnhanceApplier () +{ + m_nToneMapGranularity = 0; + m_areaWid = 0; + m_areaHgt = 0; + m_nComputedWid = 0; + m_nComputedHgt = 0; + m_pHistogram = new unsigned int[TONE_MAP_SIZE]; + m_pToneMaps = nullptr; +} + +//--------------------------------------------------------------------- + +kpEffectToneEnhanceApplier::~kpEffectToneEnhanceApplier () +{ + DeleteToneMaps(); + delete[] m_pHistogram; +} + +//--------------------------------------------------------------------- + +// protected +void kpEffectToneEnhanceApplier::DeleteToneMaps() +{ + int nToneMaps = m_nToneMapGranularity * m_nToneMapGranularity; + for(int i = 0; i < nToneMaps; i++) { + delete[] m_pToneMaps[i]; + } + delete[] m_pToneMaps; + m_pToneMaps = nullptr; + m_nToneMapGranularity = 0; +} + +//--------------------------------------------------------------------- + +// protected +unsigned int* kpEffectToneEnhanceApplier::MakeToneMap(QImage* pImage, int u, int v, int nGranularity) +{ + // Compute the region to make the tone map for + int xx, yy; + if(nGranularity > 1) + { + xx = u * (pImage->width() - 1) / (nGranularity - 1) - m_areaWid / 2; + if(xx < 0) { + xx = 0; + } + else if(xx + m_areaWid > pImage->width()) { + xx = pImage->width() - m_areaWid; + } + + yy = v * (pImage->width() - 1) / (nGranularity - 1) - m_areaHgt / 2; + + if(yy < 0) { + yy = 0; + } + else if(yy + m_areaHgt > pImage->height()) { + yy = pImage->height() - m_areaHgt; + } + } + else + { + xx = 0; + yy = 0; + } + + // Make a tone histogram for the region + memset(m_pHistogram, '\0', sizeof(unsigned int) * TONE_MAP_SIZE); + int x, y; + unsigned int tone; + for(y = 0; y < m_areaHgt; y++) + { + for(x = 0; x < m_areaWid; x++) + { + tone = ComputeTone(pImage->pixel(xx + x, yy + y)); + m_pHistogram[tone >> TONE_DROP_BITS]++; + } + } + + // Forward sum the tone histogram + int i{}; + for(i = 1; i < TONE_MAP_SIZE; i++) { + m_pHistogram[i] += m_pHistogram[i - 1]; + } + + // Compute the forward contribution to the tone map + auto total = m_pHistogram[i - 1]; + auto *pToneMap = new unsigned int[TONE_MAP_SIZE]; + for(i = 0; i < TONE_MAP_SIZE; i++) { + pToneMap[i] = static_cast (static_cast (m_pHistogram[i] * MAX_TONE_VALUE / total)); + } +/* + // Undo the forward sum and reverse sum the tone histogram + m_pHistogram[TONE_MAP_SIZE - 1] -= m_pHistogram[TONE_MAP_SIZE - 2]; + for(i = TONE_MAP_SIZE - 2; i > 0; i--) + { + m_pHistogram[i] -= m_pHistogram[i - 1]; + m_pHistogram[i] += m_pHistogram[i + 1]; + } + m_pHistogram[0] += m_pHistogram[1]; +*/ + return pToneMap; +} + +//--------------------------------------------------------------------- + +// protected +void kpEffectToneEnhanceApplier::ComputeToneMaps(QImage* pImage, int nGranularity) +{ + if(nGranularity == m_nToneMapGranularity && pImage->width() == + static_cast (m_nComputedWid) && pImage->height() == static_cast (m_nComputedHgt)) + { + return; // We've already computed tone maps for this granularity + } + DeleteToneMaps(); + m_pToneMaps = new unsigned int*[nGranularity * nGranularity]; + m_nToneMapGranularity = nGranularity; + m_nComputedWid = static_cast (pImage->width()); + m_nComputedHgt = static_cast (pImage->height()); + int u, v; + for(v = 0; v < nGranularity; v++) + { + for(u = 0; u < nGranularity; u++) { + m_pToneMaps[nGranularity * v + u] = MakeToneMap(pImage, u, v, nGranularity); + } + } +} + +//--------------------------------------------------------------------- + +// protected +unsigned int kpEffectToneEnhanceApplier::InterpolateNewTone(QImage* pImage, unsigned int oldTone, int x, int y, int nGranularity) +{ + oldTone = (oldTone >> TONE_DROP_BITS); + if(m_nToneMapGranularity <= 1) { + return m_pToneMaps[0][oldTone]; + } + auto u = x * (nGranularity - 1) / pImage->width(); + auto v = y * (nGranularity - 1) / pImage->height(); + auto x1y1 = m_pToneMaps[m_nToneMapGranularity * v + u][oldTone]; + auto x2y1 = m_pToneMaps[m_nToneMapGranularity * v + u + 1][oldTone]; + auto x1y2 = m_pToneMaps[m_nToneMapGranularity * (v + 1) + u][oldTone]; + auto x2y2 = m_pToneMaps[m_nToneMapGranularity * (v + 1) + u + 1][oldTone]; + auto hFac = x - (u * (pImage->width() - 1) / (nGranularity - 1)); + if(hFac > m_areaWid) { + hFac = m_areaWid; + } + unsigned int y1 = (x1y1 * (static_cast (m_areaWid) - static_cast (hFac)) + + x2y1 * static_cast (hFac)) / static_cast (m_areaWid); + + unsigned int y2 = (x1y2 * (static_cast (m_areaWid) - static_cast (hFac)) + + x2y2 * static_cast (hFac)) / static_cast (m_areaWid); + + int vFac = y - (v * (pImage->height() - 1) / (nGranularity - 1)); + if(vFac > m_areaHgt) { + vFac = m_areaHgt; + } + return (y1 * (static_cast (m_areaHgt) - static_cast (vFac)) + + y2 * static_cast (vFac)) / static_cast (m_areaHgt); +} + +//--------------------------------------------------------------------- + +// public +void kpEffectToneEnhanceApplier::BalanceImageTone(QImage* pImage, double granularity, double amount) +{ + if(pImage->width() < MIN_IMAGE_DIM || pImage->height() < MIN_IMAGE_DIM) { + return; // the image is not big enough to perform this operation + } + int nGranularity = static_cast (granularity * (MAX_GRANULARITY - 2)) + 1; + m_areaWid = pImage->width() / nGranularity; + if(m_areaWid < MIN_IMAGE_DIM) { + m_areaWid = MIN_IMAGE_DIM; + } + m_areaHgt = pImage->height() / nGranularity; + if(m_areaHgt < MIN_IMAGE_DIM) { + m_areaHgt = MIN_IMAGE_DIM; + } + ComputeToneMaps(pImage, nGranularity); + int x, y; + unsigned int oldTone, newTone, col; + for(y = 0; y < pImage->height(); y++) + { + for(x = 0; x < pImage->width(); x++) + { + col = pImage->pixel(x, y); + oldTone = ComputeTone(col); + newTone = InterpolateNewTone(pImage, oldTone, x, y, nGranularity); + pImage->setPixel(x, y, AdjustTone(col, oldTone, newTone, amount)); + } + } +} + +//--------------------------------------------------------------------- + +// public static +kpImage kpEffectToneEnhance::applyEffect (const kpImage &image, + double granularity, double amount) +{ + if (amount == 0.0) { + return image; + } + + QImage qimage(image); + + // OPT: Cache the calculated values? + kpEffectToneEnhanceApplier applier; + applier.BalanceImageTone (&qimage, granularity, amount); + + return qimage; +} + +//--------------------------------------------------------------------- diff --git a/imagelib/effects/kpEffectToneEnhance.h b/imagelib/effects/kpEffectToneEnhance.h new file mode 100644 index 0000000..a2503f1 --- /dev/null +++ b/imagelib/effects/kpEffectToneEnhance.h @@ -0,0 +1,61 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2006 Mike Gashler + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEffectToneEnhance_H +#define kpEffectToneEnhance_H + + +#include "imagelib/kpImage.h" + + +// +// Histogram Equalizer effect. +// +// It just divides out the color histogram from the pixel values. (So if +// you plot the color histogram after equalizing, you should get a nearly +// flat/uniform distribution.) +// +// The two sliders adjust: +// +// 1. The extent to which it equalizes +// 2. The local-ness of the equalization. (In other words, it computes just +// the histogram of the regions near the pixel it is adjusting.) +// +// ASSUMPTION: The given is not paletted (currently, this is the +// same as the screen mode not being paletted). +// +class kpEffectToneEnhance +{ +public: + static kpImage applyEffect (const kpImage &image, + double granularity, double amount); +}; + + +#endif // kpEffectToneEnhance_H diff --git a/imagelib/kpColor.cpp b/imagelib/kpColor.cpp new file mode 100644 index 0000000..12b2f3b --- /dev/null +++ b/imagelib/kpColor.cpp @@ -0,0 +1,317 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_COLOR 0 + + +#include "kpColor.h" + +#include + +#include "kpLogCategories.h" + +//--------------------------------------------------------------------- + +kpColor::kpColor() + : m_rgbaIsValid(false), + m_rgba(0), + m_colorCacheIsValid(false) +{ +} + +//--------------------------------------------------------------------- + +kpColor::kpColor (int red, int green, int blue, bool isTransparent) + : m_rgba(0), m_colorCacheIsValid(false) +{ +#if DEBUG_KP_COLOR + qCDebug(kpLogImagelib) << "kpColor::(r=" << red << ",g=" << green << ",b=" << blue + << ",isTrans=" << isTransparent << ")"; +#endif + if (red < 0 || red > 255 || + green < 0 || green > 255 || + blue < 0 || blue > 255) + { + qCCritical(kpLogImagelib) << "kpColor::(r=" << red + << ",g=" << green + << ",b=" << blue + << ",t=" << isTransparent + << ") passed out of range values"; + m_rgbaIsValid = false; + return; + } + + m_rgba = qRgba (red, green, blue, isTransparent ? 0 : 255/*opaque*/); + m_rgbaIsValid = true; +} + +//--------------------------------------------------------------------- + +kpColor::kpColor (const QRgb &rgba) + : m_colorCacheIsValid (false) +{ +#if DEBUG_KP_COLOR + qCDebug(kpLogImagelib) << "kpColor::(rgba=" << (int *) rgba << ")"; +#endif + m_rgba = rgba; + m_rgbaIsValid = true; +} + +//--------------------------------------------------------------------- + +kpColor::kpColor (const kpColor &rhs) + : m_rgbaIsValid (rhs.m_rgbaIsValid), + m_rgba (rhs.m_rgba), + m_colorCacheIsValid (rhs.m_colorCacheIsValid), + m_colorCache (rhs.m_colorCache) +{ +#if DEBUG_KP_COLOR + qCDebug(kpLogImagelib) << "kpColor::()"; +#endif +} + +//--------------------------------------------------------------------- + +// friend +QDataStream &operator<< (QDataStream &stream, const kpColor &color) +{ + stream << int (color.m_rgbaIsValid) << int (color.m_rgba); + + return stream; +} + +//--------------------------------------------------------------------- + +// friend +QDataStream &operator>> (QDataStream &stream, kpColor &color) +{ + int a, b; + stream >> a >> b; + color.m_rgbaIsValid = a; + color.m_rgba = static_cast (b); + + color.m_colorCacheIsValid = false; + + return stream; +} + +//--------------------------------------------------------------------- + +kpColor &kpColor::operator= (const kpColor &rhs) +{ + // (as soon as you add a ptr, you won't be complaining to me that this + // method was unnecessary :)) + + if (this == &rhs) { + return *this; + } + + m_rgbaIsValid = rhs.m_rgbaIsValid; + m_rgba = rhs.m_rgba; + m_colorCacheIsValid = rhs.m_colorCacheIsValid; + m_colorCache = rhs.m_colorCache; + + return *this; +} + +bool kpColor::operator== (const kpColor &rhs) const +{ + return isSimilarTo (rhs, kpColor::Exact); +} + +bool kpColor::operator!= (const kpColor &rhs) const +{ + return !(*this == rhs); +} + +//--------------------------------------------------------------------- + + +template +inline dtype square (dtype val) +{ + return val * val; +} + +//--------------------------------------------------------------------- + +// public static +int kpColor::processSimilarity (double colorSimilarity) +{ + // sqrt (dr ^ 2 + dg ^ 2 + db ^ 2) <= colorSimilarity * sqrt (255 ^ 2 * 3) + // dr ^ 2 + dg ^ 2 + db ^ 2 <= (colorSimilarity ^ 2) * (255 ^ 2 * 3) + + return int (square (colorSimilarity) * (square (255) * 3)); +} + +//--------------------------------------------------------------------- + +bool kpColor::isSimilarTo (const kpColor &rhs, int processedSimilarity) const +{ + // Are we the same? + if (this == &rhs) { + return true; + } + + + // Do we dither in terms of validity? + if (isValid () != rhs.isValid ()) { + return false; + } + + // Are both of us invalid? + if (!isValid ()) { + return true; + } + + // --- both are now valid --- + + if (m_rgba == rhs.m_rgba) { + return true; + } + + if (processedSimilarity == kpColor::Exact) { + return false; + } + + + return (square (qRed (m_rgba) - qRed (rhs.m_rgba)) + + square (qGreen (m_rgba) - qGreen (rhs.m_rgba)) + + square (qBlue (m_rgba) - qBlue (rhs.m_rgba)) + <= processedSimilarity); + +} + +//--------------------------------------------------------------------- + +// public +bool kpColor::isValid () const +{ + return m_rgbaIsValid; +} + +//--------------------------------------------------------------------- + +// public +int kpColor::red () const +{ + if (!m_rgbaIsValid) + { + qCCritical(kpLogImagelib) << "kpColor::red() called with invalid kpColor"; + return 0; + } + + return qRed (m_rgba); +} + +//--------------------------------------------------------------------- + +// public +int kpColor::green () const +{ + if (!m_rgbaIsValid) + { + qCCritical(kpLogImagelib) << "kpColor::green() called with invalid kpColor"; + return 0; + } + + return qGreen (m_rgba); +} + +//--------------------------------------------------------------------- + +// public +int kpColor::blue () const +{ + if (!m_rgbaIsValid) + { + qCCritical(kpLogImagelib) << "kpColor::blue() called with invalid kpColor"; + return 0; + } + + return qBlue (m_rgba); +} + +//--------------------------------------------------------------------- + +// public +int kpColor::alpha () const +{ + if (!m_rgbaIsValid) + { + qCCritical(kpLogImagelib) << "kpColor::alpha() called with invalid kpColor"; + return 0; + } + + return qAlpha (m_rgba); +} + +//--------------------------------------------------------------------- + +// public +bool kpColor::isTransparent () const +{ + return (alpha () == 0); +} + +//--------------------------------------------------------------------- + +// public +QRgb kpColor::toQRgb () const +{ + if (!m_rgbaIsValid) + { + qCCritical(kpLogImagelib) << "kpColor::toQRgb() called with invalid kpColor"; + return 0; + } + + return m_rgba; +} + +//--------------------------------------------------------------------- + +// public +QColor kpColor::toQColor () const +{ + if (!m_rgbaIsValid) + { + qCCritical(kpLogImagelib) << "kpColor::toQColor() called with invalid kpColor"; + return Qt::black; + } + + if (m_colorCacheIsValid) { + return m_colorCache; + } + + m_colorCache = QColor(qRed(m_rgba), qGreen(m_rgba), qBlue(m_rgba), qAlpha(m_rgba)); + m_colorCacheIsValid = true; + + return m_colorCache; +} + +//--------------------------------------------------------------------- diff --git a/imagelib/kpColor.h b/imagelib/kpColor.h new file mode 100644 index 0000000..2a100a6 --- /dev/null +++ b/imagelib/kpColor.h @@ -0,0 +1,156 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_COLOR_H +#define KP_COLOR_H + + +#include + + +class QDataStream; + + +// +// kpColor is an object-oriented abstraction of QRgb, for document image data. +// In the future, other color models such as +// 8-bit indexed will be supported. It also provides better error handling, +// reporting (noisy qCCritical(kpLogImagelib)'s) and recovery compared to Qt. This abstraction +// will allow us to eventually dump the Qt paint routines. +// +// In general, you should pass around kpColor objects instead of QRgb +// and QColor. Only convert an opaque kpColor to a QColor (using toQColor()) +// if you need to draw something on-screen. +// +// Constructing a kpColor object from QColor is usually wrong since QColor's +// come from on-screen pixels, which may lack the full color resolution of +// kpColor, due to the limited color range on e.g. a 16-bit screen. +// +class kpColor +{ +public: + kpColor (); + kpColor (int red, int green, int blue, bool isTransparent = false); + kpColor (const QRgb &rgba); + kpColor (const kpColor &rhs); + friend QDataStream &operator<< (QDataStream &stream, const kpColor &color); + friend QDataStream &operator>> (QDataStream &stream, kpColor &color); + kpColor &operator= (const kpColor &rhs); + bool operator== (const kpColor &rhs) const; + bool operator!= (const kpColor &rhs) const; + + +// +// Constants +// +public: + // "lhs.isSimilarTo (rhs, kpColor::Exact)" is exactly the same as calling + // "lhs == rhs". + static const int Exact; + + static const kpColor Invalid; + static const kpColor Transparent; + + + // + // Primary Colors + B&W + // + + static const kpColor Red, Green, Blue; + static const kpColor Black, White; + + + // + // Full-brightness Colors + // + + static const kpColor Yellow, Purple, Aqua; + + + // + // Mixed Colors + // + + static const kpColor Gray, LightGray, Orange; + + + // + // Pastel Colors + // + + static const kpColor Pink, LightGreen, LightBlue, Tan; + + + // + // Dark Colors + // + + static const kpColor DarkRed; + + // (identical) + static const kpColor DarkOrange, Brown; + + static const kpColor DarkYellow, DarkGreen, DarkAqua, DarkBlue, + DarkPurple, DarkGray; + + +public: + static int processSimilarity (double colorSimilarity); + // Usage: isSimilarTo (rhs, kpColor::processSimilarity (.1)) checks for + // Color Similarity within 10% + bool isSimilarTo (const kpColor &rhs, int processedSimilarity) const; + + bool isValid () const; + + int red () const; + int green () const; + int blue () const; + int alpha () const; + bool isTransparent () const; + + // Cast operators will most likely result in careless conversions so + // use explicit functions instead: + QRgb toQRgb () const; + + QColor toQColor () const; + +private: + // Catch accidental call to "const QRgb &rgba" (unsigned int) ctor + // by e.g. "kpColor(Qt::black)" (Qt::black is an enum element that can cast + // to "unsigned int"). + kpColor (Qt::GlobalColor color); + + bool m_rgbaIsValid; + QRgb m_rgba; + + mutable bool m_colorCacheIsValid; + mutable QColor m_colorCache; +}; + + +#endif // KP_COLOR_H diff --git a/imagelib/kpColor_Constants.cpp b/imagelib/kpColor_Constants.cpp new file mode 100644 index 0000000..b029228 --- /dev/null +++ b/imagelib/kpColor_Constants.cpp @@ -0,0 +1,117 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_COLOR 0 + + +#include "kpColor.h" + + +static inline int RoundUp2 (int val) +{ + return val % 2 ? val + 1 : val; +} + +static inline int Bound0_255 (int val) +{ + return qBound (0, val, 255); +} + + +enum +{ + BlendDark = 25, + BlendNormal = 50, + BlendLight = 75, + BlendAdd = 100 +}; + +// Adds the 2 given colors together and then multiplies by the given . +static inline kpColor Blend (const kpColor &a, const kpColor &b, + int percent = ::BlendNormal) +{ + return kpColor (::Bound0_255 (::RoundUp2 (a.red () + b.red ()) * percent / 100), + ::Bound0_255 (::RoundUp2 (a.green () + b.green ()) * percent / 100), + ::Bound0_255 (::RoundUp2 (a.blue () + b.blue ()) * percent / 100)); +} + +static inline kpColor Add (const kpColor &a, const kpColor &b) +{ + return ::Blend (a, b, ::BlendAdd); +} + + +// (intentionally _not_ an HSV darkener) +static inline kpColor Dark (const kpColor &color) +{ + return ::Blend (color, kpColor::Black); +} + + +// public static +const int kpColor::Exact = 0; + +// public static +const kpColor kpColor::Invalid; // LOTODO: what's wrong with explicitly specifying () constructor? +const kpColor kpColor::Transparent (0, 0, 0, true/*isTransparent*/); + + +// +// Make our own colors in case weird ones like "Qt::cyan" +// (turquoise) get changed by Qt. +// + + +const kpColor kpColor::Red (255, 0, 0); +const kpColor kpColor::Green (0, 255, 0); +const kpColor kpColor::Blue (0, 0, 255); +const kpColor kpColor::Black (0, 0, 0); +const kpColor kpColor::White (255, 255, 255); + +const kpColor kpColor::Yellow = ::Add (kpColor::Red, kpColor::Green); +const kpColor kpColor::Purple = ::Add (kpColor::Red, kpColor::Blue); +const kpColor kpColor::Aqua = ::Add (kpColor::Green, kpColor::Blue); + +const kpColor kpColor::Gray = ::Blend (kpColor::Black, kpColor::White); +const kpColor kpColor::LightGray = ::Blend (kpColor::Gray, kpColor::White); +const kpColor kpColor::Orange = ::Blend (kpColor::Red, kpColor::Yellow); + +const kpColor kpColor::Pink = ::Blend (kpColor::Red, kpColor::White); +const kpColor kpColor::LightGreen = ::Blend (kpColor::Green, kpColor::White); +const kpColor kpColor::LightBlue = ::Blend (kpColor::Blue, kpColor::White); +const kpColor kpColor::Tan = ::Blend (kpColor::Yellow, kpColor::White); + +const kpColor kpColor::DarkRed = ::Dark (kpColor::Red); +const kpColor kpColor::DarkOrange = ::Dark (kpColor::Orange); +const kpColor kpColor::Brown = kpColor::DarkOrange; +const kpColor kpColor::DarkYellow = ::Dark (kpColor::Yellow); +const kpColor kpColor::DarkGreen = ::Dark (kpColor::Green); +const kpColor kpColor::DarkAqua = ::Dark (kpColor::Aqua); +const kpColor kpColor::DarkBlue = ::Dark (kpColor::Blue); +const kpColor kpColor::DarkPurple = ::Dark (kpColor::Purple); +const kpColor kpColor::DarkGray = ::Dark (kpColor::Gray); diff --git a/imagelib/kpDocumentMetaInfo.cpp b/imagelib/kpDocumentMetaInfo.cpp new file mode 100644 index 0000000..d31577f --- /dev/null +++ b/imagelib/kpDocumentMetaInfo.cpp @@ -0,0 +1,277 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "kpDocumentMetaInfo.h" + +#include + +#include + +#include "kpLogCategories.h" + +#include "kpDefs.h" + + +// +// Constants which "ought to be enough for anybody" +// LOTODO: Maybe there are some QImage constants somewhere? +// + +// public static + +// (round up to guarantee at least 1 dot per inch) +const int kpDocumentMetaInfo::MinDotsPerMeter = + int (std::ceil (1/*single dot per inch - a very low DPI*/ * KP_INCHES_PER_METER) + 0.1); + +const int kpDocumentMetaInfo::MaxDotsPerMeter = + int ((600 * 100)/*a lot of DPI*/ * KP_INCHES_PER_METER); + +// public static +const int kpDocumentMetaInfo::MaxOffset = (4000/*big image*/ * 100)/*a very big image*/; +const int kpDocumentMetaInfo::MinOffset = -kpDocumentMetaInfo::MaxOffset; + +//--------------------------------------------------------------------- + +struct kpDocumentMetaInfoPrivate +{ + int m_dotsPerMeterX{}, m_dotsPerMeterY{}; + QPoint m_offset; + + QMap m_textMap; +}; + +//--------------------------------------------------------------------- + +// public +kpDocumentMetaInfo::kpDocumentMetaInfo () + : d (new kpDocumentMetaInfoPrivate ()) +{ + d->m_dotsPerMeterX = 0; + d->m_dotsPerMeterY = 0; + d->m_offset = QPoint (0, 0); +} + +//--------------------------------------------------------------------- + +kpDocumentMetaInfo::kpDocumentMetaInfo (const kpDocumentMetaInfo &rhs) + : d (new kpDocumentMetaInfoPrivate ()) +{ + d->m_dotsPerMeterX = rhs.dotsPerMeterX (); + d->m_dotsPerMeterY = rhs.dotsPerMeterY (); + d->m_offset = rhs.offset (); + d->m_textMap = rhs.textMap (); +} + +//--------------------------------------------------------------------- + +// public +kpDocumentMetaInfo::~kpDocumentMetaInfo () +{ + delete d; +} + +//--------------------------------------------------------------------- + +// public +bool kpDocumentMetaInfo::operator== (const kpDocumentMetaInfo &rhs) const +{ + return (d->m_dotsPerMeterX == rhs.d->m_dotsPerMeterX && + d->m_dotsPerMeterY == rhs.d->m_dotsPerMeterY && + d->m_offset == rhs.d->m_offset && + d->m_textMap == rhs.d->m_textMap); +} + +//--------------------------------------------------------------------- + +// public +bool kpDocumentMetaInfo::operator!= (const kpDocumentMetaInfo &rhs) const +{ + return !(*this == rhs); +} + +//--------------------------------------------------------------------- + +// public +kpDocumentMetaInfo &kpDocumentMetaInfo::operator= (const kpDocumentMetaInfo &rhs) +{ + if (this == &rhs) { + return *this; + } + + d->m_dotsPerMeterX = rhs.dotsPerMeterX (); + d->m_dotsPerMeterY = rhs.dotsPerMeterY (); + d->m_offset = rhs.offset (); + d->m_textMap = rhs.textMap (); + + return *this; +} + +//--------------------------------------------------------------------- + +// public +void kpDocumentMetaInfo::printDebug (const QString &prefix) const +{ + const QString usedPrefix = !prefix.isEmpty() ? QString(prefix + QLatin1String(":")) : QString(); + + qCDebug(kpLogImagelib) << usedPrefix; + + qCDebug(kpLogImagelib) << "dotsPerMeter X=" << dotsPerMeterX () + << " Y=" << dotsPerMeterY () + << " offset=" << offset (); + + foreach (const QString &key, textKeys()) + qCDebug(kpLogImagelib) << "key=" << key << " text=" << text(key); + + qCDebug(kpLogImagelib) << usedPrefix << "ENDS"; +} + +//--------------------------------------------------------------------- + +// public +kpCommandSize::SizeType kpDocumentMetaInfo::size () const +{ + kpCommandSize::SizeType ret = 0; + + for (const auto &key : d->m_textMap.keys ()) + { + ret += kpCommandSize::StringSize (key) + + kpCommandSize::StringSize (d->m_textMap [key]); + } + + // We don't know what the QMap size overhead is so overestimate the size + // rather than underestimating it. + // LOTODO: Find the proper size in bytes. + return ret * 3; +} + +//--------------------------------------------------------------------- + +// public +int kpDocumentMetaInfo::dotsPerMeterX () const +{ + return d->m_dotsPerMeterX; +} + +//--------------------------------------------------------------------- + +// public +void kpDocumentMetaInfo::setDotsPerMeterX (int val) +{ + // Unspecified resolution? + if (val == 0) + { + d->m_dotsPerMeterX = 0; + return; + } + + d->m_dotsPerMeterX = qBound (MinDotsPerMeter, val, MaxDotsPerMeter); +} + +//--------------------------------------------------------------------- + +// public +int kpDocumentMetaInfo::dotsPerMeterY () const +{ + return d->m_dotsPerMeterY; +} + +//--------------------------------------------------------------------- + +// public +void kpDocumentMetaInfo::setDotsPerMeterY (int val) +{ + // Unspecified resolution? + if (val == 0) + { + d->m_dotsPerMeterY = 0; + return; + } + + d->m_dotsPerMeterY = qBound (MinDotsPerMeter, val, MaxDotsPerMeter); +} + +//--------------------------------------------------------------------- + +// public +QPoint kpDocumentMetaInfo::offset () const +{ + return d->m_offset; +} + +//--------------------------------------------------------------------- + +// public +void kpDocumentMetaInfo::setOffset (const QPoint &point) +{ + const int x = qBound (MinOffset, point.x (), MaxOffset); + const int y = qBound (MinOffset, point.y (), MaxOffset); + + d->m_offset = QPoint (x, y); +} + +//--------------------------------------------------------------------- + +// public +QMap kpDocumentMetaInfo::textMap () const +{ + return d->m_textMap; +} + +//--------------------------------------------------------------------- + +// public +QList kpDocumentMetaInfo::textKeys () const +{ + return d->m_textMap.keys (); +} + +//--------------------------------------------------------------------- + +// public +QString kpDocumentMetaInfo::text (const QString &key) const +{ + if (key.isEmpty ()) { + return {}; + } + + return d->m_textMap [key]; +} + +//--------------------------------------------------------------------- + +// public +void kpDocumentMetaInfo::setText (const QString &key, const QString &value) +{ + if (key.isEmpty ()) { + return; + } + + d->m_textMap [key] = value; +} + +//--------------------------------------------------------------------- diff --git a/imagelib/kpDocumentMetaInfo.h b/imagelib/kpDocumentMetaInfo.h new file mode 100644 index 0000000..50430a8 --- /dev/null +++ b/imagelib/kpDocumentMetaInfo.h @@ -0,0 +1,107 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_DOCUMENT_META_INFO_H +#define KP_DOCUMENT_META_INFO_H + + +#include +#include +#include +#include + +#include "commands/kpCommandSize.h" + + +class QPoint; + + +class kpDocumentMetaInfo +{ +public: + kpDocumentMetaInfo (); + kpDocumentMetaInfo (const kpDocumentMetaInfo &rhs); + virtual ~kpDocumentMetaInfo (); + + bool operator== (const kpDocumentMetaInfo &rhs) const; + bool operator!= (const kpDocumentMetaInfo &rhs) const; + + kpDocumentMetaInfo &operator= (const kpDocumentMetaInfo &rhs); + + + void printDebug (const QString &prefix) const; + + + kpCommandSize::SizeType size () const; + + + // + // Constants (enforced by methods) + // + + static const int MinDotsPerMeter, MaxDotsPerMeter; + static const int MinOffset, MaxOffset; + + + // See QImage documentation + + // is 0 if the resolution is unspecified. + // Else, these methods automatically bound to be between + // MinDotsPerMeter ... MaxDotsPerMeter inclusive. + int dotsPerMeterX () const; + void setDotsPerMeterX (int val); + + // is 0 if the resolution is unspecified. + // Else, these methods automatically bound to be between + // MinDotsPerMeter ... MaxDotsPerMeter inclusive. + int dotsPerMeterY () const; + void setDotsPerMeterY (int val); + + + // These methods automatically bound each of X and Y to be between + // MinOffset and MaxOffset inclusive. + QPoint offset () const; + void setOffset (const QPoint &point); + + + QMap textMap () const; + QList textKeys () const; + + // (if is empty, it returns an empty string) + QString text (const QString &key) const; + + // (if is empty, the operation is ignored) + void setText (const QString &key, const QString &value); + + +private: + struct kpDocumentMetaInfoPrivate *d; +}; + + +#endif // KP_DOCUMENT_META_INFO_H diff --git a/imagelib/kpFloodFill.cpp b/imagelib/kpFloodFill.cpp new file mode 100644 index 0000000..0f67854 --- /dev/null +++ b/imagelib/kpFloodFill.cpp @@ -0,0 +1,423 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_FLOOD_FILL 0 + + +#include "kpFloodFill.h" + +#include +#include +#include +#include + +#include "kpLogCategories.h" + +#include "kpColor.h" +#include "kpDefs.h" +#include "pixmapfx/kpPixmapFX.h" +#include "tools/kpTool.h" + +//--------------------------------------------------------------------- + +class kpFillLine +{ +public: + kpFillLine (int y = -1, int x1 = -1, int x2 = -1) + : m_y (y), m_x1 (x1), m_x2 (x2) + { + } + + static kpCommandSize::SizeType size () + { + return sizeof (kpFillLine); + } + + int m_y, m_x1, m_x2; +}; + +//--------------------------------------------------------------------- + +static kpCommandSize::SizeType FillLinesListSize (const QList &fillLines) +{ + return (fillLines.size () * kpFillLine::size ()); +} + +//--------------------------------------------------------------------- + +struct kpFloodFillPrivate +{ + // + // Copy of whatever was passed to the constructor. + // + + kpImage *imagePtr = nullptr; + int x = 0, y = 0; + kpColor color; + int processedColorSimilarity = 0; + + + // + // Set by Step 1. + // + + kpColor colorToChange; + + + // + // Set by Step 2. + // + + QList fillLines; + QList < QList > fillLinesCache; + + QRect boundingRect; + + bool prepared = false; +}; + +//--------------------------------------------------------------------- + +kpFloodFill::kpFloodFill (kpImage *image, int x, int y, + const kpColor &color, int processedColorSimilarity) + : d (new kpFloodFillPrivate ()) +{ + d->imagePtr = image; + d->x = x; + d->y = y; + d->color = color; + d->processedColorSimilarity = processedColorSimilarity; + + d->prepared = false; +} + +//--------------------------------------------------------------------- + +kpFloodFill::~kpFloodFill () +{ + delete d; +} + +//--------------------------------------------------------------------- + +// public +kpColor kpFloodFill::color () const +{ + return d->color; +} + +//--------------------------------------------------------------------- + +// public +int kpFloodFill::processedColorSimilarity () const +{ + return d->processedColorSimilarity; +} + +//--------------------------------------------------------------------- + +// public +kpCommandSize::SizeType kpFloodFill::size () const +{ + kpCommandSize::SizeType fillLinesCacheSize = 0; + for (const auto &linesList : d->fillLinesCache) + { + fillLinesCacheSize += ::FillLinesListSize (linesList); + } + + return ::FillLinesListSize(d->fillLines) + + kpCommandSize::QImageSize(d->imagePtr) + + fillLinesCacheSize; +} + +//--------------------------------------------------------------------- + +// public +void kpFloodFill::prepareColorToChange () +{ + if (d->colorToChange.isValid ()) { + return; + } + +#if DEBUG_KP_FLOOD_FILL && 1 + qCDebug(kpLogImagelib) << "kpFloodFill::prepareColorToChange()"; +#endif + + d->colorToChange = kpPixmapFX::getColorAtPixel (*d->imagePtr, QPoint (d->x, d->y)); +} + +//--------------------------------------------------------------------- + +// public +kpColor kpFloodFill::colorToChange () +{ + prepareColorToChange (); + + return d->colorToChange; +} + +//--------------------------------------------------------------------- + +// Derived from the zSprite2 Graphics Engine + +// private +kpColor kpFloodFill::pixelColor (int x, int y, bool *beenHere) const +{ + if (beenHere) { + *beenHere = false; + } + + Q_ASSERT (y >= 0 && y < static_cast (d->fillLinesCache.count ())); + + for (const auto &line : d->fillLinesCache [y]) + { + if (x >= line.m_x1 && x <= line.m_x2) + { + if (beenHere) { + *beenHere = true; + } + return d->color; + } + } + + return kpPixmapFX::getColorAtPixel (*(d->imagePtr), QPoint (x, y)); +} + +//--------------------------------------------------------------------- + +// private +bool kpFloodFill::shouldGoTo (int x, int y) const +{ + bool beenThere; + const kpColor col = pixelColor (x, y, &beenThere); + + return (!beenThere && col.isSimilarTo (d->colorToChange, d->processedColorSimilarity)); +} + +//--------------------------------------------------------------------- + +// private +int kpFloodFill::findMinX (int y, int x) const +{ + for (;;) + { + if (x < 0) { + return 0; + } + + if (shouldGoTo (x, y)) { + x--; + } + else { + return x + 1; + } + } +} + +//--------------------------------------------------------------------- + +// private +int kpFloodFill::findMaxX (int y, int x) const +{ + for (;;) + { + if (x > d->imagePtr->width () - 1) { + return d->imagePtr->width () - 1; + } + + if (shouldGoTo (x, y)) { + x++; + } + else { + return x - 1; + } + } +} + +//--------------------------------------------------------------------- + +// private +void kpFloodFill::addLine (int y, int x1, int x2) +{ +#if DEBUG_KP_FLOOD_FILL && 0 + qCDebug(kpLogImagelib) << "kpFillCommand::fillAddLine (" + << y << "," << x1 << "," << x2 << ")" << endl; +#endif + + d->fillLines.append (kpFillLine (y, x1, x2)); + d->fillLinesCache [y].append ( + kpFillLine (y/*OPT: can determine from array index*/, x1, x2)); + d->boundingRect = d->boundingRect.united (QRect (QPoint (x1, y), QPoint (x2, y))); +} + +//--------------------------------------------------------------------- + +// private +void kpFloodFill::findAndAddLines (const kpFillLine &fillLine, int dy) +{ + // out of bounds? + if (fillLine.m_y + dy < 0 || fillLine.m_y + dy >= d->imagePtr->height ()) { + return; + } + + for (int xnow = fillLine.m_x1; xnow <= fillLine.m_x2; xnow++) + { + // At current position, right colour? + if (shouldGoTo (xnow, fillLine.m_y + dy)) + { + // Find minimum and maximum x values + int minxnow = findMinX (fillLine.m_y + dy, xnow); + int maxxnow = findMaxX (fillLine.m_y + dy, xnow); + + // Draw line + addLine (fillLine.m_y + dy, minxnow, maxxnow); + + // Move x pointer + xnow = maxxnow; + } + } +} + +//--------------------------------------------------------------------- + +// public +void kpFloodFill::prepare () +{ + if (d->prepared) { + return; + } + +#if DEBUG_KP_FLOOD_FILL && 1 + qCDebug(kpLogImagelib) << "kpFloodFill::prepare()"; +#endif + + prepareColorToChange (); + + d->boundingRect = QRect (); + + +#if DEBUG_KP_FLOOD_FILL && 1 + qCDebug(kpLogImagelib) << "\tperforming NOP check"; +#endif + + // get the color we need to replace + if (d->processedColorSimilarity == 0 && d->color == d->colorToChange) + { + // need to do absolutely nothing (this is a significant optimization + // for people who randomly click a lot over already-filled areas) + d->prepared = true; // sync with all "return true"'s + return; + } + +#if DEBUG_KP_FLOOD_FILL && 1 + qCDebug(kpLogImagelib) << "\tcreating fillLinesCache"; +#endif + + // ready cache + for (int i = 0; i < d->imagePtr->height (); i++) { + d->fillLinesCache.append (QList ()); + } + +#if DEBUG_KP_FLOOD_FILL && 1 + qCDebug(kpLogImagelib) << "\tcreating fill lines"; +#endif + + // draw initial line + addLine (d->y, findMinX (d->y, d->x), findMaxX (d->y, d->x)); + + for (int i = 0; i < d->fillLines.count(); i++) + { + kpFillLine &fl = d->fillLines[i]; + + #if DEBUG_KP_FLOOD_FILL && 0 + qCDebug(kpLogImagelib) << "Expanding from y=" << fl.m_y + << " x1=" << fl.m_x1 + << " x2=" << fl.m_x2 + << endl; + #endif + + // + // Make more lines above and below current line. + // + // WARNING: Adds to end of "fillLines" (the list we are iterating through). + findAndAddLines(fl, -1); + findAndAddLines(fl, +1); + } + +#if DEBUG_KP_FLOOD_FILL && 1 + qCDebug(kpLogImagelib) << "\tfinalising memory usage"; +#endif + + // finalize memory usage + d->fillLinesCache.clear (); + + d->prepared = true; // sync with all "return true"'s +} + +//--------------------------------------------------------------------- + +// public +QRect kpFloodFill::boundingRect () +{ + prepare (); + + return d->boundingRect; +} + +//--------------------------------------------------------------------- +// public +void kpFloodFill::fill() +{ + prepare(); + + QApplication::setOverrideCursor(Qt::WaitCursor); + + QPainter painter(d->imagePtr); + + // by definition, flood fill with a fully transparent color erases the pixels + // and sets them to be fully transparent + if ( d->color.isTransparent() ) { + painter.setCompositionMode(QPainter::CompositionMode_Clear); + } + + painter.setPen(d->color.toQColor()); + + for (const auto &l : d->fillLines) + { + if ( l.m_x1 == l.m_x2 ) { + painter.drawPoint(l.m_x1, l.m_y); + } + else { + painter.drawLine(l.m_x1, l.m_y, l.m_x2, l.m_y); + } + } + + QApplication::restoreOverrideCursor(); +} + +//--------------------------------------------------------------------- diff --git a/imagelib/kpFloodFill.h b/imagelib/kpFloodFill.h new file mode 100644 index 0000000..3568bc5 --- /dev/null +++ b/imagelib/kpFloodFill.h @@ -0,0 +1,128 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_FLOOD_FILL_H +#define KP_FLOOD_FILL_H + + +#include "kpImage.h" +#include "commands/kpCommandSize.h" + + +class kpColor; +class kpFillLine; + + +struct kpFloodFillPrivate; + +class kpFloodFill +{ +public: + kpFloodFill (kpImage *image, int x, int y, + const kpColor &color, + int processedColorSimilarity); + ~kpFloodFill (); + + + // + // Spits back constructor arguments. + // + +public: + kpColor color () const; + int processedColorSimilarity () const; + + +public: + // Used for calculating the size of a command in the command history. + kpCommandSize::SizeType size () const; + + + // + // Step 1: Determines the colour that will be changed to color(). + // + // Very fast. + // + +public: + void prepareColorToChange (); + + // (may invoke prepareColorToChange()). + kpColor colorToChange (); + + + // + // Step 2: Determines the scanlines / pixels that will be changed to color(). + // + // The slowest part of the whole fill operation. + // + // Before calling a Step 2 function, you don't have to (but you can) + // call any of the functions in Step 1. + // + +private: + kpColor pixelColor (int x, int y, bool *beenHere = nullptr) const; + bool shouldGoTo (int x, int y) const; + + // Finds the minimum x value at a certain line to be filled. + int findMinX (int y, int x) const; + + // Finds the maximum x value at a certain line to be filled. + int findMaxX (int y, int x) const; + + void addLine (int y, int x1, int x2); + void findAndAddLines (const kpFillLine &fillLine, int dy); + +public: + // (may invoke Step 1's prepareColorToChange()) + void prepare (); + + // (may invoke prepare()) + QRect boundingRect (); + + + // + // Step 3: Draws the lines identified in Step 2 in color(). + // + // Between the speeds of Step 2 and Step 1. + // + // Before calling a Step 3 function, you don't have to (but you can) + // call any of the functions in Step 1 or 2. + // + +public: + // (may invoke Step 2's prepare()) + void fill (); + + +private: + kpFloodFillPrivate * const d; +}; + + +#endif // KP_FLOOD_FILL_H diff --git a/imagelib/kpImage.h b/imagelib/kpImage.h new file mode 100644 index 0000000..428f94b --- /dev/null +++ b/imagelib/kpImage.h @@ -0,0 +1,38 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_IMAGE_H +#define KP_IMAGE_H + + +#include + +typedef QImage kpImage; + + +#endif // KP_IMAGE_H diff --git a/imagelib/kpPainter.cpp b/imagelib/kpPainter.cpp new file mode 100644 index 0000000..5be9ed4 --- /dev/null +++ b/imagelib/kpPainter.cpp @@ -0,0 +1,512 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_PAINTER 0 + + +#include "kpPainter.h" + +#include "pixmapfx/kpPixmapFX.h" +#include "tools/kpTool.h" +#include "tools/flow/kpToolFlowBase.h" + +#include + +#include +#include +#include + +#include "kpLogCategories.h" + +//--------------------------------------------------------------------- + +// public static +bool kpPainter::pointsAreCardinallyAdjacent (const QPoint &p, const QPoint &q) +{ + int dx = qAbs (p.x () - q.x ()); + int dy = qAbs (p.y () - q.y ()); + + return (dx + dy == 1); +} + +//--------------------------------------------------------------------- + +// public static +QList kpPainter::interpolatePoints (const QPoint &startPoint, + const QPoint &endPoint, + bool cardinalAdjacency, + double probability) +{ +#if DEBUG_KP_PAINTER + qCDebug(kpLogImagelib) << "CALL(startPoint=" << startPoint + << ",endPoint=" << endPoint << ")"; +#endif + + QList ret; + + Q_ASSERT (probability >= 0.0 && probability <= 1.0); + const int probabilityTimes1000 = qRound (probability * 1000); +#define SHOULD_DRAW() ( (probabilityTimes1000 == 1000) /*avoid QRandomGenerator call*/ || \ + (QRandomGenerator::global()->bounded(1000) < probabilityTimes1000) ) + + + // Derived from the zSprite2 Graphics Engine. + // "MODIFIED" comment shows deviation from zSprite2 and Bresenham's line + // algorithm. + + const int x1 = startPoint.x (), + y1 = startPoint.y (), + x2 = endPoint.x (), + y2 = endPoint.y (); + + // Difference of x and y values + const int dx = x2 - x1; + const int dy = y2 - y1; + + // Absolute values of differences + const int ix = qAbs (dx); + const int iy = qAbs (dy); + + // Larger of the x and y differences + const int inc = ix > iy ? ix : iy; + + // Plot location + int plotx = x1; + int ploty = y1; + + int x = 0; + int y = 0; + + if (SHOULD_DRAW ()) { + ret.append (QPoint (plotx, ploty)); + } + + + for (int i = 0; i <= inc; i++) + { + // oldplotx is equally as valid but would look different + // (but nobody will notice which one it is) + const int oldploty = ploty; + int plot = 0; + + x += ix; + y += iy; + + if (x > inc) + { + plot++; + x -= inc; + + if (dx < 0) { + plotx--; + } + else { + plotx++; + } + } + + if (y > inc) + { + plot++; + y -= inc; + + if (dy < 0) { + ploty--; + } + else { + ploty++; + } + } + + if (plot) + { + if (cardinalAdjacency && plot == 2) + { + // MODIFIED: Every point is + // horizontally or vertically adjacent to another point (if there + // is more than 1 point, of course). This is in contrast to the + // ordinary line algorithm which can create diagonal adjacencies. + + if (SHOULD_DRAW ()) { + ret.append (QPoint (plotx, oldploty)); + } + } + + if (SHOULD_DRAW ()) { + ret.append (QPoint (plotx, ploty)); + } + } + } + +#undef SHOULD_DRAW + + return ret; +} + +//--------------------------------------------------------------------- + +// public static +void kpPainter::fillRect (kpImage *image, + int x, int y, int width, int height, + const kpColor &color) +{ + kpPixmapFX::fillRect (image, x, y, width, height, color); +} + +//--------------------------------------------------------------------- + + +// are operating on the original image +// (the original image is not passed to this function). +// +// = subset of the original image containing all the pixels in +// +// = the rectangle, relative to the painters, whose pixels we +// want to change +static bool ReadableImageWashRect (QPainter *rgbPainter, + const QImage &image, + const kpColor &colorToReplace, + const QRect &imageRect, const QRect &drawRect, + int processedColorSimilarity) +{ + bool didSomething = false; + +#if DEBUG_KP_PAINTER && 0 + qCDebug(kpLogImagelib) << "kppixmapfx.cpp:WashRect(imageRect=" << imageRect + << ",drawRect=" << drawRect + << ")" << endl; +#endif + + // If you're going to pass painter pointers, those painters had better be + // active (i.e. QPainter::begin() has been called). + Q_ASSERT (!rgbPainter || rgbPainter->isActive ()); + +// make use of scanline coherence +#define FLUSH_LINE() \ +{ \ + if (rgbPainter) { \ + if (startDrawX == x - 1) \ + rgbPainter->drawPoint (startDrawX + imageRect.x (), \ + y + imageRect.y ()); \ + else \ + rgbPainter->drawLine (startDrawX + imageRect.x (), \ + y + imageRect.y (), \ + x - 1 + imageRect.x (), \ + y + imageRect.y ()); \ + } \ + didSomething = true; \ + startDrawX = -1; \ +} + + const int maxY = drawRect.bottom () - imageRect.top (); + + const int minX = drawRect.left () - imageRect.left (); + const int maxX = drawRect.right () - imageRect.left (); + + for (int y = drawRect.top () - imageRect.top (); + y <= maxY; + y++) + { + int startDrawX = -1; + + int x; // for FLUSH_LINE() + for (x = minX; x <= maxX; x++) + { + #if DEBUG_KP_PAINTER && 0 + fprintf (stderr, "y=%i x=%i colorAtPixel=%08X colorToReplace=%08X ... ", + y, x, + kpPixmapFX::getColorAtPixel (image, QPoint (x, y)).toQRgb (), + colorToReplace.toQRgb ()); + #endif + if (kpPixmapFX::getColorAtPixel (image, QPoint (x, y)).isSimilarTo (colorToReplace, processedColorSimilarity)) + { + #if DEBUG_KP_PAINTER && 0 + fprintf (stderr, "similar\n"); + #endif + if (startDrawX < 0) { + startDrawX = x; + } + } + else + { + #if DEBUG_KP_PAINTER && 0 + fprintf (stderr, "different\n"); + #endif + if (startDrawX >= 0) { + FLUSH_LINE (); + } + } + } + + if (startDrawX >= 0) { + FLUSH_LINE (); + } + } + +#undef FLUSH_LINE + + return didSomething; +} + +//--------------------------------------------------------------------- + +struct WashPack +{ + QPoint startPoint, endPoint; + kpColor color; + int penWidth{}, penHeight{}; + kpColor colorToReplace; + int processedColorSimilarity{}; + + QRect readableImageRect; + QImage readableImage; +}; + +//--------------------------------------------------------------------- + +static QRect Wash (kpImage *image, + const QPoint &startPoint, const QPoint &endPoint, + const kpColor &color, int penWidth, int penHeight, + const kpColor &colorToReplace, + int processedColorSimilarity, + QRect (*drawFunc) (QPainter * /*rgbPainter*/, void * /*data*/)) +{ + WashPack pack; + pack.startPoint = startPoint; pack.endPoint = endPoint; + pack.color = color; + pack.penWidth = penWidth; pack.penHeight = penHeight; + pack.colorToReplace = colorToReplace; + pack.processedColorSimilarity = processedColorSimilarity; + + + // Get the rectangle that bounds the changes and the pixmap for that + // rectangle. + const QRect normalizedRect = kpPainter::normalizedRect(pack.startPoint, pack.endPoint); + pack.readableImageRect = kpTool::neededRect (normalizedRect, + qMax (pack.penWidth, pack.penHeight)); +#if DEBUG_KP_PAINTER + qCDebug(kpLogImagelib) << "kppainter.cpp:Wash() startPoint=" << startPoint + << " endPoint=" << endPoint + << " --> normalizedRect=" << normalizedRect + << " readableImageRect=" << pack.readableImageRect + << endl; +#endif + pack.readableImage = kpPixmapFX::getPixmapAt (*image, pack.readableImageRect); + + QPainter painter(image); + return (*drawFunc)(&painter, &pack); +} + +//--------------------------------------------------------------------- + +void WashHelperSetup (QPainter *rgbPainter, const WashPack *pack) +{ + // Set the drawing colors for the painters. + + if (rgbPainter) { + rgbPainter->setPen (pack->color.toQColor()); + } +} + +//--------------------------------------------------------------------- + +static QRect WashLineHelper (QPainter *rgbPainter, void *data) +{ +#if DEBUG_KP_PAINTER && 0 + qCDebug(kpLogImagelib) << "Washing pixmap (w=" << rect.width () + << ",h=" << rect.height () << ")" << endl; + QTime timer; + int convAndWashTime; +#endif + + auto *pack = static_cast (data); + + // Setup painters. + ::WashHelperSetup (rgbPainter, pack); + + + bool didSomething = false; + + QList points = kpPainter::interpolatePoints (pack->startPoint, pack->endPoint); + foreach (const QPoint &p, points) + { + // OPT: This may be reading and possibly writing pixels that were + // visited on a previous iteration, since the pen is usually + // bigger than 1 pixel. Maybe we could use QRegion to determine + // all the non-intersecting regions and only wash each region once. + // + // Profiling needs to be done as QRegion is known to be a CPU hog. + if (::ReadableImageWashRect (rgbPainter, + pack->readableImage, + pack->colorToReplace, + pack->readableImageRect, + kpToolFlowBase::hotRectForMousePointAndBrushWidthHeight ( + p, pack->penWidth, pack->penHeight), + pack->processedColorSimilarity)) + { + didSomething = true; + } + } + + +#if DEBUG_KP_PAINTER && 0 + int ms = timer.restart (); + qCDebug(kpLogImagelib) << "\ttried to wash: " << ms << "ms" + << " (" << (ms ? (rect.width () * rect.height () / ms) : -1234) + << " pixels/ms)" + << endl; + convAndWashTime += ms; +#endif + + + // TODO: Rectangle may be too big. Use QRect::united() incrementally? + // Efficiency? + return didSomething ? pack->readableImageRect : QRect (); +} + +//--------------------------------------------------------------------- + +// public static +QRect kpPainter::washLine (kpImage *image, + int x1, int y1, int x2, int y2, + const kpColor &color, int penWidth, int penHeight, + const kpColor &colorToReplace, + int processedColorSimilarity) +{ + return ::Wash (image, + QPoint (x1, y1), QPoint (x2, y2), + color, penWidth, penHeight, + colorToReplace, + processedColorSimilarity, + &::WashLineHelper); +} + +//--------------------------------------------------------------------- + +static QRect WashRectHelper (QPainter *rgbPainter, void *data) +{ + auto *pack = static_cast (data); +#if DEBUG_KP_PAINTER && 0 + qCDebug(kpLogImagelib) << "Washing pixmap (w=" << rect.width () + << ",h=" << rect.height () << ")" << endl; + QTime timer; + int convAndWashTime; +#endif + + // Setup painters. + ::WashHelperSetup (rgbPainter, pack); + + + const QRect drawRect (pack->startPoint, pack->endPoint); + + bool didSomething = false; + + if (::ReadableImageWashRect (rgbPainter, + pack->readableImage, + pack->colorToReplace, + pack->readableImageRect, + drawRect, + pack->processedColorSimilarity)) + { + didSomething = true; + } + + +#if DEBUG_KP_PAINTER && 0 + int ms = timer.restart (); + qCDebug(kpLogImagelib) << "\ttried to wash: " << ms << "ms" + << " (" << (ms ? (rect.width () * rect.height () / ms) : -1234) + << " pixels/ms)" + << endl; + convAndWashTime += ms; +#endif + + + return didSomething ? drawRect : QRect (); +} + +//--------------------------------------------------------------------- + +// public static +QRect kpPainter::washRect (kpImage *image, + int x, int y, int width, int height, + const kpColor &color, + const kpColor &colorToReplace, + int processedColorSimilarity) +{ + return ::Wash (image, + QPoint (x, y), QPoint (x + width - 1, y + height - 1), + color, 1/*pen width*/, 1/*pen height*/, + colorToReplace, + processedColorSimilarity, + &::WashRectHelper); +} + +//--------------------------------------------------------------------- + +// public static +void kpPainter::sprayPoints (kpImage *image, + const QList &points, + const kpColor &color, + int spraycanSize) +{ +#if DEBUG_KP_PAINTER + qCDebug(kpLogImagelib) << "kpPainter::sprayPoints()"; +#endif + + Q_ASSERT (spraycanSize > 0); + + QPainter painter(image); + const int radius = spraycanSize / 2; + + // Set the drawing colors for the painters. + + painter.setPen(color.toQColor()); + + for (const auto &p : points) + { + for (int i = 0; i < 10; i++) + { + const int dx = (QRandomGenerator::global()->generate() % spraycanSize) - radius; + const int dy = (QRandomGenerator::global()->generate() % spraycanSize) - radius; + + // Make it look circular. + // TODO: Can be done better by doing a random vector angle & length + // but would sin and cos be too slow? + if ((dx * dx) + (dy * dy) > (radius * radius)) { + continue; + } + + const QPoint p2 (p.x () + dx, p.y () + dy); + + painter.drawPoint(p2); + } + } +} + +//--------------------------------------------------------------------- diff --git a/imagelib/kpPainter.h b/imagelib/kpPainter.h new file mode 100644 index 0000000..8e58371 --- /dev/null +++ b/imagelib/kpPainter.h @@ -0,0 +1,137 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_PAINTER_H +#define KP_PAINTER_H + + +#include "kpColor.h" +#include "kpImage.h" + + + + +// +// Stateless painter with sane semantics that works on kpImage's i.e. it +// works on document - not view - data. If you find that you need state, +// you should probably move it into kpPainter to avoid the overhead of +// passing around this state (e.g. color, line width) and for reuse. +// +// kpPainter is to kpImage as QPainter is to QPixmap. +// +// This encapsulates the set of functionality used by all of KolourPaint's +// document drawing functions and nothing more, permitting rewriting of +// the image library. Currently uses QPainter/kpPixmapFX as the image library. +// + +struct kpPainterPrivate; + +class kpPainter +{ +public: + // helper to make a correct QRect out of 2 QPoints regardless of their relative position + // to each other + static QRect normalizedRect(const QPoint& p1, const QPoint& p2) + { + return QRect(qMin(p1.x(), p2.x()), qMin(p1.y(), p2.y()), + qAbs(p2.x() - p1.x()) + 1, qAbs(p2.y() - p1.y()) + 1); + } + + // Returns whether the given points are cardinally adjacent (i.e. one point + // is exactly 1 pixel north, east, south or west of the other). Equal + // points are not cardinally adjacent. + static bool pointsAreCardinallyAdjacent (const QPoint &p, const QPoint &q); + + // Returns a list of points representing a straight line from + // to inclusive, using Bresenham's line algorithm. Each point + // is created only with the specified . + // + // If is set, a modified Bresenham's algorithm will add + // an extra point between every pair of originally strictly-diagonally-adjacent + // points, such that these points become cardinally adjacent. However, these + // extra points are also created only with the specified . + // + // For instance, must be set if a diagonal line is to + // drawn at each of the returned points, otherwise things won't look right: + // + // .\..... + // \.\.... + // .\.B... + // ..Ac\.. + // ...\.\. + // ....\.. + // + // 'A' is the previous Bresenham point. 'B' is the new point. See how if + // diagonal lines are drawn at A and B, there is a gap between the lines. + // Setting will solve this problem, since it will add + // a point at 'c'. + // + // ASSUMPTION: is between 0.0 and 1.0 inclusive. + static QList interpolatePoints (const QPoint &startPoint, + const QPoint &endPoint, + bool cardinalAdjacency = false, + double probability = 1.0); + + static void fillRect (kpImage *image, + int x, int y, int width, int height, + const kpColor &color); + + // Replaces all pixels of on the line + // from (x1,y1) to (x2,y2) of , with a pen of with + // dimensions x. + // + // The corners are centred at those coordinates so if > 1 or + // > 1, the line is likely to extend past a rectangle with + // those corners. + // + // Returns the dirty rectangle. + static QRect washLine (kpImage *image, + int x1, int y1, int x2, int y2, + const kpColor &color, int penWidth, int penHeight, + const kpColor &colorToReplace, + int processedColorSimilarity); + + static QRect washRect (kpImage *image, + int x, int y, int width, int height, + const kpColor &color, + const kpColor &colorToReplace, + int processedColorSimilarity); + + // For each point in , sprays a random pattern of 10 dots of , + // each within a circle of diameter , onto . + // + // ASSUMPTION: spraycanSize > 0. + // TODO: I think this diameter is 1 or 2 off. + static void sprayPoints (kpImage *image, + const QList &points, + const kpColor &color, + int spraycanSize); +}; + + +#endif // KP_PAINTER_H diff --git a/imagelib/transforms/kpTransformAutoCrop.cpp b/imagelib/transforms/kpTransformAutoCrop.cpp new file mode 100644 index 0000000..f3b3085 --- /dev/null +++ b/imagelib/transforms/kpTransformAutoCrop.cpp @@ -0,0 +1,756 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// TODO: Color Similarity is obviously useful in Autocrop but it isn't +// obvious as to how to implement it. The current heuristic, +// for each side, chooses an arbitrary reference color for which +// all other candidate pixels in that side are tested against +// for similarity. But if the reference color happens to be at +// one extreme of the range of colors in that side, then pixels +// at the other extreme would not be deemed similar enough. The +// key is to find the median color as the reference but how do +// you do this if you don't know which pixels to sample in the first +// place (that's what you're trying to find)? Chicken and egg situation. +// +// The other heuristic that is in doubt is the use of the average +// color in determining the similarity of sides (it is possible +// to get vastly differently colors in both sides yet they will be +// considered similar). + +#define DEBUG_KP_TOOL_AUTO_CROP 0 + + +#include "kpTransformAutoCrop.h" + +#include "layers/selections/image/kpAbstractImageSelection.h" +#include "widgets/toolbars/kpColorToolBar.h" +#include "environments/commands/kpCommandEnvironment.h" +#include "commands/kpCommandHistory.h" +#include "document/kpDocument.h" +#include "mainWindow/kpMainWindow.h" +#include "imagelib/kpPainter.h" +#include "pixmapfx/kpPixmapFX.h" +#include "layers/selections/image/kpRectangularImageSelection.h" +#include "generic/kpSetOverrideCursorSaver.h" +#include "tools/kpTool.h" +#include "views/manager/kpViewManager.h" + +#include "kpLogCategories.h" +#include +#include + +#include + +//--------------------------------------------------------------------- + +class kpTransformAutoCropBorder +{ +public: + // WARNING: Only call the with imagePtr = 0 if you are going to use + // operator= to fill it in with a valid imagePtr immediately + // afterwards. + kpTransformAutoCropBorder (const kpImage *imagePtr = nullptr, int processedColorSimilarity = 0); + + kpCommandSize::SizeType size () const; + + const kpImage *image () const; + int processedColorSimilarity () const; + QRect rect () const; + int left () const; + int right () const; + int top () const; + int bottom () const; + kpColor referenceColor () const; + kpColor averageColor () const; + bool isSingleColor () const; + + // (returns true on success (even if no rect) or false on error) + bool calculate (int isX, int dir); + + bool fillsEntireImage () const; + bool exists () const; + void invalidate (); + +private: + const kpImage *m_imagePtr; + int m_processedColorSimilarity; + + QRect m_rect; + kpColor m_referenceColor; + int m_redSum, m_greenSum, m_blueSum; + bool m_isSingleColor; +}; + +kpTransformAutoCropBorder::kpTransformAutoCropBorder (const kpImage *imagePtr, + int processedColorSimilarity) + : m_imagePtr (imagePtr), + m_processedColorSimilarity (processedColorSimilarity) +{ + invalidate (); +} + + +// public +kpCommandSize::SizeType kpTransformAutoCropBorder::size () const +{ + return sizeof (kpTransformAutoCropBorder); +} + + +// public +const kpImage *kpTransformAutoCropBorder::image () const +{ + return m_imagePtr; +} + +// public +int kpTransformAutoCropBorder::processedColorSimilarity () const +{ + return m_processedColorSimilarity; +} + +// public +QRect kpTransformAutoCropBorder::rect () const +{ + return m_rect; +} + +// public +int kpTransformAutoCropBorder::left () const +{ + return m_rect.left (); +} + +// public +int kpTransformAutoCropBorder::right () const +{ + return m_rect.right (); +} + +// public +int kpTransformAutoCropBorder::top () const +{ + return m_rect.top (); +} + +// public +int kpTransformAutoCropBorder::bottom () const +{ + return m_rect.bottom (); +} + +// public +kpColor kpTransformAutoCropBorder::referenceColor () const +{ + return m_referenceColor; +} + +// public +kpColor kpTransformAutoCropBorder::averageColor () const +{ + if (!m_rect.isValid ()) + return kpColor::Invalid; + + if (m_referenceColor.isTransparent ()) + return kpColor::Transparent; + + if (m_processedColorSimilarity == 0) + return m_referenceColor; + + int numPixels = (m_rect.width () * m_rect.height ()); + Q_ASSERT (numPixels > 0); + + return kpColor (m_redSum / numPixels, m_greenSum / numPixels, m_blueSum / numPixels); + +} + +//--------------------------------------------------------------------- + +bool kpTransformAutoCropBorder::isSingleColor () const +{ + return m_isSingleColor; +} + +//--------------------------------------------------------------------- + +// public +bool kpTransformAutoCropBorder::calculate (int isX, int dir) +{ +#if DEBUG_KP_TOOL_AUTO_CROP && 1 + qCDebug(kpLogImagelib) << "kpTransformAutoCropBorder::calculate() CALLED!"; +#endif + int maxX = m_imagePtr->width () - 1; + int maxY = m_imagePtr->height () - 1; + + QImage qimage = *m_imagePtr; + Q_ASSERT (!qimage.isNull ()); + + // (sync both branches) + if (isX) + { + int numCols = 0; + int startX = (dir > 0) ? 0 : maxX; + + kpColor col = kpPixmapFX::getColorAtPixel (qimage, startX, 0); + for (int x = startX; + x >= 0 && x <= maxX; + x += dir) + { + int y; + for (y = 0; y <= maxY; y++) + { + if (!kpPixmapFX::getColorAtPixel (qimage, x, y).isSimilarTo (col, m_processedColorSimilarity)) + break; + } + + if (y <= maxY) + break; + else + numCols++; + } + + if (numCols) + { + m_rect = + kpPainter::normalizedRect(QPoint(startX, 0), + QPoint(startX + (numCols - 1) * dir, maxY)); + m_referenceColor = col; + } + } + else + { + int numRows = 0; + int startY = (dir > 0) ? 0 : maxY; + + kpColor col = kpPixmapFX::getColorAtPixel (qimage, 0, startY); + for (int y = startY; + y >= 0 && y <= maxY; + y += dir) + { + int x; + for (x = 0; x <= maxX; x++) + { + if (!kpPixmapFX::getColorAtPixel (qimage, x, y).isSimilarTo (col, m_processedColorSimilarity)) + break; + } + + if (x <= maxX) + break; + else + numRows++; + } + + if (numRows) + { + m_rect = kpPainter::normalizedRect(QPoint(0, startY), + QPoint(maxX, startY + (numRows - 1) * dir)); + m_referenceColor = col; + } + } + + + if (m_rect.isValid ()) + { + m_isSingleColor = true; + + if (m_processedColorSimilarity != 0) + { + for (int y = m_rect.top (); y <= m_rect.bottom (); y++) + { + for (int x = m_rect.left (); x <= m_rect.right (); x++) + { + kpColor colAtPixel = kpPixmapFX::getColorAtPixel (qimage, x, y); + + if (m_isSingleColor && colAtPixel != m_referenceColor) + m_isSingleColor = false; + + m_redSum += colAtPixel.red (); + m_greenSum += colAtPixel.green (); + m_blueSum += colAtPixel.blue (); + } + } + } + } + + + return true; +} + +// public +bool kpTransformAutoCropBorder::fillsEntireImage () const +{ + return (m_rect == m_imagePtr->rect ()); +} + +// public +bool kpTransformAutoCropBorder::exists () const +{ + // (will use in an addition so make sure returns 1 or 0) + return (m_rect.isValid () ? 1 : 0); +} + +// public +void kpTransformAutoCropBorder::invalidate () +{ + m_rect = QRect (); + m_referenceColor = kpColor::Invalid; + m_redSum = m_greenSum = m_blueSum = 0; + m_isSingleColor = false; +} + + +struct kpTransformAutoCropCommandPrivate +{ + bool actOnSelection{}; + kpTransformAutoCropBorder leftBorder, rightBorder, topBorder, botBorder; + kpImage *leftImage{}, *rightImage{}, *topImage{}, *botImage{}; + + QRect contentsRect; + int oldWidth{}, oldHeight{}; + kpAbstractImageSelection *oldSelectionPtr{}; +}; + +// REFACTOR: Move to /commands/ +kpTransformAutoCropCommand::kpTransformAutoCropCommand (bool actOnSelection, + const kpTransformAutoCropBorder &leftBorder, + const kpTransformAutoCropBorder &rightBorder, + const kpTransformAutoCropBorder &topBorder, + const kpTransformAutoCropBorder &botBorder, + kpCommandEnvironment *environ) + : kpNamedCommand(text(actOnSelection, DontShowAccel), environ), + d (new kpTransformAutoCropCommandPrivate ()) +{ + d->actOnSelection = actOnSelection; + d->leftBorder = leftBorder; + d->rightBorder = rightBorder; + d->topBorder = topBorder; + d->botBorder = botBorder; + d->leftImage = nullptr; + d->rightImage = nullptr; + d->topImage = nullptr; + d->botImage = nullptr; + + kpDocument *doc = document (); + Q_ASSERT (doc); + + d->oldWidth = doc->width (d->actOnSelection); + d->oldHeight = doc->height (d->actOnSelection); + + d->oldSelectionPtr = nullptr; +} + +//--------------------------------------------------------------------- + +kpTransformAutoCropCommand::~kpTransformAutoCropCommand () +{ + deleteUndoImages (); + + delete d->oldSelectionPtr; + delete d; +} + +//--------------------------------------------------------------------- +// public static + +QString kpTransformAutoCropCommand::text(bool actOnSelection, int options) +{ + if (actOnSelection) + { + if (options & kpTransformAutoCropCommand::ShowAccel) { + return i18n ("Remove Internal B&order"); + } + + return i18n ("Remove Internal Border"); + } + + if (options & kpTransformAutoCropCommand::ShowAccel) + return i18n ("Autocr&op"); + + return i18n ("Autocrop"); +} + +//--------------------------------------------------------------------- +// public virtual [base kpCommand] + +kpCommandSize::SizeType kpTransformAutoCropCommand::size () const +{ + return d->leftBorder.size () + + d->rightBorder.size () + + d->topBorder.size () + + d->botBorder.size () + + ImageSize (d->leftImage) + + ImageSize (d->rightImage) + + ImageSize (d->topImage) + + ImageSize (d->botImage) + + SelectionSize (d->oldSelectionPtr); +} + +//--------------------------------------------------------------------- +// private + +void kpTransformAutoCropCommand::getUndoImage (const kpTransformAutoCropBorder &border, kpImage **image) +{ + kpDocument *doc = document (); + Q_ASSERT (doc); + +#if DEBUG_KP_TOOL_AUTO_CROP && 1 + qCDebug(kpLogImagelib) << "kpTransformAutoCropCommand::getUndoImage()"; + qCDebug(kpLogImagelib) << "\timage=" << image + << " border: rect=" << border.rect () + << " isSingleColor=" << border.isSingleColor (); +#endif + + if (image && border.exists () && !border.isSingleColor ()) + { + if (*image) + { + #if DEBUG_KP_TOOL_AUTO_CROP && 1 + qCDebug(kpLogImagelib) << "\talready have *image - delete it"; + #endif + delete *image; + } + + *image = new kpImage ( + kpPixmapFX::getPixmapAt (doc->image (d->actOnSelection), + border.rect ())); + } +} + + +// private +void kpTransformAutoCropCommand::getUndoImages () +{ + getUndoImage (d->leftBorder, &d->leftImage); + getUndoImage (d->rightBorder, &d->rightImage); + getUndoImage (d->topBorder, &d->topImage); + getUndoImage (d->botBorder, &d->botImage); +} + +// private +void kpTransformAutoCropCommand::deleteUndoImages () +{ +#if DEBUG_KP_TOOL_AUTO_CROP && 1 + qCDebug(kpLogImagelib) << "kpTransformAutoCropCommand::deleteUndoImages()"; +#endif + + delete d->leftImage; d->leftImage = nullptr; + delete d->rightImage; d->rightImage = nullptr; + delete d->topImage; d->topImage = nullptr; + delete d->botImage; d->botImage = nullptr; +} + + +// public virtual [base kpCommand] +void kpTransformAutoCropCommand::execute () +{ + if (!d->contentsRect.isValid ()) { + d->contentsRect = contentsRect (); + } + + + getUndoImages (); + + + kpDocument *doc = document (); + Q_ASSERT (doc); + + + kpImage imageWithoutBorder = + kpTool::neededPixmap (doc->image (d->actOnSelection), + d->contentsRect); + + + if (!d->actOnSelection) { + doc->setImage (imageWithoutBorder); + } + else { + d->oldSelectionPtr = doc->imageSelection ()->clone (); + d->oldSelectionPtr->setBaseImage (kpImage ()); + + // d->contentsRect is relative to the top of the sel + // while sel is relative to the top of the doc + QRect rect = d->contentsRect; + rect.translate (d->oldSelectionPtr->x (), d->oldSelectionPtr->y ()); + + kpRectangularImageSelection sel ( + rect, + imageWithoutBorder, + d->oldSelectionPtr->transparency ()); + + doc->setSelection (sel); + + environ ()->somethingBelowTheCursorChanged (); + } +} + +// public virtual [base kpCommand] +void kpTransformAutoCropCommand::unexecute () +{ +#if DEBUG_KP_TOOL_AUTO_CROP && 1 + qCDebug(kpLogImagelib) << "kpTransformAutoCropCommand::unexecute()"; +#endif + + kpDocument *doc = document (); + Q_ASSERT (doc); + + kpImage image (d->oldWidth, d->oldHeight, QImage::Format_ARGB32_Premultiplied); + + // restore the position of the center image + kpPixmapFX::setPixmapAt (&image, d->contentsRect, + doc->image (d->actOnSelection)); + + // draw the borders + + const kpTransformAutoCropBorder *borders [] = + { + &d->leftBorder, &d->rightBorder, + &d->topBorder, &d->botBorder, + nullptr + }; + + const kpImage *images [] = + { + d->leftImage, d->rightImage, + d->topImage, d->botImage, + nullptr + }; + + const kpImage **p = images; + for (const kpTransformAutoCropBorder **b = borders; *b; b++, p++) + { + if (!(*b)->exists ()) { + continue; + } + + if ((*b)->isSingleColor ()) + { + kpColor col = (*b)->referenceColor (); + #if DEBUG_KP_TOOL_AUTO_CROP && 1 + qCDebug(kpLogImagelib) << "\tdrawing border " << (*b)->rect () + << " rgb=" << (int *) col.toQRgb () /* %X hack */; + #endif + + const QRect r = (*b)->rect (); + kpPainter::fillRect (&image, + r.x (), r.y (), r.width (), r.height (), + col); + } + else + { + #if DEBUG_KP_TOOL_AUTO_CROP && 1 + qCDebug(kpLogImagelib) << "\trestoring border image " << (*b)->rect (); + #endif + if (*p) + { + // REFACTOR: Add equivalent method to kpPainter and use. + kpPixmapFX::setPixmapAt (&image, (*b)->rect (), **p); + } + } + } + + + if (!d->actOnSelection) { + doc->setImage (image); + } + else + { + d->oldSelectionPtr->setBaseImage (image); + + doc->setSelection (*d->oldSelectionPtr); + delete d->oldSelectionPtr; d->oldSelectionPtr = nullptr; + + environ ()->somethingBelowTheCursorChanged (); + } + + + deleteUndoImages (); +} + + +// private +QRect kpTransformAutoCropCommand::contentsRect () const +{ + const kpImage image = document ()->image (d->actOnSelection); + + QPoint topLeft (d->leftBorder.exists () ? + d->leftBorder.rect ().right () + 1 : + 0, + d->topBorder.exists () ? + d->topBorder.rect ().bottom () + 1 : + 0); + QPoint botRight (d->rightBorder.exists () ? + d->rightBorder.rect ().left () - 1 : + image.width () - 1, + d->botBorder.exists () ? + d->botBorder.rect ().top () - 1 : + image.height () - 1); + + return {topLeft, botRight}; +} + + +static void ShowNothingToAutocropMessage (kpMainWindow *mainWindow, bool actOnSelection) +{ + kpSetOverrideCursorSaver cursorSaver (Qt::ArrowCursor); + + if (actOnSelection) + { + KMessageBox::information (mainWindow, + i18n ("KolourPaint cannot remove the selection's internal border as it" + " could not be located."), + i18nc ("@title:window", "Cannot Remove Internal Border"), + QStringLiteral("NothingToAutoCrop")); + } + else + { + KMessageBox::information (mainWindow, + i18n ("KolourPaint cannot automatically crop the image as its" + " border could not be located."), + i18nc ("@title:window", "Cannot Autocrop"), + QStringLiteral("NothingToAutoCrop")); + } +} + +bool kpTransformAutoCrop (kpMainWindow *mainWindow) +{ +#if DEBUG_KP_TOOL_AUTO_CROP + qCDebug(kpLogImagelib) << "kpTransformAutoCrop() CALLED!"; +#endif + + Q_ASSERT (mainWindow); + kpDocument *doc = mainWindow->document (); + Q_ASSERT (doc); + + // OPT: if already pulled selection image, no need to do it again here + kpImage image = doc->selection () ? doc->getSelectedBaseImage () : doc->image (); + Q_ASSERT (!image.isNull ()); + + kpViewManager *vm = mainWindow->viewManager (); + Q_ASSERT (vm); + + int processedColorSimilarity = mainWindow->colorToolBar ()->processedColorSimilarity (); + kpTransformAutoCropBorder leftBorder (&image, processedColorSimilarity), + rightBorder (&image, processedColorSimilarity), + topBorder (&image, processedColorSimilarity), + botBorder (&image, processedColorSimilarity); + + + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + mainWindow->colorToolBar ()->flashColorSimilarityToolBarItem (); + + // TODO: With Colour Similarity, a lot of weird (and wonderful) things can + // happen resulting in a huge number of code paths. Needs refactoring + // and regression testing. + // + // TODO: e.g. When the top fills entire rect but bot doesn't we could + // invalidate top and continue autocrop. + int numRegions = 0; + if (!leftBorder.calculate (true/*x*/, +1/*going right*/) || + leftBorder.fillsEntireImage () || + !rightBorder.calculate (true/*x*/, -1/*going left*/) || + rightBorder.fillsEntireImage () || + !topBorder.calculate (false/*y*/, +1/*going down*/) || + topBorder.fillsEntireImage () || + !botBorder.calculate (false/*y*/, -1/*going up*/) || + botBorder.fillsEntireImage () || + ((numRegions = leftBorder.exists () + + rightBorder.exists () + + topBorder.exists () + + botBorder.exists ()) == 0)) + { + #if DEBUG_KP_TOOL_AUTO_CROP + qCDebug(kpLogImagelib) << "\tcan't find border; leftBorder.rect=" << leftBorder.rect () + << " rightBorder.rect=" << rightBorder.rect () + << " topBorder.rect=" << topBorder.rect () + << " botBorder.rect=" << botBorder.rect (); + #endif + ::ShowNothingToAutocropMessage (mainWindow, static_cast (doc->selection ())); + return false; + } + +#if DEBUG_KP_TOOL_AUTO_CROP + qCDebug(kpLogImagelib) << "\tnumRegions=" << numRegions; + qCDebug(kpLogImagelib) << "\t\tleft=" << leftBorder.rect () + << " refCol=" << (leftBorder.exists () ? (int *) leftBorder.referenceColor ().toQRgb () : nullptr) + << " avgCol=" << (leftBorder.exists () ? (int *) leftBorder.averageColor ().toQRgb () : nullptr); + qCDebug(kpLogImagelib) << "\t\tright=" << rightBorder.rect () + << " refCol=" << (rightBorder.exists () ? (int *) rightBorder.referenceColor ().toQRgb () : nullptr) + << " avgCol=" << (rightBorder.exists () ? (int *) rightBorder.averageColor ().toQRgb () : nullptr); + qCDebug(kpLogImagelib) << "\t\ttop=" << topBorder.rect () + << " refCol=" << (topBorder.exists () ? (int *) topBorder.referenceColor ().toQRgb () : nullptr) + << " avgCol=" << (topBorder.exists () ? (int *) topBorder.averageColor ().toQRgb () : nullptr); + qCDebug(kpLogImagelib) << "\t\tbot=" << botBorder.rect () + << " refCol=" << (botBorder.exists () ? (int *) botBorder.referenceColor ().toQRgb () : nullptr) + << " avgCol=" << (botBorder.exists () ? (int *) botBorder.averageColor ().toQRgb () : nullptr); +#endif + + // In case e.g. the user pastes a solid, coloured-in rectangle, + // we favor killing the bottom and right regions + // (these regions probably contain the unwanted whitespace due + // to the doc being bigger than the pasted selection to start with). + // + // We also kill if they kiss or even overlap. + + if (leftBorder.exists () && rightBorder.exists ()) + { + const kpColor leftCol = leftBorder.averageColor (); + const kpColor rightCol = rightBorder.averageColor (); + + if ((numRegions == 2 && !leftCol.isSimilarTo (rightCol, processedColorSimilarity)) || + leftBorder.right () >= rightBorder.left () - 1) // kissing or overlapping + { + #if DEBUG_KP_TOOL_AUTO_CROP + qCDebug(kpLogImagelib) << "\tignoring left border"; + #endif + leftBorder.invalidate (); + } + } + + if (topBorder.exists () && botBorder.exists ()) + { + const kpColor topCol = topBorder.averageColor (); + const kpColor botCol = botBorder.averageColor (); + + if ((numRegions == 2 && !topCol.isSimilarTo (botCol, processedColorSimilarity)) || + topBorder.bottom () >= botBorder.top () - 1) // kissing or overlapping + { + #if DEBUG_KP_TOOL_AUTO_CROP + qCDebug(kpLogImagelib) << "\tignoring top border"; + #endif + topBorder.invalidate (); + } + } + + + mainWindow->addImageOrSelectionCommand ( + new kpTransformAutoCropCommand (static_cast (doc->selection ()), + leftBorder, rightBorder, topBorder, botBorder, mainWindow->commandEnvironment ())); + + + return true; +} diff --git a/imagelib/transforms/kpTransformAutoCrop.h b/imagelib/transforms/kpTransformAutoCrop.h new file mode 100644 index 0000000..1a246b9 --- /dev/null +++ b/imagelib/transforms/kpTransformAutoCrop.h @@ -0,0 +1,85 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TRANSFORM_AUTO_CROP_H +#define KP_TRANSFORM_AUTO_CROP_H + + +#include "commands/kpNamedCommand.h" + + +class QRect; + +//class kpImage; +class kpMainWindow; +class kpTransformAutoCropBorder; + + +// REFACTOR: This should be moved into /commands/ +class kpTransformAutoCropCommand : public kpNamedCommand +{ +public: + kpTransformAutoCropCommand (bool actOnSelection, + const kpTransformAutoCropBorder &leftBorder, + const kpTransformAutoCropBorder &rightBorder, + const kpTransformAutoCropBorder &topBorder, + const kpTransformAutoCropBorder &botBorder, + kpCommandEnvironment *environ); + ~kpTransformAutoCropCommand () override; + + enum NameOptions + { + DontShowAccel = 0, + ShowAccel = 1 + }; + + static QString text(bool actOnSelection, int options); + + SizeType size () const override; + +private: + void getUndoImage (const kpTransformAutoCropBorder &border, kpImage **image); + void getUndoImages (); + void deleteUndoImages (); + +public: + void execute () override; + void unexecute () override; + +private: + QRect contentsRect () const; + + struct kpTransformAutoCropCommandPrivate *d; +}; + + +// (returns true on success (even if it did nothing) or false on error) +bool kpTransformAutoCrop (kpMainWindow *mainWindow); + + +#endif // KP_TRANSFORM_AUTO_CROP_H diff --git a/imagelib/transforms/kpTransformCrop.cpp b/imagelib/transforms/kpTransformCrop.cpp new file mode 100644 index 0000000..34d1011 --- /dev/null +++ b/imagelib/transforms/kpTransformCrop.cpp @@ -0,0 +1,77 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_CROP 0 + + +#include "kpTransformCrop.h" +#include "kpTransformCropPrivate.h" + +#include "layers/selections/image/kpAbstractImageSelection.h" +#include "layers/selections/kpAbstractSelection.h" +#include "document/kpDocument.h" +#include "mainWindow/kpMainWindow.h" +#include "layers/selections/text/kpTextSelection.h" +#include "commands/imagelib/transforms/kpTransformResizeScaleCommand.h" + +#include + + +void kpTransformCrop (kpMainWindow *mainWindow) +{ + kpDocument *doc = mainWindow->document (); + Q_ASSERT (doc); + + kpAbstractSelection *sel = doc->selection (); + Q_ASSERT (sel); + + + kpCommand *resizeDocCommand = + new kpTransformResizeScaleCommand ( + false/*act on doc, not sel*/, + sel->width (), sel->height (), + kpTransformResizeScaleCommand::Resize, + mainWindow->commandEnvironment ()); + + + auto *textSel = dynamic_cast (sel); + auto *imageSel = dynamic_cast (sel); + // It's either a text selection or an image selection, but cannot be + // neither or both. + Q_ASSERT (!!textSel != !!imageSel); + + if (textSel) { + ::kpTransformCrop_TextSelection (mainWindow, i18n ("Set as Image"), resizeDocCommand); + } + else if (imageSel) { + ::kpTransformCrop_ImageSelection (mainWindow, i18n ("Set as Image"), resizeDocCommand); + } + else { + Q_ASSERT (!"unreachable"); + } +} diff --git a/imagelib/transforms/kpTransformCrop.h b/imagelib/transforms/kpTransformCrop.h new file mode 100644 index 0000000..cb316a5 --- /dev/null +++ b/imagelib/transforms/kpTransformCrop.h @@ -0,0 +1,83 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TRANSFORM_CROP_H +#define KP_TRANSFORM_CROP_H + + +class kpMainWindow; + + +// +// ASSUMPTION: There is a current selection. +// +// +// In all cases, the document is resized to be the same size as the bounding +// rectangle of the selection. Additional behavior depends on the type of +// selection: +// +// +// If it's a text box: +// +// 1. It's moved to (0, 0) and kept editable. +// +// 2. The document background always becomes completely transparent. +// +// Text boxes with transparent backgrounds, before calling this method, +// antialias their text with the pixels of the document below. Such +// pixels are unlikely to be all of the same color, so there is no single +// "correct" color for the new document background. We choose transparent +// because it's the most neutral and forces the text to not antialias. +// TODO: Perhaps a better approach would have been to simply copy the +// pixels of the document below the text box to (0, 0)? +// +// For text boxes with opaque backgrounds, the new transparent document +// background means that the extents of text boxes are clear, when the +// boxes are moved around -- this is handy. +// +// +// If it's an image selection: +// +// 1. The pixels of the document starting from position (0, 0) are set the +// same as those inside the selection region. Unlike other image selection +// commands, if the selection is not floating, there is still no pulling +// of the selection from the document. +// +// The pixels outside the selection region are set to the background color. +// +// 2. The selection border is discarded -- even if the selection was floating +// before -- and replaced by a new one, of the same shape, but located at (0, 0). +// This allows the user to pull off a selection, if they would like. +// +// For user convenience, this border is created by a undoable +// create-selection-border command added to the undo history. +// +void kpTransformCrop (kpMainWindow *mainWindow); + + +#endif // KP_TRANSFORM_CROP_H diff --git a/imagelib/transforms/kpTransformCropPrivate.h b/imagelib/transforms/kpTransformCropPrivate.h new file mode 100644 index 0000000..08f6961 --- /dev/null +++ b/imagelib/transforms/kpTransformCropPrivate.h @@ -0,0 +1,49 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTransformCropPrivate_H +#define kpTransformCropPrivate_H + + +class QString; + +class kpCommand; +class kpMainWindow; + + +// Adds a kpMacroCommand, with name , to the command history. +// +// The first subcommand of this kpMacroCommand should be +// which resizes the document to the size of the selection. +void kpTransformCrop_TextSelection (kpMainWindow *mainWindow, + const QString &commandName, kpCommand *resizeDocCommand); +void kpTransformCrop_ImageSelection (kpMainWindow *mainWindow, + const QString &commandName, kpCommand *resizeDocCommand); + + +#endif // kpTransformCropPrivate_H diff --git a/imagelib/transforms/kpTransformCrop_ImageSelection.cpp b/imagelib/transforms/kpTransformCrop_ImageSelection.cpp new file mode 100644 index 0000000..7a527e7 --- /dev/null +++ b/imagelib/transforms/kpTransformCrop_ImageSelection.cpp @@ -0,0 +1,257 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_TOOL_CROP 0 + + +#include "kpTransformCrop.h" +#include "kpTransformCropPrivate.h" + +#include "layers/selections/image/kpAbstractImageSelection.h" +#include "environments/commands/kpCommandEnvironment.h" +#include "commands/kpCommandHistory.h" +#include "document/kpDocument.h" +#include "imagelib/kpImage.h" +#include "commands/kpMacroCommand.h" +#include "mainWindow/kpMainWindow.h" +#include "pixmapfx/kpPixmapFX.h" +#include "commands/tools/selection/kpToolSelectionCreateCommand.h" +#include "views/manager/kpViewManager.h" + + +// See the "image selection" part of the kpTransformCrop() API Doc. +// +// REFACTOR: Move into commands/ +class SetDocumentToSelectionImageCommand : public kpCommand +{ +public: + SetDocumentToSelectionImageCommand (kpCommandEnvironment *environ); + ~SetDocumentToSelectionImageCommand () override; + + /* (uninteresting child of macro cmd) */ + QString name () const override { return {}; } + + kpCommandSize::SizeType size () const override + { + return ImageSize (m_oldImage) + + SelectionSize (m_fromSelectionPtr) + + ImageSize (m_imageIfFromSelectionDoesntHaveOne); + } + + // ASSUMPTION: Document has been resized to be the same size as the + // selection. + void execute () override; + void unexecute () override; + +protected: + kpColor m_backgroundColor; + kpImage m_oldImage; + kpAbstractImageSelection *m_fromSelectionPtr; + kpImage m_imageIfFromSelectionDoesntHaveOne; +}; + + +SetDocumentToSelectionImageCommand::SetDocumentToSelectionImageCommand (kpCommandEnvironment *environ) + : kpCommand (environ), + m_backgroundColor (environ->backgroundColor ()), + m_fromSelectionPtr ( + dynamic_cast ( + environ->document ()->selection ()->clone ())) +{ + Q_ASSERT (m_fromSelectionPtr); + + if ( m_fromSelectionPtr ) // make coverity happy + { + m_imageIfFromSelectionDoesntHaveOne = + m_fromSelectionPtr->hasContent () ? + kpImage () : + document ()->getSelectedBaseImage (); + } +} + +//--------------------------------------------------------------------- + +SetDocumentToSelectionImageCommand::~SetDocumentToSelectionImageCommand () +{ + delete m_fromSelectionPtr; +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +void SetDocumentToSelectionImageCommand::execute () +{ +#if DEBUG_KP_TOOL_CROP + qCDebug(kpLogImagelib) << "SetDocumentToSelectionImageCommand::execute()"; +#endif + + viewManager ()->setQueueUpdates (); + { + // kpTransformCrop_ImageSelection's has + // executed, resizing the document to be the size of the selection + // bounding rectangle. + Q_ASSERT (document ()->width () == m_fromSelectionPtr->width ()); + Q_ASSERT (document ()->height () == m_fromSelectionPtr->height ()); + m_oldImage = document ()->image (); + + + // + // e.g. original elliptical selection: + // + // t/---\ T = original transparent selection pixel + // | TT | t = outside the selection region + // t\__/t [every other character] = original opaque selection pixel + // + // Afterwards, the _document_ image becomes: + // + // b/---\ T = [unchanged] + // | TT | b = background color + // b\__/b [every other character] = [unchanged] + // + // The selection is deleted. + // + // TODO: Do not introduce a mask if the result will not contain + // any transparent pixels. + // + + QImage newDocImage(document()->width(), document()->height(), QImage::Format_ARGB32_Premultiplied); + newDocImage.fill(m_backgroundColor.toQRgb()); + + #if DEBUG_KP_TOOL_CROP + qCDebug(kpLogImagelib) << "\tsel: rect=" << m_fromSelectionPtr->boundingRect () + << " pm=" << m_fromSelectionPtr->hasContent (); + #endif + QImage setTransparentImage; + + if (m_fromSelectionPtr->hasContent ()) + { + setTransparentImage = m_fromSelectionPtr->transparentImage (); + + #if DEBUG_KP_TOOL_CROP + qCDebug(kpLogImagelib) << "\thave pixmap; rect=" + << setTransparentImage.rect (); + #endif + } + else + { + setTransparentImage = m_imageIfFromSelectionDoesntHaveOne; + #if DEBUG_KP_TOOL_CROP + qCDebug(kpLogImagelib) << "\tno pixmap in sel - get it; rect=" + << setTransparentImage.rect (); + #endif + } + + kpPixmapFX::paintPixmapAt (&newDocImage, + QPoint (0, 0), + setTransparentImage); + + + document ()->setImageAt (newDocImage, QPoint (0, 0)); + document ()->selectionDelete (); + + + environ ()->somethingBelowTheCursorChanged (); + } + viewManager ()->restoreQueueUpdates (); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpCommand] +void SetDocumentToSelectionImageCommand::unexecute () +{ +#if DEBUG_KP_TOOL_CROP + qCDebug(kpLogImagelib) << "SetDocumentToSelectionImageCommand::unexecute()"; +#endif + + viewManager ()->setQueueUpdates (); + { + document ()->setImageAt (m_oldImage, QPoint (0, 0)); + m_oldImage = kpImage (); + + #if DEBUG_KP_TOOL_CROP + qCDebug(kpLogImagelib) << "\tsel: rect=" << m_fromSelectionPtr->boundingRect () + << " pm=" << m_fromSelectionPtr->hasContent (); + #endif + document ()->setSelection (*m_fromSelectionPtr); + + environ ()->somethingBelowTheCursorChanged (); + } + viewManager ()->restoreQueueUpdates (); +} + +//--------------------------------------------------------------------- + + +void kpTransformCrop_ImageSelection (kpMainWindow *mainWindow, + const QString &commandName, kpCommand *resizeDocCommand) +{ + // Save starting selection, minus the border. + auto *borderImageSel = dynamic_cast ( + mainWindow->document ()->selection ()->clone ()); + + Q_ASSERT (borderImageSel); + + if ( !borderImageSel ) { // make coverity happy + return; + } + + // (only interested in border) + borderImageSel->deleteContent (); + borderImageSel->moveTo (QPoint (0, 0)); + + auto *environ = mainWindow->commandEnvironment (); + auto *macroCmd = new kpMacroCommand (commandName, environ); + + // (must resize doc _before_ SetDocumentToSelectionImageCommand in case + // doc needs to gets bigger - else selection image may not fit) + macroCmd->addCommand (resizeDocCommand); + +#if DEBUG_KP_TOOL_CROP + qCDebug(kpLogImagelib) << "\tis pixmap sel"; + qCDebug(kpLogImagelib) << "\tcreating SetImage cmd"; +#endif + macroCmd->addCommand (new SetDocumentToSelectionImageCommand (environ)); + + + mainWindow->addImageOrSelectionCommand ( + macroCmd, + true/*add create cmd*/, + false/*don't add pull cmd*/); + + + // Add selection border back for convenience. + mainWindow->commandHistory ()->addCommand ( + new kpToolSelectionCreateCommand ( + i18n ("Selection: Create"), + *borderImageSel, + mainWindow->commandEnvironment ())); + + + delete borderImageSel; +} diff --git a/imagelib/transforms/kpTransformCrop_TextSelection.cpp b/imagelib/transforms/kpTransformCrop_TextSelection.cpp new file mode 100644 index 0000000..f629b31 --- /dev/null +++ b/imagelib/transforms/kpTransformCrop_TextSelection.cpp @@ -0,0 +1,76 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_TOOL_CROP 0 + + +#include "kpTransformCrop.h" +#include "kpTransformCropPrivate.h" + +#include "commands/imagelib/effects/kpEffectClearCommand.h" +#include "commands/kpMacroCommand.h" +#include "mainWindow/kpMainWindow.h" +#include "commands/tools/selection/kpToolSelectionMoveCommand.h" + + +void kpTransformCrop_TextSelection (kpMainWindow *mainWindow, + const QString &commandName, kpCommand *resizeDocCommand) +{ + kpCommandEnvironment *environ = mainWindow->commandEnvironment (); + + + auto *macroCmd = new kpMacroCommand (commandName, environ); + + macroCmd->addCommand (resizeDocCommand); + +#if DEBUG_KP_TOOL_CROP + qCDebug(kpLogImagelib) << "\tisText"; + qCDebug(kpLogImagelib) << "\tclearing doc with trans cmd"; +#endif + macroCmd->addCommand ( + new kpEffectClearCommand ( + false/*act on doc*/, + kpColor::Transparent, + environ)); + +#if DEBUG_KP_TOOL_CROP + qCDebug(kpLogImagelib) << "\tmoving sel to (0,0) cmd"; +#endif + kpToolSelectionMoveCommand *moveCmd = + new kpToolSelectionMoveCommand ( + QString()/*uninteresting child of macro cmd*/, + environ); + moveCmd->moveTo (QPoint (0, 0), true/*move on exec, not now*/); + moveCmd->finalize (); + macroCmd->addCommand (moveCmd); + + + mainWindow->addImageOrSelectionCommand ( + macroCmd, + true/*add create cmd*/, + true/*add create content cmd*/); +} diff --git a/kolourpaint.cpp b/kolourpaint.cpp new file mode 100644 index 0000000..f49dc80 --- /dev/null +++ b/kolourpaint.cpp @@ -0,0 +1,138 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2015,2016 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include + +#include "kpVersion.h" +#include "mainWindow/kpMainWindow.h" +#include + +#include +#include +#include +#include +#include + +int main(int argc, char *argv []) +{ + QApplication app(argc, argv); + QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); + + KLocalizedString::setApplicationDomain("kolourpaint"); + + KAboutData aboutData + ( + QStringLiteral("kolourpaint"), + i18n("KolourPaint"), + QStringLiteral(KOLOURPAINT_VERSION_STRING), + i18n("Paint Program by KDE"), + KAboutLicense::Custom, + QString(), // copyright statement - see license instead + QString(), // other text + QStringLiteral("http://www.kolourpaint.org/") // home page + ); + + // (this is _not_ the same as KAboutLicense::BSD) + aboutData.setLicenseText(i18n(kpLicenseText)); + aboutData.setDesktopFileName(QStringLiteral("org.kde.kolourpaint")); + + // Please add yourself here if you feel you're missing. + // SYNC: with AUTHORS + + aboutData.addAuthor(i18n("Clarence Dang"), i18n("Project Founder"), QStringLiteral("dang@kde.org")); + + aboutData.addAuthor(i18n("Thurston Dang"), i18n("Chief Investigator"), + QStringLiteral("thurston_dang@users.sourceforge.net")); + + aboutData.addAuthor(i18n("Martin Koller"), i18n("Scanning Support, Alpha Support, Current Maintainer"), + QStringLiteral("kollix@aon.at")); + + aboutData.addAuthor(i18n("Kristof Borrey"), i18n("Icons"), QStringLiteral("borrey@kde.org")); + aboutData.addAuthor(i18n("Tasuku Suzuki"), i18n("InputMethod Support"), QStringLiteral("stasuku@gmail.com")); + aboutData.addAuthor(i18n("Kazuki Ohta"), i18n("InputMethod Support"), QStringLiteral("mover@hct.zaq.ne.jp")); + aboutData.addAuthor(i18n("Nuno Pinheiro"), i18n("Icons"), QStringLiteral("nf.pinheiro@gmail.com")); + aboutData.addAuthor(i18n("Danny Allen"), i18n("Icons"), QStringLiteral("dannya40uk@yahoo.co.uk")); + aboutData.addAuthor(i18n("Mike Gashler"), i18n("Image Effects"), QStringLiteral("gashlerm@yahoo.com")); + + aboutData.addAuthor(i18n("Laurent Montel"), i18n("KDE 4 Porting"), QStringLiteral("montel@kde.org")); + aboutData.addAuthor(i18n("Christoph Feck"), i18n("KF 5 Porting"), QStringLiteral("cfeck@kde.org")); + + aboutData.addCredit(i18n("Thanks to the many others who have helped to make this program possible.")); + + QCommandLineParser cmdLine; + KAboutData::setApplicationData(aboutData); + QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("kolourpaint"), QApplication::windowIcon())); + cmdLine.addPositionalArgument(QStringLiteral("files"), i18n("Image files to open, optionally"), QStringLiteral("[files...]")); + + aboutData.setupCommandLine(&cmdLine); + cmdLine.addOption(QCommandLineOption("mimetypes", i18n("List all readable image MIME types"))); + cmdLine.process(app); + aboutData.processCommandLine(&cmdLine); + + // produce a list of MimeTypes which kolourpaint can handle (can be used inside the .desktop file) + if ( cmdLine.isSet("mimetypes") ) + { + foreach (const QByteArray &type, QImageReader::supportedMimeTypes()) + { + if ( !type.isEmpty() ) + printf("%s;", type.constData()); + } + + printf("\n"); + + return 0; + } + + if ( app.isSessionRestored() ) + { + // Creates a kpMainWindow using the default constructor and then + // calls kpMainWindow::readProperties(). + kRestoreMainWindows(); + } + else + { + kpMainWindow *mainWindow; + QStringList args = cmdLine.positionalArguments(); + + if ( args.count() >= 1 ) + { + for (int i = 0; i < args.count(); i++) + { + mainWindow = new kpMainWindow(QUrl::fromUserInput(args[i], QDir::currentPath(), QUrl::AssumeLocalFile)); + mainWindow->show(); + } + } + else + { + mainWindow = new kpMainWindow(); + mainWindow->show(); + } + } + + return QApplication::exec(); +} diff --git a/kolourpaint.qrc b/kolourpaint.qrc new file mode 100644 index 0000000..98b7fb8 --- /dev/null +++ b/kolourpaint.qrc @@ -0,0 +1,19 @@ + + + + pics/custom/colorbutton_swap_16x16.png + pics/custom/color_transparent_26x26.png + pics/custom/image_rotate_anticlockwise.png + pics/custom/image_rotate_clockwise.png + pics/custom/image_skew_horizontal.png + pics/custom/image_skew_vertical.png + pics/custom/option_opaque.png + pics/custom/option_transparent.png + pics/custom/resize.png + pics/custom/scale.png + pics/custom/smooth_scale.png + pics/custom/tool_spraycan_17x17.png + pics/custom/tool_spraycan_29x29.png + pics/custom/tool_spraycan_9x9.png + + diff --git a/kolourpaintui.rc b/kolourpaintui.rc new file mode 100644 index 0000000..0f3580c --- /dev/null +++ b/kolourpaintui.rc @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + &View + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + &Image + + + + + + + + + + + + + + + + + + + + + + + + + + + &Colors + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Main Toolbar + + + + + + + + + + + + + + + + +Selection Tool RMB Menu + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kpDefs.h b/kpDefs.h new file mode 100644 index 0000000..239ed49 --- /dev/null +++ b/kpDefs.h @@ -0,0 +1,138 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_DEFS_H +#define KP_DEFS_H + + +#include + +#include +#include +#include +#include + + +// approx. 2896x2896x32bpp or 3344x3344x24bpp (TODO: 24==32?) or 4096*4096x16bpp +#define KP_BIG_IMAGE_SIZE (32 * 1048576) + + +#define KP_INVALID_POINT QPoint (INT_MIN / 8, INT_MIN / 8) +#define KP_INVALID_WIDTH (INT_MIN / 8) +#define KP_INVALID_HEIGHT (INT_MIN / 8) +#define KP_INVALID_SIZE QSize (INT_MIN / 8, INT_MIN / 8) + + +#define KP_INCHES_PER_METER (100 / 2.54) +#define KP_MILLIMETERS_PER_INCH 25.4 + + +// +// Settings +// + +#define kpSettingsGroupRecentFiles "Recent Files" + +#define kpSettingsGroupGeneral "General Settings" +#define kpSettingFirstTime "First Time" +#define kpSettingShowGrid "Show Grid" +#define kpSettingShowPath "Show Path" +#define kpSettingDrawAntiAliased "Draw AntiAliased" +#define kpSettingColorSimilarity "Color Similarity" +#define kpSettingDitherOnOpen "Dither on Open if Screen is 15/16bpp and Image Num Colors More Than" +#define kpSettingPrintImageCenteredOnPage "Print Image Centered On Page" +#define kpSettingOpenImagesInSameWindow "Open Images in the Same Window" + +#define kpSettingsGroupFileSaveAs "File/Save As" +#define kpSettingsGroupFileExport "File/Export" +#define kpSettingsGroupEditCopyTo "Edit/Copy To" + +#define kpSettingForcedMimeType "Forced MimeType" +#define kpSettingForcedColorDepth "Forced Color Depth" +#define kpSettingForcedDither "Forced Dither" +#define kpSettingForcedQuality "Forced Quality" + +#define kpSettingLastDocSize "Last Document Size" + +#define kpSettingMoreEffectsLastEffect "More Effects - Last Effect" + +#define kpSettingsGroupUndoRedo "Undo/Redo Settings" +#define kpSettingUndoMinLimit "Min Limit" +#define kpSettingUndoMaxLimit "Max Limit" +#define kpSettingUndoMaxLimitSizeLimit "Max Limit Size Limit" + + +#define kpSettingsGroupThumbnail "Thumbnail Settings" +#define kpSettingThumbnailShown "Shown" +#define kpSettingThumbnailGeometry "Geometry" +#define kpSettingThumbnailZoomed "Zoomed" +#define kpSettingThumbnailShowRectangle "ShowRectangle" + + +#define kpSettingsGroupPreviewSave "Save Preview Settings" +#define kpSettingPreviewSaveGeometry "Geometry" +#define kpSettingPreviewSaveUpdateDelay "Update Delay" + + +#define kpSettingsGroupTools "Tool Settings" +#define kpSettingLastTool "Last Used Tool" +#define kpSettingToolBoxIconSize "Tool Box Icon Size" + + +#define kpSettingsGroupText "Text Settings" +#define kpSettingFontFamily "Font Family" +#define kpSettingFontSize "Font Size" +#define kpSettingBold "Bold" +#define kpSettingItalic "Italic" +#define kpSettingUnderline "Underline" +#define kpSettingStrikeThru "Strike Thru" + + +#define kpSettingsGroupFlattenEffect "Flatten Effect Settings" +#define kpSettingFlattenEffectColor1 "Color1" +#define kpSettingFlattenEffectColor2 "Color2" + + +// +// Session Restore Setting +// + +// URL of the document in the main window. +// +// This key only exists if the document does. If it exists, it can be empty. +// The URL need not point to a file that exists e.g. "kolourpaint doesnotexist.png". +#define kpSessionSettingDocumentUrl QString::fromLatin1 ("Session Document Url") + +// The size of a document which is not from a URL e.g. "kolourpaint doesnotexist.png". +// This key does not exist for documents from URLs. +#define kpSessionSettingNotFromUrlDocumentSize QString::fromLatin1 ("Session Not-From-Url Document Size") + + +#endif // KP_DEFS_H + + diff --git a/kpLogCategories.cpp b/kpLogCategories.cpp new file mode 100644 index 0000000..c6a6fe2 --- /dev/null +++ b/kpLogCategories.cpp @@ -0,0 +1,41 @@ +/* + Copyright (c) 2016 Martin Sandsmark + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "kpLogCategories.h" + +Q_LOGGING_CATEGORY(kpLogMisc, "kp.misc") +Q_LOGGING_CATEGORY(kpLogDialogs, "kp.dialogs") +Q_LOGGING_CATEGORY(kpLogCommands, "kp.commands") +Q_LOGGING_CATEGORY(kpLogDocument, "kp.document") +Q_LOGGING_CATEGORY(kpLogTools, "kp.tools") +Q_LOGGING_CATEGORY(kpLogViews, "kp.views") +Q_LOGGING_CATEGORY(kpLogEnvironments, "kp.environments") +Q_LOGGING_CATEGORY(kpLogPixmapfx, "kp.pixmapfx") +Q_LOGGING_CATEGORY(kpLogWidgets, "kp.widgets") +Q_LOGGING_CATEGORY(kpLogMainWindow, "kp.mainwindow") +Q_LOGGING_CATEGORY(kpLogLayers, "kp.layers") +Q_LOGGING_CATEGORY(kpLogImagelib, "kp.imagelib") + diff --git a/kpLogCategories.h b/kpLogCategories.h new file mode 100644 index 0000000..74d9e34 --- /dev/null +++ b/kpLogCategories.h @@ -0,0 +1,46 @@ +/* + Copyright (c) 2016 Martin Sandsmark + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef KPLOGCATEGORIES_H +#define KPLOGCATEGORIES_H + +#include + +Q_DECLARE_LOGGING_CATEGORY(kpLogMisc) +Q_DECLARE_LOGGING_CATEGORY(kpLogDialogs) +Q_DECLARE_LOGGING_CATEGORY(kpLogCommands) +Q_DECLARE_LOGGING_CATEGORY(kpLogDocument) +Q_DECLARE_LOGGING_CATEGORY(kpLogTools) +Q_DECLARE_LOGGING_CATEGORY(kpLogViews) +Q_DECLARE_LOGGING_CATEGORY(kpLogEnvironments) +Q_DECLARE_LOGGING_CATEGORY(kpLogPixmapfx) +Q_DECLARE_LOGGING_CATEGORY(kpLogWidgets) +Q_DECLARE_LOGGING_CATEGORY(kpLogMainWindow) +Q_DECLARE_LOGGING_CATEGORY(kpLogLayers) +Q_DECLARE_LOGGING_CATEGORY(kpLogImagelib) + +#endif + diff --git a/kpThumbnail.cpp b/kpThumbnail.cpp new file mode 100644 index 0000000..206d0ee --- /dev/null +++ b/kpThumbnail.cpp @@ -0,0 +1,183 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_THUMBNAIL 0 + + +#include "kpThumbnail.h" + +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "mainWindow/kpMainWindow.h" +#include "views/kpThumbnailView.h" +#include "tools/kpTool.h" + +#include "kpLogCategories.h" +#include + +#include +#include + + +struct kpThumbnailPrivate +{ + kpMainWindow *mainWindow; + kpThumbnailView *view; + QHBoxLayout *lay; +}; + +kpThumbnail::kpThumbnail (kpMainWindow *parent) + : kpSubWindow (parent), + d (new kpThumbnailPrivate ()) +{ + Q_ASSERT (parent); + + d->mainWindow = parent; + d->view = nullptr; + d->lay = new QHBoxLayout (this); + + + setMinimumSize (64, 64); + + + updateCaption (); +} + +kpThumbnail::~kpThumbnail () +{ + delete d; +} + + +// public +kpThumbnailView *kpThumbnail::view () const +{ + return d->view; +} + +// public +void kpThumbnail::setView (kpThumbnailView *view) +{ +#if DEBUG_KP_THUMBNAIL + qCDebug(kpLogMisc) << "kpThumbnail::setView(" << view << ")"; +#endif + + if (d->view == view) { + return; + } + + + if (d->view) + { + disconnect (d->view, &kpThumbnailView::destroyed, + this, &kpThumbnail::slotViewDestroyed); + + disconnect (d->view, &kpThumbnailView::zoomLevelChanged, + this, &kpThumbnail::updateCaption); + + d->lay->removeWidget (d->view); + } + + d->view = view; + + if (d->view) + { + connect (d->view, &kpThumbnailView::destroyed, + this, &kpThumbnail::slotViewDestroyed); + + connect (d->view, &kpThumbnailView::zoomLevelChanged, + this, &kpThumbnail::updateCaption); + + Q_ASSERT (d->view->parent () == this); + d->lay->addWidget (d->view, Qt::AlignCenter); + + d->view->show (); + } + + updateCaption (); +} + + +// public slot +void kpThumbnail::updateCaption () +{ + setWindowTitle (view () ? view ()->caption () : i18nc ("@title:window", "Thumbnail")); +} + + +// protected slot +void kpThumbnail::slotViewDestroyed () +{ +#if DEBUG_KP_THUMBNAIL + qCDebug(kpLogMisc) << "kpThumbnail::slotViewDestroyed()"; +#endif + + d->view = nullptr; + updateCaption (); +} + + +// protected virtual [base QWidget] +void kpThumbnail::resizeEvent (QResizeEvent *e) +{ +#if DEBUG_KP_THUMBNAIL + qCDebug(kpLogMisc) << "kpThumbnail::resizeEvent(" << width () + << "," << height () << ")"; +#endif + + QWidget::resizeEvent (e); + + // updateVariableZoom (); TODO: is below a good idea since this commented out? + + if (d->mainWindow) + { + d->mainWindow->notifyThumbnailGeometryChanged (); + + if (d->mainWindow->tool ()) { + d->mainWindow->tool ()->somethingBelowTheCursorChanged (); + } + } +} + +// protected virtual [base QWidget] +void kpThumbnail::moveEvent (QMoveEvent * /*e*/) +{ + if (d->mainWindow) { + d->mainWindow->notifyThumbnailGeometryChanged (); + } +} + +// protected virtual [base QWidget] +void kpThumbnail::closeEvent (QCloseEvent *e) +{ + QWidget::closeEvent (e); + + emit windowClosed (); +} + + diff --git a/kpThumbnail.h b/kpThumbnail.h new file mode 100644 index 0000000..d612c9b --- /dev/null +++ b/kpThumbnail.h @@ -0,0 +1,76 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_THUMBNAIL_H +#define KP_THUMBNAIL_H + + +#include "generic/widgets/kpSubWindow.h" + + +class QMoveEvent; +class QResizeEvent; + +class kpMainWindow; +class kpThumbnailView; + + +struct kpThumbnailPrivate; + +class kpThumbnail : public kpSubWindow +{ +Q_OBJECT + +public: + kpThumbnail (kpMainWindow *parent); + ~kpThumbnail () override; + +public: + kpThumbnailView *view () const; + void setView (kpThumbnailView *view); + +public slots: + void updateCaption (); + +protected slots: + void slotViewDestroyed (); + +protected: + void resizeEvent (QResizeEvent *e) override; + void moveEvent (QMoveEvent *e) override; + void closeEvent (QCloseEvent *e) override; + +signals: + void windowClosed (); + +private: + kpThumbnailPrivate * const d; +}; + + +#endif // KP_THUMBNAIL_H diff --git a/kpViewScrollableContainer.cpp b/kpViewScrollableContainer.cpp new file mode 100644 index 0000000..48a4274 --- /dev/null +++ b/kpViewScrollableContainer.cpp @@ -0,0 +1,1209 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2011 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_KP_VIEW_SCROLLABLE_CONTAINER 0 + +#include "kpViewScrollableContainer.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kpLogCategories.h" +#include + +#include "kpDefs.h" +#include "pixmapfx/kpPixmapFX.h" +#include "views/kpView.h" +#include "generic/kpWidgetMapper.h" + +//--------------------------------------------------------------------- + +// (Pulled from out of Thurston's hat) +static const int DragScrollLeftTopMargin = 0; +static const int DragScrollRightBottomMargin = 16; // scrollbarish +static const int DragScrollInterval = 150; +static const int DragScrollInitialInterval = DragScrollInterval * 2; +static const int DragScrollNumPixels = 10; +static const int DragDistanceFromRectMaxFor1stMultiplier = 50; +static const int DragDistanceFromRectMaxFor2ndMultiplier = 100; + +//--------------------------------------------------------------------- +//--------------------------------------------------------------------- +// a transparent widget above all others in the viewport used only while resizing the document +// to be able to show the resize lines above everything else + +class kpOverlay : public QWidget +{ + public: + kpOverlay(QWidget *parent, kpViewScrollableContainer *container) + : QWidget(parent), m_container(container) + { + } + + void paintEvent(QPaintEvent *) override + { + m_container->drawResizeLines(); + } + + private: + kpViewScrollableContainer *m_container; + +}; + +//--------------------------------------------------------------------- + +const int kpGrip::Size = 5; + +//--------------------------------------------------------------------- + +kpGrip::kpGrip (GripType type, QWidget *parent) + : QWidget(parent), + m_type (type), + m_startPoint (KP_INVALID_POINT), + m_currentPoint (KP_INVALID_POINT), + m_shouldReleaseMouseButtons (false) +{ + setCursor(cursorForType(m_type)); + + setMouseTracking(true); // mouseMoveEvent's even when no mousebtn down + + setAutoFillBackground(true); + setBackgroundRole(QPalette::Highlight); + + setFixedSize(kpGrip::Size, kpGrip::Size); +} + +//--------------------------------------------------------------------- + +// public +kpGrip::GripType kpGrip::type () const +{ + return m_type; +} + +//--------------------------------------------------------------------- + +// public static +QCursor kpGrip::cursorForType (GripType type) +{ + switch (type) + { + case kpGrip::Bottom: + return Qt::SizeVerCursor; + + case kpGrip::Right: + return Qt::SizeHorCursor; + + case kpGrip::BottomRight: + return Qt::SizeFDiagCursor; + } + + return Qt::ArrowCursor; +} + +//--------------------------------------------------------------------- + +// public +bool kpGrip::containsCursor() +{ + return isVisible() && + QRect(mapToGlobal(rect().topLeft()), + mapToGlobal(rect().bottomRight())).contains(QCursor::pos()); +} + +//--------------------------------------------------------------------- + +// public +bool kpGrip::isDrawing () const +{ + return (m_startPoint != KP_INVALID_POINT); +} + +//--------------------------------------------------------------------- + +// public +QString kpGrip::haventBegunDrawUserMessage () const +{ + return i18n ("Left drag the handle to resize the image."); +} + +//--------------------------------------------------------------------- + +// public +QString kpGrip::userMessage () const +{ + return m_userMessage; +} + +//--------------------------------------------------------------------- + +// public +void kpGrip::setUserMessage (const QString &message) +{ + // Don't do NOP checking here since another grip might have changed + // the message so an apparent NOP for this grip is not a NOP in the + // global sense (kpViewScrollableContainer::slotGripStatusMessageChanged()). + + m_userMessage = message; + emit statusMessageChanged (message); +} + +//--------------------------------------------------------------------- + +// protected +void kpGrip::cancel () +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + qCDebug(kpLogMisc) << "kpGrip::cancel()"; +#endif + if (m_currentPoint == KP_INVALID_POINT) { + return; + } + + m_startPoint = KP_INVALID_POINT; + m_currentPoint = KP_INVALID_POINT; + + setUserMessage (i18n ("Resize Image: Let go of all the mouse buttons.")); + setCursor (Qt::ArrowCursor); + m_shouldReleaseMouseButtons = true; + + emit cancelledDraw (); +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpGrip::keyReleaseEvent (QKeyEvent *e) +{ + if (m_startPoint != KP_INVALID_POINT && + e->key () == Qt::Key_Escape) + { + cancel (); + } +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpGrip::mousePressEvent (QMouseEvent *e) +{ + if (m_startPoint == KP_INVALID_POINT && + (e->buttons () & Qt::MouseButtonMask) == Qt::LeftButton) + { + m_startPoint = e->pos (); + m_currentPoint = e->pos (); + emit beganDraw (); + setFocus(); // allow to receive keyboard events to be able to handle ESC + + setUserMessage (i18n ("Resize Image: Right click to cancel.")); + setCursor (cursorForType (m_type)); + } + else + { + if (m_startPoint != KP_INVALID_POINT) { + cancel (); + } + } +} + +//--------------------------------------------------------------------- + +// public +QPoint kpGrip::viewDeltaPoint () const +{ + if (m_startPoint == KP_INVALID_POINT) { + return KP_INVALID_POINT; + } + + const QPoint point = mapFromGlobal (QCursor::pos ()); + + // TODO: this is getting out of sync with m_currentPoint + + return {(m_type & kpGrip::Right) ? point.x () - m_startPoint.x () : 0, + (m_type & kpGrip::Bottom) ? point.y () - m_startPoint.y () : 0}; + +} + +//--------------------------------------------------------------------- + +// public +void kpGrip::mouseMovedTo (const QPoint &point, bool dueToDragScroll) +{ + if (m_startPoint == KP_INVALID_POINT) { + return; + } + + m_currentPoint = point; + + emit continuedDraw (((m_type & kpGrip::Right) ? point.x () - m_startPoint.x () : 0), + ((m_type & kpGrip::Bottom) ? point.y () - m_startPoint.y () : 0), + dueToDragScroll); +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpGrip::mouseMoveEvent (QMouseEvent *e) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + qCDebug(kpLogMisc) << "kpGrip::mouseMoveEvent() m_startPoint=" << m_startPoint + << " stateAfter: buttons=" << (int *) (int) e->buttons (); +#endif + + if (m_startPoint == KP_INVALID_POINT) + { + if ((e->buttons () & Qt::MouseButtonMask) == 0) { + setUserMessage (haventBegunDrawUserMessage ()); + } + return; + } + + mouseMovedTo (e->pos (), false/*not due to drag scroll*/); +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpGrip::mouseReleaseEvent (QMouseEvent *e) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + qCDebug(kpLogMisc) << "kpGrip::mouseReleaseEvent() m_startPoint=" << m_startPoint + << " stateAfter: buttons=" << (int *) (int) e->buttons (); +#endif + + if (m_startPoint != KP_INVALID_POINT) + { + const int dx = m_currentPoint.x () - m_startPoint.x (), + dy = m_currentPoint.y () - m_startPoint.y (); + + m_currentPoint = KP_INVALID_POINT; + m_startPoint = KP_INVALID_POINT; + + emit endedDraw ((m_type & kpGrip::Right) ? dx : 0, + (m_type & kpGrip::Bottom) ? dy : 0); + } + + if ((e->buttons () & Qt::MouseButtonMask) == 0) + { + m_shouldReleaseMouseButtons = false; + setUserMessage(QString()); + setCursor (cursorForType (m_type)); + + emit releasedAllButtons (); + } +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +void kpGrip::enterEvent (QEnterEvent * /*e*/) +#else +void kpGrip::enterEvent (QEvent * /*e*/) +#endif +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + qCDebug(kpLogMisc) << "kpGrip::enterEvent()" + << " m_startPoint=" << m_startPoint + << " shouldReleaseMouseButtons=" + << m_shouldReleaseMouseButtons; +#endif + + if (m_startPoint == KP_INVALID_POINT && + !m_shouldReleaseMouseButtons) + { + #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + qCDebug(kpLogMisc) << "\tsending message"; + #endif + setUserMessage (haventBegunDrawUserMessage ()); + } +} + +//--------------------------------------------------------------------- + +// protected virtual [base QWidget] +void kpGrip::leaveEvent (QEvent * /*e*/) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + qCDebug(kpLogMisc) << "kpGrip::leaveEvent()" + << " m_startPoint=" << m_startPoint + << " shouldReleaseMouseButtons=" + << m_shouldReleaseMouseButtons; +#endif + if (m_startPoint == KP_INVALID_POINT && + !m_shouldReleaseMouseButtons) + { + setUserMessage(QString()); + } +} + +//--------------------------------------------------------------------- +//--------------------------------------------------------------------- +//--------------------------------------------------------------------- + +// TODO: Are we checking for m_view == 0 often enough? Also an issue in KDE 3. +kpViewScrollableContainer::kpViewScrollableContainer(QWidget *parent) + : QScrollArea(parent), + m_view(nullptr), m_overlay(new kpOverlay(viewport(), this)), + m_docResizingGrip (nullptr), + m_dragScrollTimer (new QTimer (this)), + m_zoomLevel (100), + m_scrollTimerRunOnce (false), + m_resizeRoundedLastViewX (-1), m_resizeRoundedLastViewY (-1), + m_resizeRoundedLastViewDX (0), m_resizeRoundedLastViewDY (0), + m_haveMovedFromOriginalDocSize (false) + +{ + // the base widget holding the documents view plus the resize grips + setWidget(new QWidget(viewport())); + + m_bottomGrip = new kpGrip(kpGrip::Bottom, widget()); + m_rightGrip = new kpGrip(kpGrip::Right, widget()); + m_bottomRightGrip = new kpGrip(kpGrip::BottomRight, widget()); + + m_bottomGrip->setObjectName(QStringLiteral("Bottom Grip")); + m_rightGrip->setObjectName(QStringLiteral("Right Grip")); + m_bottomRightGrip->setObjectName(QStringLiteral("BottomRight Grip")); + + m_bottomGrip->hide (); + connectGripSignals (m_bottomGrip); + + m_rightGrip->hide (); + connectGripSignals (m_rightGrip); + + m_bottomRightGrip->hide (); + connectGripSignals (m_bottomRightGrip); + + + connect (horizontalScrollBar(), &QScrollBar::valueChanged, + this, &kpViewScrollableContainer::slotContentsMoved); + + connect (verticalScrollBar(), &QScrollBar::valueChanged, + this, &kpViewScrollableContainer::slotContentsMoved); + + connect (m_dragScrollTimer, &QTimer::timeout, this, [this]{slotDragScroll();}); + + m_overlay->hide(); +} + +//--------------------------------------------------------------------- + +// protected +void kpViewScrollableContainer::connectGripSignals (kpGrip *grip) +{ + connect (grip, &kpGrip::beganDraw, + this, &kpViewScrollableContainer::slotGripBeganDraw); + + connect (grip, &kpGrip::continuedDraw, + this, &kpViewScrollableContainer::slotGripContinuedDraw); + + connect (grip, &kpGrip::cancelledDraw, + this, &kpViewScrollableContainer::slotGripCancelledDraw); + + connect (grip, &kpGrip::endedDraw, + this, &kpViewScrollableContainer::slotGripEndedDraw); + + connect (grip, &kpGrip::statusMessageChanged, + this, &kpViewScrollableContainer::slotGripStatusMessageChanged); + + connect (grip, &kpGrip::releasedAllButtons, + this, &kpViewScrollableContainer::recalculateStatusMessage); +} + +//--------------------------------------------------------------------- + +// public +QSize kpViewScrollableContainer::newDocSize () const +{ + return newDocSize (m_resizeRoundedLastViewDX, + m_resizeRoundedLastViewDY); +} + +//--------------------------------------------------------------------- + +// public +bool kpViewScrollableContainer::haveMovedFromOriginalDocSize () const +{ + return m_haveMovedFromOriginalDocSize; +} + +//--------------------------------------------------------------------- + +// public +QString kpViewScrollableContainer::statusMessage () const +{ + return m_gripStatusMessage; +} + +//--------------------------------------------------------------------- + +// public +void kpViewScrollableContainer::clearStatusMessage () +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER && 1 + qCDebug(kpLogMisc) << "kpViewScrollableContainer::clearStatusMessage()"; +#endif + m_bottomRightGrip->setUserMessage(QString()); + m_bottomGrip->setUserMessage(QString()); + m_rightGrip->setUserMessage(QString()); +} + +//--------------------------------------------------------------------- + +// protected +QSize kpViewScrollableContainer::newDocSize (int viewDX, int viewDY) const +{ + if (!m_view) { + return {}; + } + + if (!docResizingGrip ()) { + return {}; + } + + const int docX = static_cast (m_view->transformViewToDocX (m_view->width () + viewDX)); + const int docY = static_cast (m_view->transformViewToDocY (m_view->height () + viewDY)); + + return {qMax (1, docX), qMax (1, docY)}; +} + +//--------------------------------------------------------------------- + +// protected +void kpViewScrollableContainer::calculateDocResizingGrip () +{ + if (m_bottomRightGrip->isDrawing ()) { + m_docResizingGrip = m_bottomRightGrip; + } + else if (m_bottomGrip->isDrawing ()) { + m_docResizingGrip = m_bottomGrip; + } + else if (m_rightGrip->isDrawing ()) { + m_docResizingGrip = m_rightGrip; + } + else { + m_docResizingGrip = nullptr; + } +} + +//--------------------------------------------------------------------- + +// protected +kpGrip *kpViewScrollableContainer::docResizingGrip () const +{ + return m_docResizingGrip; +} + +//--------------------------------------------------------------------- + +// protected +int kpViewScrollableContainer::bottomResizeLineWidth () const +{ + if (!docResizingGrip ()) { + return -1; + } + + if (!m_view) { + return -1; + } + + if (docResizingGrip ()->type () & kpGrip::Bottom) { + return qMax (m_view->zoomLevelY () / 100, 1); + } + + return 1; +} + +//--------------------------------------------------------------------- + +// protected +int kpViewScrollableContainer::rightResizeLineWidth () const +{ + if (!docResizingGrip ()) { + return -1; + } + + if (!m_view) { + return -1; + } + + if (docResizingGrip ()->type () & kpGrip::Right) { + return qMax (m_view->zoomLevelX () / 100, 1); + } + + return 1; +} + +//--------------------------------------------------------------------- + +// protected +QRect kpViewScrollableContainer::bottomResizeLineRect () const +{ + if (m_resizeRoundedLastViewX < 0 || m_resizeRoundedLastViewY < 0) { + return {}; + } + + QRect visibleArea = QRect(QPoint(horizontalScrollBar()->value(),verticalScrollBar()->value()), viewport()->size()); + + return QRect (QPoint (0, + m_resizeRoundedLastViewY), + QPoint (m_resizeRoundedLastViewX - 1, + m_resizeRoundedLastViewY + bottomResizeLineWidth () - 1)).intersected(visibleArea); +} + +//--------------------------------------------------------------------- + +// protected +QRect kpViewScrollableContainer::rightResizeLineRect () const +{ + if (m_resizeRoundedLastViewX < 0 || m_resizeRoundedLastViewY < 0) { + return {}; + } + + QRect visibleArea = QRect(QPoint(horizontalScrollBar()->value(),verticalScrollBar()->value()), viewport()->size()); + + return QRect (QPoint (m_resizeRoundedLastViewX, + 0), + QPoint (m_resizeRoundedLastViewX + rightResizeLineWidth () - 1, + m_resizeRoundedLastViewY - 1)).intersected(visibleArea); +} + +//--------------------------------------------------------------------- + +// protected +QRect kpViewScrollableContainer::bottomRightResizeLineRect () const +{ + if (m_resizeRoundedLastViewX < 0 || m_resizeRoundedLastViewY < 0) { + return {}; + } + + QRect visibleArea = QRect(QPoint(horizontalScrollBar()->value(),verticalScrollBar()->value()), viewport()->size()); + + return QRect (QPoint (m_resizeRoundedLastViewX, + m_resizeRoundedLastViewY), + QPoint (m_resizeRoundedLastViewX + rightResizeLineWidth () - 1, + m_resizeRoundedLastViewY + bottomResizeLineWidth () - 1)).intersected(visibleArea); +} + +//--------------------------------------------------------------------- + +// private +QRect kpViewScrollableContainer::mapViewToViewport (const QRect &viewRect) +{ + if (!viewRect.isValid ()) { + return {}; + } + + QRect ret = viewRect; + ret.translate (-horizontalScrollBar()->value() - viewport()->x(), -verticalScrollBar()->value() - viewport()->y()); + return ret; +} + +//--------------------------------------------------------------------- + +void kpViewScrollableContainer::drawResizeLines () +{ + static const char *stipple[] = + { + "8 8 2 1", + ". c #000000", + "# c #ffffff", + "....####", + "....####", + "....####", + "....####", + "####....", + "####....", + "####....", + "####...." + }; + + QPainter p(m_overlay); + p.setBackground(QPixmap(stipple)); + + const QRect rightRect = rightResizeLineRect(); + if ( rightRect.isValid() ) + { + QRect rect = mapViewToViewport(rightRect); + p.setBrushOrigin(rect.x(), rect.y()); + p.eraseRect(rect); + } + + const QRect bottomRect = bottomResizeLineRect(); + if ( bottomRect.isValid() ) + { + QRect rect = mapViewToViewport(bottomRect); + p.setBrushOrigin(rect.x(), rect.y()); + p.eraseRect(rect); + } + + const QRect bottomRightRect = bottomRightResizeLineRect (); + if ( bottomRightRect.isValid() ) + { + QRect rect = mapViewToViewport(bottomRightRect); + p.setBrushOrigin(rect.x(), rect.y()); + p.eraseRect(rect); + } +} + +//--------------------------------------------------------------------- + +// protected +void kpViewScrollableContainer::updateResizeLines (int viewX, int viewY, + int viewDX, int viewDY) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER && 0 + qCDebug(kpLogMisc) << "kpViewScrollableContainer::updateResizeLines(" + << viewX << "," << viewY << ")" + << " oldViewX=" << m_resizeRoundedLastViewX + << " oldViewY=" << m_resizeRoundedLastViewY + << " viewDX=" << viewDX + << " viewDY=" << viewDY; +#endif + + + if (viewX >= 0 && viewY >= 0) + { + m_resizeRoundedLastViewX = + static_cast (m_view->transformDocToViewX (m_view->transformViewToDocX (viewX))); + + m_resizeRoundedLastViewY = + static_cast (m_view->transformDocToViewY (m_view->transformViewToDocY (viewY))); + + m_resizeRoundedLastViewDX = viewDX; + m_resizeRoundedLastViewDY = viewDY; + } + else + { + m_resizeRoundedLastViewX = -1; + m_resizeRoundedLastViewY = -1; + + m_resizeRoundedLastViewDX = 0; + m_resizeRoundedLastViewDY = 0; + } + + m_overlay->update(); +} + +//--------------------------------------------------------------------- + +// protected slot +void kpViewScrollableContainer::slotGripBeganDraw () +{ + if (!m_view) { + return; + } + + m_overlay->resize(viewport()->size()); // make it cover whole viewport + m_overlay->move(viewport()->pos()); + m_overlay->show(); + m_overlay->raise(); // make it top-most + + calculateDocResizingGrip (); + + m_haveMovedFromOriginalDocSize = false; + + updateResizeLines (m_view->width (), m_view->height (), + 0/*viewDX*/, 0/*viewDY*/); + + emit beganDocResize (); +} + +//--------------------------------------------------------------------- + +// protected slot +void kpViewScrollableContainer::slotGripContinuedDraw (int inViewDX, int inViewDY, + bool dueToDragScroll) +{ + int viewDX = inViewDX, + viewDY = inViewDY; + +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + qCDebug(kpLogMisc) << "kpViewScrollableContainer::slotGripContinuedDraw(" + << viewDX << "," << viewDY << ") size=" + << newDocSize (viewDX, viewDY) + << " dueToDragScroll=" << dueToDragScroll; +#endif + + if (!m_view) { + return; + } + + if (!dueToDragScroll && + beginDragScroll(m_view->zoomLevelX ())) + { + const QPoint newViewDeltaPoint = docResizingGrip ()->viewDeltaPoint (); + viewDX = newViewDeltaPoint.x (); + viewDY = newViewDeltaPoint.y (); + #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + qCDebug(kpLogMisc) << "\tdrag scrolled - new view delta point=" + << newViewDeltaPoint; + #endif + } + + m_haveMovedFromOriginalDocSize = true; + + updateResizeLines (qMax (1, qMax (m_view->width () + viewDX, static_cast (m_view->transformDocToViewX (1)))), + qMax (1, qMax (m_view->height () + viewDY, static_cast (m_view->transformDocToViewY (1)))), + viewDX, viewDY); + + emit continuedDocResize (newDocSize ()); +} + +//--------------------------------------------------------------------- + +// protected slot +void kpViewScrollableContainer::slotGripCancelledDraw () +{ + m_haveMovedFromOriginalDocSize = false; + + updateResizeLines (-1, -1, 0, 0); + + calculateDocResizingGrip (); + + emit cancelledDocResize (); + + endDragScroll (); + + m_overlay->hide(); +} + +//--------------------------------------------------------------------- + +// protected slot +void kpViewScrollableContainer::slotGripEndedDraw (int viewDX, int viewDY) +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + qCDebug(kpLogMisc) << "kpViewScrollableContainer::slotGripEndedDraw(" + << viewDX << "," << viewDY << ") size=" + << newDocSize (viewDX, viewDY); +#endif + + if (!m_view) { + return; + } + + const QSize newSize = newDocSize (viewDX, viewDY); + + m_haveMovedFromOriginalDocSize = false; + + // must erase lines before view size changes + updateResizeLines (-1, -1, 0, 0); + + calculateDocResizingGrip (); + + emit endedDocResize (newSize); + + endDragScroll (); + + m_overlay->hide(); +} + +//--------------------------------------------------------------------- + +// protected slot +void kpViewScrollableContainer::slotGripStatusMessageChanged (const QString &string) +{ + if (string == m_gripStatusMessage) { + return; + } + + m_gripStatusMessage = string; + emit statusMessageChanged (string); +} + +//--------------------------------------------------------------------- + +// public slot +void kpViewScrollableContainer::recalculateStatusMessage () +{ +#if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER + qCDebug(kpLogMisc) << "kpViewScrollabelContainer::recalculateStatusMessage()"; + qCDebug(kpLogMisc) << "\tQCursor::pos=" << QCursor::pos () + << " global visibleRect=" + << kpWidgetMapper::toGlobal (this, + QRect(0, 0, viewport->width(), viewport->height())); +#endif + + // HACK: After dragging to a new size, handles move so that they are now + // under the mouse pointer but no mouseMoveEvent() is generated for + // any grip. This also handles the case of canceling over any + // grip. + // + if (kpWidgetMapper::toGlobal (this, + QRect(0, 0, viewport()->width(), viewport()->height())) + .contains (QCursor::pos ())) + { + if ( m_bottomRightGrip->containsCursor() ) + { + m_bottomRightGrip->setUserMessage (i18n ("Left drag the handle to resize the image.")); + } + else if ( m_bottomGrip->containsCursor() ) + { + m_bottomGrip->setUserMessage (i18n ("Left drag the handle to resize the image.")); + } + else if ( m_rightGrip->containsCursor() ) + { + m_rightGrip->setUserMessage (i18n ("Left drag the handle to resize the image.")); + } + else + { + clearStatusMessage (); + } + } + else + { + clearStatusMessage (); + } +} + +//--------------------------------------------------------------------- + +// protected slot +void kpViewScrollableContainer::slotContentsMoved () +{ + kpGrip *grip = docResizingGrip (); + if (grip) + { + grip->mouseMovedTo (grip->mapFromGlobal (QCursor::pos ()), + true/*moved due to drag scroll*/); + } + + m_overlay->move(viewport()->pos()); + m_overlay->update(); + + emit contentsMoved(); +} + +//--------------------------------------------------------------------- + +// protected +void kpViewScrollableContainer::disconnectViewSignals () +{ + disconnect (m_view, + static_cast(&kpView::sizeChanged), + this, &kpViewScrollableContainer::updateGrips); + + disconnect (m_view, &kpView::destroyed, + this, &kpViewScrollableContainer::slotViewDestroyed); +} + +//--------------------------------------------------------------------- + +// protected +void kpViewScrollableContainer::connectViewSignals () +{ + connect (m_view, + static_cast(&kpView::sizeChanged), + this, &kpViewScrollableContainer::updateGrips); + + connect (m_view, &kpView::destroyed, + this, &kpViewScrollableContainer::slotViewDestroyed); +} + +//--------------------------------------------------------------------- + +// public +kpView *kpViewScrollableContainer::view () const +{ + return m_view; +} + +//--------------------------------------------------------------------- + +// public +void kpViewScrollableContainer::setView (kpView *view) +{ + if (m_view == view) { + return; + } + + if (m_view) + { + disconnectViewSignals (); + } + + m_view = view; + + if ( m_view ) + { + m_view->setParent(widget()); + m_view->show(); + } + + updateGrips (); + + if (m_view) + { + connectViewSignals (); + } +} + +//--------------------------------------------------------------------- + +// public slot +void kpViewScrollableContainer::updateGrips () +{ + if (m_view) + { + widget()->resize(m_view->size() + m_bottomRightGrip->size()); + + // to make the grip more easily "touchable" make it as high as the view + m_rightGrip->setFixedHeight(m_view->height()); + m_rightGrip->move(m_view->width(), 0); + + // to make the grip more easily "touchable" make it as wide as the view + m_bottomGrip->setFixedWidth(m_view->width()); + m_bottomGrip->move(0, m_view->height ()); + + m_bottomRightGrip->move(m_view->width(), m_view->height()); + } + + m_bottomGrip->setHidden (m_view == nullptr); + m_rightGrip->setHidden (m_view == nullptr); + m_bottomRightGrip->setHidden (m_view == nullptr); + + recalculateStatusMessage (); +} + +//--------------------------------------------------------------------- + +// protected slot +void kpViewScrollableContainer::slotViewDestroyed () +{ + m_view = nullptr; + updateGrips (); +} + +//--------------------------------------------------------------------- + +// public slot +bool kpViewScrollableContainer::beginDragScroll(int zoomLevel, bool *didSomething) +{ + if (didSomething) { + *didSomething = false; + } + + m_zoomLevel = zoomLevel; + + const QPoint p = mapFromGlobal (QCursor::pos ()); + + bool stopDragScroll = true; + bool scrolled = false; + + if (!noDragScrollRect ().contains (p)) + { + if (m_dragScrollTimer->isActive ()) + { + if (m_scrollTimerRunOnce) + { + scrolled = slotDragScroll (); + } + } + else + { + m_scrollTimerRunOnce = false; + m_dragScrollTimer->start (DragScrollInitialInterval); + } + + stopDragScroll = false; + } + + if (stopDragScroll) { + m_dragScrollTimer->stop (); + } + + if (didSomething) { + *didSomething = scrolled; + } + + return scrolled; +} + +//--------------------------------------------------------------------- + +// public slot +bool kpViewScrollableContainer::beginDragScroll(int zoomLevel) +{ + return beginDragScroll(zoomLevel, + nullptr/*don't want scrolled notification*/); +} + +//--------------------------------------------------------------------- + +// public slot +bool kpViewScrollableContainer::endDragScroll () +{ + if (m_dragScrollTimer->isActive ()) + { + m_dragScrollTimer->stop (); + return true; + } + + return false; +} + +//--------------------------------------------------------------------- + +static int distanceFromRectToMultiplier (int dist) +{ + if (dist < 0) { + return 0; + } + + if (dist < DragDistanceFromRectMaxFor1stMultiplier) { + return 1; + } + + if (dist < DragDistanceFromRectMaxFor2ndMultiplier) { + return 2; + } + + return 4; +} + +//--------------------------------------------------------------------- + +// protected slot +bool kpViewScrollableContainer::slotDragScroll (bool *didSomething) +{ + bool scrolled = false; + + if (didSomething) { + *didSomething = false; + } + + + const QRect rect = noDragScrollRect (); + const QPoint pos = mapFromGlobal (QCursor::pos ()); + + int dx = 0, dy = 0; + int dxMultiplier = 0, dyMultiplier = 0; + + if (pos.x () < rect.left ()) + { + dx = -DragScrollNumPixels; + dxMultiplier = distanceFromRectToMultiplier (rect.left () - pos.x ()); + } + else if (pos.x () > rect.right ()) + { + dx = +DragScrollNumPixels; + dxMultiplier = distanceFromRectToMultiplier (pos.x () - rect.right ()); + } + + if (pos.y () < rect.top ()) + { + dy = -DragScrollNumPixels; + dyMultiplier = distanceFromRectToMultiplier (rect.top () - pos.y ()); + } + else if (pos.y () > rect.bottom ()) + { + dy = +DragScrollNumPixels; + dyMultiplier = distanceFromRectToMultiplier (pos.y () - rect.bottom ()); + } + + dx *= dxMultiplier;// * qMax (1, m_zoomLevel / 100); + dy *= dyMultiplier;// * qMax (1, m_zoomLevel / 100); + + if (dx || dy) + { + const int oldContentsX = horizontalScrollBar()->value (), + oldContentsY = verticalScrollBar()->value (); + + horizontalScrollBar()->setValue(oldContentsX + dx); + verticalScrollBar()->setValue(oldContentsY + dy); + + scrolled = (oldContentsX != horizontalScrollBar()->value () || + oldContentsY != verticalScrollBar()->value ()); + + if (scrolled) + { + QRegion region = QRect (horizontalScrollBar()->value (), verticalScrollBar()->value (), + viewport()->width(), viewport()->height()); + region -= QRect (oldContentsX, oldContentsY, + viewport()->width(), viewport()->height()); + + // Repaint newly exposed region immediately to reduce tearing + // of scrollView. + m_view->repaint (region); + } + } + + m_dragScrollTimer->start (DragScrollInterval); + m_scrollTimerRunOnce = true; + + if (didSomething) { + *didSomething = scrolled; + } + + return scrolled; +} + + +//--------------------------------------------------------------------- + +// protected virtual +void kpViewScrollableContainer::wheelEvent (QWheelEvent *e) +{ + e->ignore (); + + if (m_view) { + m_view->wheelEvent (e); + } + + if ( !e->isAccepted() ) { + QScrollArea::wheelEvent(e); + } +} + +//--------------------------------------------------------------------------------- + +QRect kpViewScrollableContainer::noDragScrollRect () const +{ + return {DragScrollLeftTopMargin, DragScrollLeftTopMargin, + width () - DragScrollLeftTopMargin - DragScrollRightBottomMargin, + height () - DragScrollLeftTopMargin - DragScrollRightBottomMargin}; +} + +//--------------------------------------------------------------------- + +// protected virtual [base QScrollView] +void kpViewScrollableContainer::resizeEvent (QResizeEvent *e) +{ + QScrollArea::resizeEvent (e); + + emit resized (); +} + +//--------------------------------------------------------------------- + diff --git a/kpViewScrollableContainer.h b/kpViewScrollableContainer.h new file mode 100644 index 0000000..c579a5d --- /dev/null +++ b/kpViewScrollableContainer.h @@ -0,0 +1,217 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_VIEW_SCROLLABLE_CONTAINER_H +#define KP_VIEW_SCROLLABLE_CONTAINER_H + + +#include +#include +#include +#include + + +class QCursor; +class QEvent; +class QKeyEvent; +class QMouseEvent; +class QRect; +class QResizeEvent; +class QTimer; + +class kpView; +class kpOverlay; + + +//--------------------------------------------------------------------- + +// REFACTOR: refactor by sharing iface's with kpTool +class kpGrip : public QWidget +{ +Q_OBJECT + +public: + enum GripType + { + Right = 1, Bottom = 2, + BottomRight = Right | Bottom + }; + + kpGrip (GripType type, QWidget *parent); + + GripType type () const; + + static QCursor cursorForType (GripType type); + + bool containsCursor(); + + bool isDrawing () const; + + static const int Size; + +signals: + void beganDraw (); + void continuedDraw (int viewDX, int viewDY, bool dueToDragScroll); + void cancelledDraw (); + void endedDraw (int viewDX, int viewDY); + + void statusMessageChanged (const QString &string); + + void releasedAllButtons (); + +public: + QString haventBegunDrawUserMessage () const; + + QString userMessage () const; + void setUserMessage (const QString &message); + +protected: + void cancel (); + +protected: + void keyReleaseEvent (QKeyEvent *e) override; + void mousePressEvent (QMouseEvent *e) override; +public: + QPoint viewDeltaPoint () const; + void mouseMovedTo (const QPoint &point, bool dueToDragScroll); +protected: + void mouseMoveEvent (QMouseEvent *e) override; + void mouseReleaseEvent (QMouseEvent *e) override; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + void enterEvent (QEnterEvent *e) override; +#else + void enterEvent (QEvent *e) override; +#endif + void leaveEvent (QEvent *e) override; + +protected: + GripType m_type; + QPoint m_startPoint, m_currentPoint; + QString m_userMessage; + bool m_shouldReleaseMouseButtons; +}; + +//--------------------------------------------------------------------- + +class kpViewScrollableContainer : public QScrollArea +{ +Q_OBJECT + +public: + kpViewScrollableContainer(QWidget *parent); + + QSize newDocSize () const; + bool haveMovedFromOriginalDocSize () const; + QString statusMessage () const; + void clearStatusMessage (); + + kpView *view () const; + void setView (kpView *view); + + void drawResizeLines(); // public only for kpOverlay + +signals: + void contentsMoved(); + + void beganDocResize (); + void continuedDocResize (const QSize &size); + void cancelledDocResize (); + void endedDocResize (const QSize &size); + + // (string.isEmpty() if kpViewScrollableContainer has nothing to say) + void statusMessageChanged (const QString &string); + + void resized (); + +public slots: + void recalculateStatusMessage (); + + void updateGrips (); + + // TODO: Why the need for view's zoomLevel? We have the view() anyway. + bool beginDragScroll (int zoomLevel, + bool *didSomething); + bool beginDragScroll (int zoomLevel); + bool endDragScroll (); + +private: + void connectGripSignals (kpGrip *grip); + + QSize newDocSize (int viewDX, int viewDY) const; + + void calculateDocResizingGrip (); + kpGrip *docResizingGrip () const; + + int bottomResizeLineWidth () const; + int rightResizeLineWidth () const; + + QRect bottomResizeLineRect () const; + QRect rightResizeLineRect () const; + QRect bottomRightResizeLineRect () const; + + QRect mapViewToViewport (const QRect &viewRect); + + void updateResizeLines (int viewX, int viewY, + int viewDX, int viewDY); + + void disconnectViewSignals (); + void connectViewSignals (); + + QRect noDragScrollRect () const; + + void wheelEvent(QWheelEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + +private slots: + void slotGripBeganDraw (); + void slotGripContinuedDraw (int viewDX, int viewDY, bool dueToScrollView); + void slotGripCancelledDraw (); + void slotGripEndedDraw (int viewDX, int viewDY); + + void slotGripStatusMessageChanged (const QString &string); + + void slotContentsMoved (); + void slotViewDestroyed (); + bool slotDragScroll (bool *didSomething = nullptr); + +private: + kpView *m_view; + kpOverlay *m_overlay; + kpGrip *m_bottomGrip, *m_rightGrip, *m_bottomRightGrip; + kpGrip *m_docResizingGrip; + QTimer *m_dragScrollTimer; + int m_zoomLevel; + bool m_scrollTimerRunOnce; + int m_resizeRoundedLastViewX, m_resizeRoundedLastViewY; + int m_resizeRoundedLastViewDX, m_resizeRoundedLastViewDY; + bool m_haveMovedFromOriginalDocSize; + QString m_gripStatusMessage; +}; + +#endif // KP_VIEW_SCROLLABLE_CONTAINER_H diff --git a/layers/selections/image/kpAbstractImageSelection.cpp b/layers/selections/image/kpAbstractImageSelection.cpp new file mode 100644 index 0000000..288dc92 --- /dev/null +++ b/layers/selections/image/kpAbstractImageSelection.cpp @@ -0,0 +1,589 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_SELECTION 0 + + +#include "layers/selections/image/kpAbstractImageSelection.h" + +#include +#include + +#include "kpLogCategories.h" + +//--------------------------------------------------------------------- + +// Returns whether can be set to have . +// In other words, this is the precondition for .setBaseImage(width () && + baseImage.height () == sel->height ()); +} + +//--------------------------------------------------------------------- + +struct kpAbstractImageSelectionPrivate +{ + kpImage baseImage; + + kpImageSelectionTransparency transparency; + + // The mask for the image, after selection transparency (a.k.a. background + // subtraction) is applied. + QBitmap transparencyMaskCache; // OPT: calculate lazily i.e. on-demand only +}; + +//--------------------------------------------------------------------- + +// protected +kpAbstractImageSelection::kpAbstractImageSelection ( + const kpImageSelectionTransparency &transparency) + : kpAbstractSelection (), + d (new kpAbstractImageSelectionPrivate ()) +{ + setTransparency (transparency); +} + +//--------------------------------------------------------------------- + +// protected +kpAbstractImageSelection::kpAbstractImageSelection (const QRect &rect, + const kpImage &baseImage, + const kpImageSelectionTransparency &transparency) + : kpAbstractSelection (rect), + d (new kpAbstractImageSelectionPrivate ()) +{ + // This also checks that and have compatible + // relative dimensions. + setBaseImage (baseImage); + + setTransparency (transparency); +} + +//--------------------------------------------------------------------- + +// protected +kpAbstractImageSelection::kpAbstractImageSelection (const QRect &rect, + const kpImageSelectionTransparency &transparency) + : kpAbstractSelection (rect), + d (new kpAbstractImageSelectionPrivate ()) +{ + setTransparency (transparency); +} + +//--------------------------------------------------------------------- + +// protected +kpAbstractImageSelection &kpAbstractImageSelection::operator= ( + const kpAbstractImageSelection &rhs) +{ + kpAbstractSelection::operator= (rhs); + + d->baseImage = rhs.d->baseImage; + + d->transparency = rhs.d->transparency; + d->transparencyMaskCache = rhs.d->transparencyMaskCache; + + return *this; +} + +//--------------------------------------------------------------------- + +// protected +kpAbstractImageSelection::~kpAbstractImageSelection () +{ + delete d; +} + +//--------------------------------------------------------------------- + +// public virtual [base kpAbstractSelection] +bool kpAbstractImageSelection::readFromStream (QDataStream &stream) +{ + if (!kpAbstractSelection::readFromStream (stream )) { + return false; + } + + QImage qimage; + stream >> qimage; +#if DEBUG_KP_SELECTION && 1 + qCDebug(kpLogLayers) << "\timage: w=" << qimage.width () << " h=" << qimage.height () + << " depth=" << qimage.depth (); +#endif + + if (!qimage.isNull ()) + { + // Image size does not match the selection's dimensions? + // This call only accesses our superclass' fields, which have already + // been read in. + if (!::CanSetBaseImageTo (this, qimage)) + { + return false; + } + + d->baseImage = qimage; + } + // (was just a selection border in the clipboard, even though KolourPaint's + // GUI doesn't allow you to copy such a thing into the clipboard) + else { + d->baseImage = kpImage (); + } + + // TODO: Reset transparency mask? + // TODO: Concrete subclass need to emit changed()? + // [we can't since changed() must be called after all reading + // is complete and subclasses always call this method + // _before_ their reading logic] + return true; +} + +//--------------------------------------------------------------------- + +// public virtual [base kpAbstractSelection] +void kpAbstractImageSelection::writeToStream (QDataStream &stream) const +{ + kpAbstractSelection::writeToStream (stream); + + if (!d->baseImage.isNull ()) + { + const QImage image = d->baseImage; + #if DEBUG_KP_SELECTION && 1 + qCDebug(kpLogLayers) << "\twrote image rect=" << image.rect (); + #endif + stream << image; + } + else + { + #if DEBUG_KP_SELECTION && 1 + qCDebug(kpLogLayers) << "\twrote no image because no pixmap"; + #endif + stream << QImage (); + } +} + +//--------------------------------------------------------------------- + +// public virtual [kpAbstractSelection] +QString kpAbstractImageSelection::name () const +{ + return i18n ("Selection"); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpAbstractSelection] +kpCommandSize::SizeType kpAbstractImageSelection::size () const +{ + return kpAbstractSelection::size () + + kpCommandSize::ImageSize (d->baseImage) + + (d->transparencyMaskCache.width() * d->transparencyMaskCache.height()) / 8; +} + +//--------------------------------------------------------------------- + +// public +kpCommandSize::SizeType kpAbstractImageSelection::sizeWithoutImage () const +{ + return (size () - kpCommandSize::ImageSize (d->baseImage)); +} + +//--------------------------------------------------------------------- + +// public virtual [kpAbstractSelection] +int kpAbstractImageSelection::minimumWidth () const +{ + return 1; +} + +//--------------------------------------------------------------------- + +// public virtual [kpAbstractSelection] +int kpAbstractImageSelection::minimumHeight () const +{ + return 1; +} + +//--------------------------------------------------------------------- +// public virtual +QBitmap kpAbstractImageSelection::shapeBitmap (bool nullForRectangular) const +{ + (void) nullForRectangular; + + Q_ASSERT (boundingRect ().isValid ()); + + QBitmap maskBitmap (width (), height ()); + maskBitmap.fill (Qt::color0/*transparent*/); + + { + QPainter painter(&maskBitmap); + + painter.setPen (Qt::color1/*opaque*/); + painter.setBrush (Qt::color1/*opaque*/); + + QPolygon points = calculatePoints (); + points.translate (-x (), -y ()); + + // Unlike QPainter::drawRect(), this draws the points literally + // without being 1 pixel wider and higher. This requires a QPen + // or it will draw 1 pixel narrower and shorter. + painter.drawPolygon (points, Qt::OddEvenFill); + } + + return maskBitmap; +} + +//--------------------------------------------------------------------- + +// public +kpImage kpAbstractImageSelection::givenImageMaskedByShape (const kpImage &image) const +{ +#if DEBUG_KP_SELECTION + qCDebug(kpLogLayers) << "kpAbstractImageSelection::givenImageMaskedByShape() boundingRect=" + << boundingRect () << endl; +#endif + Q_ASSERT (image.width () == width () && image.height () == height ()); + + if (isRectangular ()) { + return image; + } + + const QRegion mRegion = shapeRegion ().translated (-topLeft ()); + +#if DEBUG_KP_SELECTION + qCDebug(kpLogLayers) << "\tshapeRegion=" << shapeRegion () + << " [rect=" << shapeRegion ().boundingRect () << "]" + << " calculatePoints=" << calculatePoints () + << " [rect=" << calculatePoints ().boundingRect () << "]" + << endl; +#endif + + kpImage retImage(width (), height (), QImage::Format_ARGB32_Premultiplied); + retImage.fill(0); // transparent + + QPainter painter(&retImage); + painter.setClipRegion(mRegion); + painter.drawImage(0, 0, image); + painter.end(); + + return retImage; +} + +//--------------------------------------------------------------------- + +// public virtual [kpAbstractSelection] +bool kpAbstractImageSelection::hasContent () const +{ + return !d->baseImage.isNull (); +} + +//--------------------------------------------------------------------- + +// public virtual [kpAbstractSelection] +void kpAbstractImageSelection::deleteContent () +{ + if (!hasContent ()) { + return; + } + + setBaseImage (kpImage ()); +} + +//--------------------------------------------------------------------- + + +// public +kpImage kpAbstractImageSelection::baseImage () const +{ + return d->baseImage; +} + +//--------------------------------------------------------------------- + +// public +void kpAbstractImageSelection::setBaseImage (const kpImage &baseImage) +{ + Q_ASSERT (::CanSetBaseImageTo (this, baseImage)); + + // qt doc: the image format must be set to Format_ARGB32Premultiplied or Format_ARGB32 + // for the composition modes to have any effect + d->baseImage = baseImage.convertToFormat(QImage::Format_ARGB32_Premultiplied); + + recalculateTransparencyMaskCache (); + + emit changed (boundingRect ()); +} + +//--------------------------------------------------------------------- + +// public +kpImageSelectionTransparency kpAbstractImageSelection::transparency () const +{ + return d->transparency; +} + +//--------------------------------------------------------------------- + +// public +bool kpAbstractImageSelection::setTransparency ( + const kpImageSelectionTransparency &transparency, + bool checkTransparentPixmapChanged) +{ + if (d->transparency == transparency) { + return false; + } + + d->transparency = transparency; + + bool haveChanged = true; + + QBitmap oldTransparencyMaskCache = d->transparencyMaskCache; + recalculateTransparencyMaskCache (); + + if ( oldTransparencyMaskCache.size() == d->transparencyMaskCache.size() ) + { + if (d->transparencyMaskCache.isNull ()) + { + #if DEBUG_KP_SELECTION + qCDebug(kpLogLayers) << "\tboth old and new pixmaps are null - nothing changed"; + #endif + haveChanged = false; + } + else if (checkTransparentPixmapChanged) + { + QImage oldTransparencyMaskImage = oldTransparencyMaskCache.toImage(); + QImage newTransparencyMaskImage = d->transparencyMaskCache.toImage(); + + bool changed = false; + for (int y = 0; y < oldTransparencyMaskImage.height () && !changed; y++) + { + for (int x = 0; x < oldTransparencyMaskImage.width () && !changed; x++) + { + if (kpPixmapFX::getColorAtPixel (oldTransparencyMaskImage, x, y) != + kpPixmapFX::getColorAtPixel (newTransparencyMaskImage, x, y)) + { + #if DEBUG_KP_SELECTION + qCDebug(kpLogLayers) << "\tdiffer at " << QPoint (x, y) + << " old=" << kpPixmapFX::getColorAtPixel (oldTransparencyMaskImage, x, y).toQRgb () + << " new=" << kpPixmapFX::getColorAtPixel (newTransparencyMaskImage, x, y).toQRgb () + << endl; + #endif + changed = true; + break; + } + } + } + + if (!changed) { + haveChanged = false; + } + } + } + + + if (haveChanged) { + emit changed (boundingRect ()); + } + + return haveChanged; +} + +//--------------------------------------------------------------------- + +// private +void kpAbstractImageSelection::recalculateTransparencyMaskCache () +{ +#if DEBUG_KP_SELECTION + qCDebug(kpLogLayers) << "kpAbstractImageSelection::recalculateTransparencyMaskCache()"; +#endif + + if (d->baseImage.isNull ()) + { + #if DEBUG_KP_SELECTION + qCDebug(kpLogLayers) << "\tno image - no need for transparency mask"; + #endif + d->transparencyMaskCache = QBitmap (); + return; + } + + if (d->transparency.isOpaque ()) + { + #if DEBUG_KP_SELECTION + qCDebug(kpLogLayers) << "\topaque - no need for transparency mask"; + #endif + d->transparencyMaskCache = QBitmap (); + return; + } + + d->transparencyMaskCache = QBitmap(d->baseImage.size()); + + QPainter transparencyMaskPainter (&d->transparencyMaskCache); + + bool hasTransparent = false; + for (int y = 0; y < d->baseImage.height (); y++) + { + for (int x = 0; x < d->baseImage.width (); x++) + { + const kpColor pixelCol = kpPixmapFX::getColorAtPixel (d->baseImage, x, y); + if (pixelCol == kpColor::Transparent || + pixelCol.isSimilarTo (d->transparency.transparentColor (), + d->transparency.processedColorSimilarity ())) + { + transparencyMaskPainter.setPen (Qt::color1/*transparent*/); + hasTransparent = true; + } + else + { + transparencyMaskPainter.setPen (Qt::color0/*opaque*/); + } + + transparencyMaskPainter.drawPoint (x, y); + } + } + + transparencyMaskPainter.end (); + + if (!hasTransparent) + { + #if DEBUG_KP_SELECTION + qCDebug(kpLogLayers) << "\tcolour useless - completely opaque"; + #endif + d->transparencyMaskCache = QBitmap (); + return; + } +} + +//--------------------------------------------------------------------- + +// public +kpImage kpAbstractImageSelection::transparentImage () const +{ + kpImage image = baseImage (); + + if (!d->transparencyMaskCache.isNull ()) + { + QPainter painter(&image); + painter.setCompositionMode(QPainter::CompositionMode_Clear); + painter.drawPixmap(0, 0, d->transparencyMaskCache); + } + + return image; +} + +//--------------------------------------------------------------------- + +// public +void kpAbstractImageSelection::fill (const kpColor &color) +{ + QImage newImage(width(), height(), QImage::Format_ARGB32_Premultiplied); + newImage.fill(color.toQRgb()); + + // LOTODO: Maybe disable Image/Clear menu item if transparent color + if ( !color.isTransparent() ) + { + QPainter painter(&newImage); + painter.setCompositionMode(QPainter::CompositionMode_Clear); + painter.drawPixmap(0, 0, shapeBitmap()); + } + + setBaseImage (newImage); +} + +//--------------------------------------------------------------------- + +// public virtual +void kpAbstractImageSelection::flip (bool horiz, bool vert) +{ +#if DEBUG_KP_SELECTION && 1 + qCDebug(kpLogLayers) << "kpAbstractImageSelection::flip(horiz=" << horiz + << ",vert=" << vert << ")"; +#endif + + if (!d->baseImage.isNull ()) + { + #if DEBUG_KP_SELECTION && 1 + qCDebug(kpLogLayers) << "\thave pixmap - flipping that"; + #endif + d->baseImage = d->baseImage.mirrored(horiz, vert); + } + + if (!d->transparencyMaskCache.isNull ()) + { + #if DEBUG_KP_SELECTION && 1 + qCDebug(kpLogLayers) << "\thave transparency mask - flipping that"; + #endif + QImage image = d->transparencyMaskCache.toImage().mirrored(horiz, vert); + d->transparencyMaskCache = QBitmap::fromImage(image); + } + + emit changed (boundingRect ()); +} + +//--------------------------------------------------------------------- + +static void Paint (const kpAbstractImageSelection *sel, const kpImage &srcImage, + QImage *destImage, const QRect &docRect) +{ + if (!srcImage.isNull ()) + { + kpPixmapFX::paintPixmapAt (destImage, + sel->topLeft () - docRect.topLeft (), + srcImage); + } +} + +//--------------------------------------------------------------------- + +// public virtual [kpAbstractSelection] +void kpAbstractImageSelection::paint (QImage *destImage, + const QRect &docRect) const +{ + ::Paint (this, transparentImage (), destImage, docRect); +} + +//--------------------------------------------------------------------- + +// public +void kpAbstractImageSelection::paintWithBaseImage (QImage *destImage, + const QRect &docRect) const +{ + ::Paint (this, baseImage (), destImage, docRect); +} + +//--------------------------------------------------------------------- + + diff --git a/layers/selections/image/kpAbstractImageSelection.h b/layers/selections/image/kpAbstractImageSelection.h new file mode 100644 index 0000000..de08597 --- /dev/null +++ b/layers/selections/image/kpAbstractImageSelection.h @@ -0,0 +1,266 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpAbstractImageSelection_H +#define kpAbstractImageSelection_H + + +#include "kpImageSelectionTransparency.h" +#include "layers/selections/kpAbstractSelection.h" +#include "imagelib/kpImage.h" + + +// +// An abstract selection with optional image content and background +// subtraction. If there is image content, it is known as a "floating +// selection" that hovers above the document. Otherwise, it is an +// image selection "border", that highlights pixels of the document, and +// may later be upgraded to a floating selection by giving it an image +// consisting of those pixels. +// +// The images passed to this class (known as "base images") should have all +// pixels, outside of the border, set to transparent. However, nothing +// enforces this. Pixels on, or inside, the border might be opaque or +// transparent, depending on the content. +// +// The "transparent image" is the base image with background subtraction +// (kpImageSelectionTransparency) applied. This is automatically computed. +// +// The boundingRect() is the size of the border. The base image must be of +// exactly the same size, except that the base image is allowed to be null +// (for a selection that only consists of a border). +// +// Instead of copying selections' images to the clipboard, we copy +// selections, to preserve the border across KolourPaint instances. +// Background subtraction is not copied to the clipboard so that the base +// image is affected by the background subtraction of the destination +// KolourPaint window. +// +class kpAbstractImageSelection : public kpAbstractSelection +{ +Q_OBJECT + +// +// Initialization +// + +protected: + // (Call these in subclass constructors) + kpAbstractImageSelection (const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + kpAbstractImageSelection (const QRect &rect, + const kpImage &baseImage = kpImage (), + const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + kpAbstractImageSelection (const QRect &rect, + const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + // (Call this in subclass implementations of operator=) + kpAbstractImageSelection &operator= (const kpAbstractImageSelection &rhs); + +public: + // (Covariant return-type specialization of superclass pure virtual method) + kpAbstractImageSelection *clone () const override = 0; + + ~kpAbstractImageSelection () override; + + +// +// Marshalling +// + +public: + // You must override this if you have extra serializable fields. + // Remember to call this base implementation before your code. + bool readFromStream (QDataStream &stream) override; + + // You must override this if you have extra serializable fields. + // Remember to call this base implementation before your code. + void writeToStream (QDataStream &stream) const override; + + +// +// General Queries +// + +public: + QString name () const override; + + // You must override this, if you have extra fields that take a + // non-constant amount of space, and add the size returned by this + // implementation. + kpCommandSize::SizeType size () const override; + + // Same as virtual size() (it even calls it) but subtracts the size of the + // baseImage(). + // + // kpCommand's store the kpImage's they are working on. These images may + // be from documents or selections. In the case of a selection, the + // selection's baseImage() is identical to that image, but takes no extra + // space due to kpImage's copy-on-write. This method fixes that + // double-counting of baseImage()'s size. + // + // The size of the internal transparency() mask is still included + // (see recalculateTransparencyMask()). + // + // sync: kpImage copy-on-write behavior + // + // TODO: Check all size() implementations are correct since we've + // started removing the old kpSelection::setPixmap(QPixmap()) + // (now kpAbstractImageSelection::setBaseImage(kpImage()) or + // kpAbstractImageSelection::deleteContent()) space saving hack. + kpCommandSize::SizeType sizeWithoutImage () const; + + +// +// Dimensions +// + +public: + int minimumWidth () const override; + int minimumHeight () const override; + + +// +// Shape Mask +// +// These methods do not access any class instance fields. +// + +public: + // Returns the mask corresponding to the shape of the selection. + // + // If is set, the method _may_ return a null + // bitmap if the selection is rectangular. + // + // This base implementation calls calculatePoints() and ignores + // . + // + // You should override this if you can implement it more efficiently or + // if you can honor . + // + // Note: This must be consistent with the outputs of calculatePoints() and + // shapeRegion(). + // + // TODO: Try to get rid of this method since it's slow. + virtual QBitmap shapeBitmap (bool nullForRectangular = false) const; + + // Returns the region corresponding to the shape of the selection + // e.g. elliptical region for an elliptical selection. + // + // Very slow. + // + // Note: This must be consistent with the outputs of calculatePoints() and + // shapeRegion(). + // + // OPT: QRegion is probably incredibly slow - cache + virtual QRegion shapeRegion () const = 0; + + // Returns the given with the pixels outside of the selection's + // shape set to transparent. + // + // Very slow. + // + // ASSUMPTION: The image has the same dimensions as the selection. + kpImage givenImageMaskedByShape (const kpImage &image) const; + + +// +// Content - Base Image +// + +public: + // Returns whether there's a non-null base image. + bool hasContent () const override; + + void deleteContent () override; + +public: + kpImage baseImage () const; + void setBaseImage (const kpImage &baseImage); + + +// +// Background Subtraction +// + +public: + kpImageSelectionTransparency transparency () const; + + // Returns whether or not the selection changed due to setting the + // transparency info. If is set, + // it will try harder to return false (although the check is + // expensive). + bool setTransparency (const kpImageSelectionTransparency &transparency, + bool checkTransparentPixmapChanged = false); + +private: + // Updates the selection transparency (a.k.a. background subtraction) mask + // so that transparentImage() will work. + // + // Called when the base image or selection transparency changes. + void recalculateTransparencyMaskCache (); + +public: + // Returns baseImage() after applying kpImageSelectionTransparency + kpImage transparentImage () const; + + +// +// Mutation - Effects +// + +public: + // Overwrites the base image with the selection's shape (e.g. ellipse) + // filled in with . See shapeBitmap(). + void fill (const kpColor &color); + + virtual void flip (bool horiz, bool vert); + + +// +// Rendering +// + +public: + // (using transparent image) + void paint (QImage *destPixmap, const QRect &docRect) const override; + + // (using base image) + void paintWithBaseImage (QImage *destPixmap, const QRect &docRect) const; + + +private: + struct kpAbstractImageSelectionPrivate * const d; +}; + + +#endif // kpAbstractImageSelection_H diff --git a/layers/selections/image/kpEllipticalImageSelection.cpp b/layers/selections/image/kpEllipticalImageSelection.cpp new file mode 100644 index 0000000..ec475d0 --- /dev/null +++ b/layers/selections/image/kpEllipticalImageSelection.cpp @@ -0,0 +1,185 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_SELECTION 0 + + +#include "layers/selections/image/kpEllipticalImageSelection.h" + +#include +#include +#include + + +struct kpEllipticalImageSelectionPrivate +{ +}; + + +kpEllipticalImageSelection::kpEllipticalImageSelection ( + const kpImageSelectionTransparency &transparency) + : kpAbstractImageSelection (transparency), + d (new kpEllipticalImageSelectionPrivate ()) +{ +} + +kpEllipticalImageSelection::kpEllipticalImageSelection (const QRect &rect, + const kpImage &baseImage, + const kpImageSelectionTransparency &transparency) + : kpAbstractImageSelection (rect, baseImage, transparency), + d (new kpEllipticalImageSelectionPrivate ()) +{ +} + +kpEllipticalImageSelection::kpEllipticalImageSelection (const QRect &rect, + const kpImageSelectionTransparency &transparency) + : kpAbstractImageSelection (rect, transparency), + d (new kpEllipticalImageSelectionPrivate ()) +{ +} + +kpEllipticalImageSelection::kpEllipticalImageSelection (const kpEllipticalImageSelection &rhs) + : kpAbstractImageSelection (), + d (new kpEllipticalImageSelectionPrivate ()) +{ + *this = rhs; +} + +kpEllipticalImageSelection &kpEllipticalImageSelection::operator= ( + const kpEllipticalImageSelection &rhs) +{ + kpAbstractImageSelection::operator= (rhs); + + return *this; +} + +kpEllipticalImageSelection *kpEllipticalImageSelection::clone () const +{ + kpEllipticalImageSelection *sel = new kpEllipticalImageSelection (); + *sel = *this; + return sel; +} + +kpEllipticalImageSelection::~kpEllipticalImageSelection () +{ + delete d; +} + +//--------------------------------------------------------------------- + +// public virtual [kpAbstractSelection] +int kpEllipticalImageSelection::serialID () const +{ + return SerialID; +} + +//--------------------------------------------------------------------- + +// public virtual [kpAbstractSelection] +bool kpEllipticalImageSelection::isRectangular () const +{ + return false; +} + +//--------------------------------------------------------------------- + +// public virtual [kpAbstractSelection] +QPolygon kpEllipticalImageSelection::calculatePoints () const +{ + Q_ASSERT (boundingRect ().isValid ()); + + if (width () == 1 && height () == 1) + { + QPolygon ret; + ret.append (topLeft ()); + return ret; + } + + QPainterPath path; + if (width () == 1 || height () == 1) + { + path.moveTo (x (), y ()); + // This does not work when the width _and_ height are 1 since lineTo() + // would not move at all. This is why we have a separate case for that + // at the top of the method. + path.lineTo (x () + width () - 1, y () + height () - 1); + } + else + { + // The adjusting is to fight QPainterPath::addEllipse() making + // the ellipse 1 pixel higher and wider than specified. + path.addEllipse (boundingRect ().adjusted (0, 0, -1, -1)); + } + + const QList polygons = path.toSubpathPolygons (); + Q_ASSERT (polygons.size () == 1); + + const QPolygonF& firstPolygonF = polygons.first (); + return firstPolygonF.toPolygon (); +} + +//--------------------------------------------------------------------- + + +// protected virtual [kpAbstractImageSelection] +QRegion kpEllipticalImageSelection::shapeRegion () const +{ + QRegion reg(calculatePoints()); + return reg; +} + +//--------------------------------------------------------------------- + + +// public virtual [kpAbstractSelection] +bool kpEllipticalImageSelection::contains (const QPoint &point) const +{ + if (!boundingRect ().contains (point)) { + return false; + } + + return shapeRegion ().contains (point); +} + +//--------------------------------------------------------------------- + + +// public virtual [kpAbstractSelection] +void kpEllipticalImageSelection::paintBorder (QImage *destPixmap, const QRect &docRect, + bool selectionFinished) const +{ + if ( !boundingRect().isValid() ) { + return; + } + + paintPolygonalBorder (calculatePoints (), + destPixmap, docRect, + selectionFinished); +} + + diff --git a/layers/selections/image/kpEllipticalImageSelection.h b/layers/selections/image/kpEllipticalImageSelection.h new file mode 100644 index 0000000..688141f --- /dev/null +++ b/layers/selections/image/kpEllipticalImageSelection.h @@ -0,0 +1,118 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpEllipticalImageSelection_H +#define kpEllipticalImageSelection_H + + +#include "layers/selections/image/kpAbstractImageSelection.h" + + +class kpEllipticalImageSelection : public kpAbstractImageSelection +{ +Q_OBJECT + +public: + kpEllipticalImageSelection (const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + kpEllipticalImageSelection (const QRect &rect, + const kpImage &baseImage = kpImage (), + const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + kpEllipticalImageSelection (const QRect &rect, + const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + kpEllipticalImageSelection (const kpEllipticalImageSelection &rhs); + + kpEllipticalImageSelection &operator= (const kpEllipticalImageSelection &rhs); + + kpEllipticalImageSelection *clone () const override; + + ~kpEllipticalImageSelection () override; + + +// +// Marshalling +// + +public: + static const int SerialID = 1; + int serialID () const override; + + +// +// General Queries +// + +public: + bool isRectangular () const override; + + +// +// Position & Dimensions +// + +public: + QPolygon calculatePoints () const override; + + +// +// Shape Mask +// + +public: + QRegion shapeRegion () const override; + + +// +// Point Testing +// + +public: + bool contains (const QPoint &point) const override; + + +// +// Rendering +// + +public: + void paintBorder (QImage *destPixmap, const QRect &docRect, + bool selectionFinished) const override; + + +private: + struct kpEllipticalImageSelectionPrivate * const d; +}; + + + +#endif // kpEllipticalImageSelection_H diff --git a/layers/selections/image/kpFreeFormImageSelection.cpp b/layers/selections/image/kpFreeFormImageSelection.cpp new file mode 100644 index 0000000..1268fe2 --- /dev/null +++ b/layers/selections/image/kpFreeFormImageSelection.cpp @@ -0,0 +1,394 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_SELECTION 0 + + +#include "layers/selections/image/kpFreeFormImageSelection.h" + +#include "kpLogCategories.h" + +#include "imagelib/kpPainter.h" + + +struct kpFreeFormImageSelectionPrivate +{ + QPolygon orgPoints; + + // Various Qt methods that take a QPolygon interpolate points differently + // (e.g. QPainter::drawPolygon() vs QRegion(QPolygon)) when given consecutive + // points that are not cardinally adjacent e.g. these 2 points: + // + // # + // # + // + // are diagonally, but not cardinally, adjacent. They are rendered + // inconsistently. Also, points which are not adjacent at all definitely + // require interpolation and are inconsistently rendered: + // + // # + // # + // + // So, we only pass cardinally interpolated points to those methods to + // avoid this issue: + // + // ## + // # + // + // These interpolated points are stored in . Regarding + // , see the APIDoc for cardinallyAdjacentPointsLoop(). + QPolygon cardPointsCache, cardPointsLoopCache; +}; + + +kpFreeFormImageSelection::kpFreeFormImageSelection ( + const kpImageSelectionTransparency &transparency) + : kpAbstractImageSelection (transparency), + d (new kpFreeFormImageSelectionPrivate ()) +{ +} + +kpFreeFormImageSelection::kpFreeFormImageSelection (const QPolygon &points, + const kpImage &baseImage, + const kpImageSelectionTransparency &transparency) + : kpAbstractImageSelection (points.boundingRect (), baseImage, transparency), + d (new kpFreeFormImageSelectionPrivate ()) +{ + d->orgPoints = points; + recalculateCardinallyAdjacentPoints (); +} + +kpFreeFormImageSelection::kpFreeFormImageSelection (const QPolygon &points, + const kpImageSelectionTransparency &transparency) + : kpAbstractImageSelection (points.boundingRect (), transparency), + d (new kpFreeFormImageSelectionPrivate ()) +{ + d->orgPoints = points; + recalculateCardinallyAdjacentPoints (); +} + +kpFreeFormImageSelection::kpFreeFormImageSelection (const kpFreeFormImageSelection &rhs) + : kpAbstractImageSelection (), + d (new kpFreeFormImageSelectionPrivate ()) +{ + *this = rhs; +} + +kpFreeFormImageSelection &kpFreeFormImageSelection::operator= (const kpFreeFormImageSelection &rhs) +{ + kpAbstractImageSelection::operator= (rhs); + + d->orgPoints = rhs.d->orgPoints; + d->cardPointsCache = rhs.d->cardPointsCache; + d->cardPointsLoopCache = rhs.d->cardPointsLoopCache; + + return *this; +} + +// public virtual [kpAbstractSelection] +kpFreeFormImageSelection *kpFreeFormImageSelection::clone () const +{ + kpFreeFormImageSelection *sel = new kpFreeFormImageSelection (); + *sel = *this; + return sel; +} + +kpFreeFormImageSelection::~kpFreeFormImageSelection () +{ + delete d; +} + + +// public virtual [kpAbstractSelection] +int kpFreeFormImageSelection::serialID () const +{ + return SerialID; +} + +// public virtual [base kpAbstractImageSelection] +bool kpFreeFormImageSelection::readFromStream (QDataStream &stream) +{ + if (!kpAbstractImageSelection::readFromStream (stream)) { + return false; + } + + stream >> d->orgPoints; + recalculateCardinallyAdjacentPoints (); + + return true; +} + +// public virtual [base kpAbstractImageSelection] +void kpFreeFormImageSelection::writeToStream (QDataStream &stream) const +{ + kpAbstractImageSelection::writeToStream (stream); + + stream << d->orgPoints; +} + + +// public virtual [base kpAbstractImageSelection] +kpCommandSize::SizeType kpFreeFormImageSelection::size () const +{ + return kpAbstractImageSelection::size () + + (kpCommandSize::PolygonSize (d->orgPoints) + + kpCommandSize::PolygonSize (d->cardPointsCache) + + kpCommandSize::PolygonSize (d->cardPointsLoopCache)); +} + +// public virtual [kpAbstractSelection] +bool kpFreeFormImageSelection::isRectangular () const +{ + return false; +} + +// public +QPolygon kpFreeFormImageSelection::originalPoints () const +{ + return d->orgPoints; +} + + +static QPolygon RecalculateCardinallyAdjacentPoints (const QPolygon &points) +{ +#if DEBUG_KP_SELECTION + qCDebug(kpLogLayers) << "kpFreeFormImageSelection.cpp:RecalculateCardinallyAdjacentPoints()"; + qCDebug(kpLogLayers) << "\tpoints=" << points; +#endif + + // Filter out duplicates. + QPolygon noDups; + for (const auto &p : points) + { + if (!noDups.isEmpty () && p == noDups.last ()) { + continue; + } + + noDups.append (p); + } +#if DEBUG_KP_SELECTION + qCDebug(kpLogLayers) << "\twithout dups=" << noDups; +#endif + + // Interpolate to ensure cardinal adjacency. + QPolygon cardPoints; + for (const auto &p : noDups) + { + if (!cardPoints.isEmpty () && + !kpPainter::pointsAreCardinallyAdjacent (p, cardPoints.last ())) + { + const QPoint lastPoint = cardPoints.last (); + + QList interpPoints = kpPainter::interpolatePoints ( + lastPoint, + p, + true/*cardinal adjacency*/); + + Q_ASSERT (interpPoints.size () >= 2); + Q_ASSERT (interpPoints [0] == lastPoint); + Q_ASSERT (interpPoints.last () == p); + + for (int i = 1/*skip already existing point*/; + i < interpPoints.size (); + i++) + { + cardPoints.append (interpPoints [i]); + } + } + else { + cardPoints.append (p); + } + } +#if DEBUG_KP_SELECTION + qCDebug(kpLogLayers) << "\tcardinally adjacent=" << cardPoints; +#endif + + return cardPoints; +} + +// protected +void kpFreeFormImageSelection::recalculateCardinallyAdjacentPoints () +{ + d->cardPointsCache = ::RecalculateCardinallyAdjacentPoints (d->orgPoints); + + + QPolygon pointsLoop = d->cardPointsCache; + if (!pointsLoop.isEmpty ()) { + pointsLoop.append (pointsLoop.first ()); + } + + // OPT: We know this method only needs to act on the last 2 points of + // "pointLoop", since the previous points are definitely cardinally + // adjacent. + d->cardPointsLoopCache = ::RecalculateCardinallyAdjacentPoints (pointsLoop); +} + +// public +QPolygon kpFreeFormImageSelection::cardinallyAdjacentPoints () const +{ + return d->cardPointsCache; +} + +// public +QPolygon kpFreeFormImageSelection::cardinallyAdjacentPointsLoop () const +{ + return d->cardPointsLoopCache; +} + + +// public virtual [kpAbstractSelection] +QPolygon kpFreeFormImageSelection::calculatePoints () const +{ + return d->cardPointsLoopCache; +} + + +// protected virtual [kpAbstractSelection] +QRegion kpFreeFormImageSelection::shapeRegion () const +{ + const QRegion region = QRegion (d->cardPointsLoopCache, Qt::OddEvenFill); + + // In Qt4, while QPainter::drawRect() gives you rectangles 1 pixel + // wider and higher, QRegion(QPolygon) gives you regions 1 pixel + // narrower and shorter! Compensate for this by merging shifted + // versions of the region. This seems to be consistent with shapeBitmap() + // but I am a bit worried. + // + // Regarding alternative solutions: + // 1. Instead of doing this region shifting and merging, if we were to + // construct a QRegion simply from a point array with 4 points for + // every point in "d->cardPointsLoopCache" (4 points = original point + 3 + // translations below), it probably wouldn't work because the order of + // the points in any point array matter for the odd-even fill + // algorithm. This would probably manifest as problems with + // self-intersecting borders. + // 2. Constructing a QRegion from QBitmap (from shapeBitmap()) is probably + // very slow since it would have to read each pixel of the QBitmap. + // Having said that, this is probably the safest option as region shifting + // is dodgy. Also, this would guarantee that shapeBitmap() and shapeRegion() + // are consistent and we wouldn't need cardinally adjacent points either + // (d->cardPointsCache and d->cardPointsLoopCache). + const QRegion regionX = region.translated (1, 0); + const QRegion regionY = region.translated (0, 1); + const QRegion regionXY = region.translated (1, 1); + + return region.united (regionX).united (regionY).united (regionXY); +} + + +// public virtual [kpAbstractSelection] +bool kpFreeFormImageSelection::contains (const QPoint &point) const +{ + if (!boundingRect ().contains (point)) { + return false; + } + + // We can't use the baseImage() (when non-null) and get the transparency of + // the pixel at , instead of this region test, as the pixel may be + // transparent but still within the border. + return shapeRegion ().contains (point); +} + + +// public virtual [base kpAbstractSelection] +void kpFreeFormImageSelection::moveBy (int dx, int dy) +{ + d->orgPoints.translate (dx, dy); + + d->cardPointsCache.translate (dx, dy); + d->cardPointsLoopCache.translate (dx, dy); + + // Call base last since it fires the changed() signal and we only + // want that to fire at the very end of this method, after all + // the selection state has been changed. + kpAbstractImageSelection::moveBy (dx, dy); +} + +//--------------------------------------------------------------------- + +static void FlipPoints (QPolygon *points, + bool horiz, bool vert, + const QRect &oldRect) +{ + points->translate (-oldRect.x (), -oldRect.y ()); + + const QTransform matrix (horiz ? -1 : +1, // m11 + 0, // m12 + 0, // m21 + vert ? -1 : +1, // m22 + horiz ? (oldRect.width() - 1) : 0, // dx + vert ? (oldRect.height() - 1) : 0); // dy + +#if !defined (QT_NO_DEBUG) && !defined (NDEBUG) + QPolygon oldPoints = *points; +#endif + + *points = matrix.map (*points); + +#if !defined (QT_NO_DEBUG) && !defined (NDEBUG) + // Sanity check: flipping the points twice gives us the original points. + Q_ASSERT (oldPoints == matrix.map (*points)); +#endif + + points->translate (oldRect.x (), oldRect.y ()); +} + +//--------------------------------------------------------------------- + +// public virtual [base kpAbstractImageSelection] +void kpFreeFormImageSelection::flip (bool horiz, bool vert) +{ + ::FlipPoints (&d->orgPoints, horiz, vert, boundingRect ()); + + ::FlipPoints (&d->cardPointsCache, horiz, vert, boundingRect ()); + ::FlipPoints (&d->cardPointsLoopCache, horiz, vert, boundingRect ()); + + + // Call base last since it fires the changed() signal and we only + // want that to fire at the very end of this method, after all + // the selection state has been changed. + kpAbstractImageSelection::flip (horiz, vert); +} + +//--------------------------------------------------------------------- + +// public virtual [kpAbstractSelection] +void kpFreeFormImageSelection::paintBorder (QImage *destPixmap, const QRect &docRect, + bool selectionFinished) const +{ + if (selectionFinished) { + paintPolygonalBorder (cardinallyAdjacentPointsLoop (), + destPixmap, docRect, selectionFinished); + } + else { + paintPolygonalBorder (cardinallyAdjacentPoints (), + destPixmap, docRect, selectionFinished); + } +} + + diff --git a/layers/selections/image/kpFreeFormImageSelection.h b/layers/selections/image/kpFreeFormImageSelection.h new file mode 100644 index 0000000..05c2f2f --- /dev/null +++ b/layers/selections/image/kpFreeFormImageSelection.h @@ -0,0 +1,159 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpFreeFormImageSelection_H +#define kpFreeFormImageSelection_H + + +#include "layers/selections/image/kpAbstractImageSelection.h" + + +class kpFreeFormImageSelection : public kpAbstractImageSelection +{ +Q_OBJECT + +public: + kpFreeFormImageSelection (const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + kpFreeFormImageSelection (const QPolygon &points, + const kpImage &baseImage = kpImage (), + const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + kpFreeFormImageSelection (const QPolygon &points, + const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + kpFreeFormImageSelection (const kpFreeFormImageSelection &rhs); + + kpFreeFormImageSelection &operator= (const kpFreeFormImageSelection &rhs); + + kpFreeFormImageSelection *clone () const override; + + ~kpFreeFormImageSelection () override; + + +// +// Marshalling +// + +public: + static const int SerialID = 2; + int serialID () const override; + + bool readFromStream (QDataStream &stream) override; + + void writeToStream (QDataStream &stream) const override; + + +// +// General Queries +// + +public: + kpCommandSize::SizeType size () const override; + + bool isRectangular () const override; + + // (as passed to the constructor) + QPolygon originalPoints () const; + + +// +// Cardinally Adjacent Points +// + +protected: + void recalculateCardinallyAdjacentPoints (); + +public: + // Returns the originalPoints() interpolated to be cardinally adjacent. + QPolygon cardinallyAdjacentPoints () const; + + // Returns cardinallyAdjacentPoints() but with extra points interpolated + // from the last point to the first point (the original points are + // thought of as a polygon where the first and last points are connected, + // rather than as a string of points). + // + // As used by the shape mask methods. + QPolygon cardinallyAdjacentPointsLoop () const; + + +// +// Position & Dimensions +// + +public: + // Implements kpAbstractSelection interface - same as + // cardinallyAdjacentPointsLoop (). + // This implementation is fast. + QPolygon calculatePoints () const override; + + +// +// Shape Mask +// + +public: + QRegion shapeRegion () const override; + + +// +// Point Testing +// + +public: + bool contains (const QPoint &point) const override; + + +// +// Mutation +// + +public: + void moveBy (int dx, int dy) override; + + void flip (bool horiz, bool vert) override; + + +// +// Rendering +// + +public: + void paintBorder (QImage *destPixmap, const QRect &docRect, + bool selectionFinished) const override; + + +private: + struct kpFreeFormImageSelectionPrivate * const d; +}; + + +#endif // kpFreeFormImageSelection_H diff --git a/layers/selections/image/kpImageSelectionTransparency.cpp b/layers/selections/image/kpImageSelectionTransparency.cpp new file mode 100644 index 0000000..4a7f835 --- /dev/null +++ b/layers/selections/image/kpImageSelectionTransparency.cpp @@ -0,0 +1,205 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_SELECTION_TRANSPARENCY 0 + + +#include "layers/selections/image/kpImageSelectionTransparency.h" + +#include "kpLogCategories.h" + +#include "widgets/colorSimilarity/kpColorSimilarityHolder.h" + + +//--------------------------------------------------------------------- + +kpImageSelectionTransparency::kpImageSelectionTransparency () + : m_isOpaque (true) +{ + setColorSimilarity (0); +} + +//--------------------------------------------------------------------- + +kpImageSelectionTransparency::kpImageSelectionTransparency (const kpColor &transparentColor, double colorSimilarity) + : m_isOpaque (false), + m_transparentColor (transparentColor) +{ + setColorSimilarity (colorSimilarity); +} + +//--------------------------------------------------------------------- + +kpImageSelectionTransparency::kpImageSelectionTransparency (bool isOpaque, const kpColor &transparentColor, + double colorSimilarity) + : m_isOpaque (isOpaque), + m_transparentColor (transparentColor) +{ + setColorSimilarity (colorSimilarity); +} + +//--------------------------------------------------------------------- + +bool kpImageSelectionTransparency::operator== (const kpImageSelectionTransparency &rhs) const +{ +#if DEBUG_KP_SELECTION_TRANSPARENCY && 0 + qCDebug(kpLogLayers) << "kpImageSelectionTransparency::operator==()"; +#endif + + if (m_isOpaque != rhs.m_isOpaque) + { + #if DEBUG_KP_SELECTION_TRANSPARENCY && 0 + qCDebug(kpLogLayers) << "\tdifferent opacity: lhs=" << m_isOpaque + << " rhs=" << rhs.m_isOpaque + << endl; + #endif + return false; + } + + if (m_isOpaque) + { + #if DEBUG_KP_SELECTION_TRANSPARENCY && 0 + qCDebug(kpLogLayers) << "\tboth opaque - eq"; + #endif + return true; + } + +#if DEBUG_KP_SELECTION_TRANSPARENCY && 0 + qCDebug(kpLogLayers) << "\tcolours: lhs=" << (int *) m_transparentColor.toQRgb () + << " rhs=" << (int *) rhs.m_transparentColor.toQRgb () + << endl; + qCDebug(kpLogLayers) << "\tcolour similarity: lhs=" << m_colorSimilarity + << " rhs=" << rhs.m_colorSimilarity + << endl; +#endif + + return (m_transparentColor == rhs.m_transparentColor && + m_colorSimilarity == rhs.m_colorSimilarity); +} + +//--------------------------------------------------------------------- + +bool kpImageSelectionTransparency::operator!= (const kpImageSelectionTransparency &rhs) const +{ + return !(*this == rhs); +} + +//--------------------------------------------------------------------- + +kpImageSelectionTransparency::~kpImageSelectionTransparency () = default; + +//--------------------------------------------------------------------- + +// public +bool kpImageSelectionTransparency::isOpaque () const +{ + return m_isOpaque; +} + +//--------------------------------------------------------------------- + +// public +bool kpImageSelectionTransparency::isTransparent () const +{ + return !isOpaque (); +} + +//--------------------------------------------------------------------- + +// public +void kpImageSelectionTransparency::setOpaque (bool yes) +{ + m_isOpaque = yes; +} + +//--------------------------------------------------------------------- + +// public +void kpImageSelectionTransparency::setTransparent (bool yes) +{ + setOpaque (!yes); +} + +//--------------------------------------------------------------------- + + +// public +kpColor kpImageSelectionTransparency::transparentColor () const +{ + if (m_isOpaque) + { + // There are legitimate uses for this so no qCCritical(kpLogLayers) + qCDebug(kpLogLayers) << "kpImageSelectionTransparency::transparentColor() " + "getting transparent color even though opaque"; + } + + return m_transparentColor; +} + +//--------------------------------------------------------------------- + +// public +void kpImageSelectionTransparency::setTransparentColor (const kpColor &transparentColor) +{ + m_transparentColor = transparentColor; +} + +//--------------------------------------------------------------------- + + +// public +double kpImageSelectionTransparency::colorSimilarity () const +{ + if (m_colorSimilarity < 0 || + m_colorSimilarity > kpColorSimilarityHolder::MaxColorSimilarity) + { + qCCritical(kpLogLayers) << "kpImageSelectionTransparency::colorSimilarity() invalid colorSimilarity"; + return 0; + } + + return m_colorSimilarity; +} + +//--------------------------------------------------------------------- + +// pubulic +void kpImageSelectionTransparency::setColorSimilarity (double colorSimilarity) +{ + m_colorSimilarity = colorSimilarity; + m_processedColorSimilarity = kpColor::processSimilarity (colorSimilarity); +} + +//--------------------------------------------------------------------- + +// public +int kpImageSelectionTransparency::processedColorSimilarity () const +{ + return m_processedColorSimilarity; +} + +//--------------------------------------------------------------------- diff --git a/layers/selections/image/kpImageSelectionTransparency.h b/layers/selections/image/kpImageSelectionTransparency.h new file mode 100644 index 0000000..69ffc1f --- /dev/null +++ b/layers/selections/image/kpImageSelectionTransparency.h @@ -0,0 +1,82 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_IMAGE_SELECTION_TRANSPARENCY_H +#define KP_IMAGE_SELECTION_TRANSPARENCY_H + + +#include "imagelib/kpColor.h" + + +// This does not apply to the Text Tool. Use kpTextStyle for that. +class kpImageSelectionTransparency +{ +public: + // Opaque selection + kpImageSelectionTransparency (); + // Selection that's transparent at pixels with + kpImageSelectionTransparency (const kpColor &transparentColor, double colorSimilarity); + // If , is allowed to be anything + // (including invalid) as the color would have no effect. + // However, you are encouraged to set it as you would if !, + // because setTransparent(true) might be called later, after which + // the would suddenly become important. + // + // It is a similar case with , although + // must be in-range (see kpColorSimilarityHolder). + kpImageSelectionTransparency (bool isOpaque, const kpColor &transparentColor, double colorSimilarity); + // Returns whether they are visually equivalent. + // This is the same as a memcmp() except that if they are both opaque, + // this function will return true regardless of the transparentColor's. + bool operator== (const kpImageSelectionTransparency &rhs) const; + bool operator!= (const kpImageSelectionTransparency &rhs) const; + ~kpImageSelectionTransparency (); + + bool isOpaque () const; + bool isTransparent () const; + void setOpaque (bool yes = true); + void setTransparent (bool yes = true); + + // If isOpaque(), transparentColor() is generally not called because + // the transparent color would have no effect. + kpColor transparentColor () const; + void setTransparentColor (const kpColor &transparentColor); + + double colorSimilarity () const; + void setColorSimilarity (double colorSimilarity); + int processedColorSimilarity () const; + +private: + bool m_isOpaque; + kpColor m_transparentColor; + double m_colorSimilarity; + int m_processedColorSimilarity; +}; + + +#endif // KP_IMAGE_SELECTION_TRANSPARENCY_H diff --git a/layers/selections/image/kpRectangularImageSelection.cpp b/layers/selections/image/kpRectangularImageSelection.cpp new file mode 100644 index 0000000..e219c8e --- /dev/null +++ b/layers/selections/image/kpRectangularImageSelection.cpp @@ -0,0 +1,149 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_SELECTION 0 + + +#include "layers/selections/image/kpRectangularImageSelection.h" + +#include +#include + + +struct kpRectangularImageSelectionPrivate +{ +}; + + +kpRectangularImageSelection::kpRectangularImageSelection ( + const kpImageSelectionTransparency &transparency) + : kpAbstractImageSelection (transparency), + d (new kpRectangularImageSelectionPrivate ()) +{ +} + +kpRectangularImageSelection::kpRectangularImageSelection (const QRect &rect, + const kpImage &baseImage, + const kpImageSelectionTransparency &transparency) + : kpAbstractImageSelection (rect, baseImage, transparency), + d (new kpRectangularImageSelectionPrivate ()) +{ +} + +kpRectangularImageSelection::kpRectangularImageSelection (const QRect &rect, + const kpImageSelectionTransparency &transparency) + : kpAbstractImageSelection (rect, transparency), + d (new kpRectangularImageSelectionPrivate ()) +{ +} + +kpRectangularImageSelection::kpRectangularImageSelection (const kpRectangularImageSelection &rhs) + : kpAbstractImageSelection (), + d (new kpRectangularImageSelectionPrivate ()) +{ + *this = rhs; +} + +kpRectangularImageSelection &kpRectangularImageSelection::operator= ( + const kpRectangularImageSelection &rhs) +{ + kpAbstractImageSelection::operator= (rhs); + + return *this; +} + +kpRectangularImageSelection *kpRectangularImageSelection::clone () const +{ + kpRectangularImageSelection *sel = new kpRectangularImageSelection (); + *sel = *this; + return sel; +} + +kpRectangularImageSelection::~kpRectangularImageSelection () +{ + delete d; +} + + +// public virtual [kpAbstractSelection] +int kpRectangularImageSelection::serialID () const +{ + return SerialID; +} + + +// public virtual [kpAbstractSelection] +bool kpRectangularImageSelection::isRectangular () const +{ + return true; +} + + +// public virtual [kpAbstractSelection] +QPolygon kpRectangularImageSelection::calculatePoints () const +{ + return kpAbstractImageSelection::CalculatePointsForRectangle (boundingRect ()); +} + + +// public virtual [base kpAbstractImageSelection] +QBitmap kpRectangularImageSelection::shapeBitmap (bool nullForRectangular) const +{ + Q_ASSERT (boundingRect ().isValid ()); + + if (nullForRectangular) { + return {}; + } + + QBitmap maskBitmap (width (), height ()); + maskBitmap.fill (Qt::color1/*opaque*/); + return maskBitmap; +} + +// public virtual [kpAbstractImageSelection] +QRegion kpRectangularImageSelection::shapeRegion () const +{ + return QRegion (boundingRect (), QRegion::Rectangle); +} + + +// public virtual [kpAbstractSelection] +bool kpRectangularImageSelection::contains (const QPoint &point) const +{ + return boundingRect ().contains (point); +} + + +// public virtual [kpAbstractSelection] +void kpRectangularImageSelection::paintBorder (QImage *destPixmap, const QRect &docRect, + bool selectionFinished) const +{ + paintRectangularBorder (destPixmap, docRect, selectionFinished); +} + + diff --git a/layers/selections/image/kpRectangularImageSelection.h b/layers/selections/image/kpRectangularImageSelection.h new file mode 100644 index 0000000..836e30b --- /dev/null +++ b/layers/selections/image/kpRectangularImageSelection.h @@ -0,0 +1,119 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpRectangularImageSelection_H +#define kpRectangularImageSelection_H + + +#include "layers/selections/image/kpAbstractImageSelection.h" + + +class kpRectangularImageSelection : public kpAbstractImageSelection +{ +Q_OBJECT + +public: + kpRectangularImageSelection (const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + kpRectangularImageSelection (const QRect &rect, + const kpImage &baseImage = kpImage (), + const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + kpRectangularImageSelection (const QRect &rect, + const kpImageSelectionTransparency &transparency = + kpImageSelectionTransparency ()); + + kpRectangularImageSelection (const kpRectangularImageSelection &rhs); + + kpRectangularImageSelection &operator= (const kpRectangularImageSelection &rhs); + + kpRectangularImageSelection *clone () const override; + + ~kpRectangularImageSelection () override; + + +// +// Marshalling +// + +public: + static const int SerialID = 0; + int serialID () const override; + + +// +// General Queries +// + +public: + bool isRectangular () const override; + + +// +// Position & Dimensions +// + +public: + QPolygon calculatePoints () const override; + + +// +// Shape Mask +// + +public: + QBitmap shapeBitmap (bool nullForRectangular = false) const override; + + QRegion shapeRegion () const override; + + +// +// Point Testing +// + +public: + bool contains (const QPoint &point) const override; + + +// +// Rendering +// + +public: + void paintBorder (QImage *destPixmap, const QRect &docRect, + bool selectionFinished) const override; + + +private: + struct kpRectangularImageSelectionPrivate * const d; +}; + + +#endif // kpRectangularImageSelection_H diff --git a/layers/selections/kpAbstractSelection.cpp b/layers/selections/kpAbstractSelection.cpp new file mode 100644 index 0000000..f8f09b0 --- /dev/null +++ b/layers/selections/kpAbstractSelection.cpp @@ -0,0 +1,315 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_SELECTION 0 + + +#include "layers/selections/kpAbstractSelection.h" + +#include +#include + +#include "kpLogCategories.h" + +struct kpAbstractSelectionPrivate +{ + QRect rect; +}; + + +// protected +kpAbstractSelection::kpAbstractSelection () + : QObject (), + d (new kpAbstractSelectionPrivate ()) +{ + d->rect = QRect (); +} + +// protected +kpAbstractSelection::kpAbstractSelection (const QRect &rect) + : QObject (), + d (new kpAbstractSelectionPrivate ()) +{ + d->rect = rect; +} + +// protected +kpAbstractSelection &kpAbstractSelection::operator= (const kpAbstractSelection &rhs) +{ + if (this == &rhs) { + return *this; + } + + d->rect = rhs.d->rect; + + return *this; +} + +// protected +kpAbstractSelection::~kpAbstractSelection () +{ + delete d; +} + + +// public virtual +bool kpAbstractSelection::readFromStream (QDataStream &stream) +{ + stream >> d->rect; + + return true; +} + +// public virtual +void kpAbstractSelection::writeToStream (QDataStream &stream) const +{ + stream << d->rect; +} + +// friend +QDataStream &operator<< (QDataStream &stream, const kpAbstractSelection &selection) +{ +#if DEBUG_KP_SELECTION && 1 + qCDebug(kpLogLayers) << "kpAbstractSelection::operator<<(sel: rect=" << + selection.boundingRect (); +#endif + stream << selection.serialID (); + selection.writeToStream (stream); + return stream; +} + + +// public virtual +kpCommandSize::SizeType kpAbstractSelection::size () const +{ + return 0/*constant size*/; +} + + +// public +QSize kpAbstractSelection::minimumSize () const +{ + return {minimumWidth (), minimumHeight ()}; +} + + +// public +int kpAbstractSelection::x () const +{ + return d->rect.x (); +} + +// public +int kpAbstractSelection::y () const +{ + return d->rect.y (); +} + +// public +QPoint kpAbstractSelection::topLeft () const +{ + return d->rect.topLeft (); +} + + +// public +int kpAbstractSelection::width () const +{ + return boundingRect ().width (); +} + +// public +int kpAbstractSelection::height () const +{ + return boundingRect ().height (); +} + +// public +QRect kpAbstractSelection::boundingRect () const +{ + return d->rect; +} + +// public static +QPolygon kpAbstractSelection::CalculatePointsForRectangle (const QRect &rect) +{ + QPolygon points; + + // OPT: not space optimal - current code adds duplicate corner points. + + // top + for (int x = 0; x < rect.width (); x++) { + points.append (QPoint (rect.x () + x, rect.top ())); + } + + // right + for (int y = 0; y < rect.height (); y++) { + points.append (QPoint (rect.right (), rect.y () + y)); + } + + // bottom + for (int x = rect.width () - 1; x >= 0; x--) { + points.append (QPoint (rect.x () + x, rect.bottom ())); + } + + // left + for (int y = rect.height () - 1; y >= 0; y--) { + points.append (QPoint (rect.left (), rect.y () + y)); + } + + return points; +} + + +// public +bool kpAbstractSelection::contains (int x, int y) const +{ + return contains (QPoint (x, y)); +} + + +// public virtual +void kpAbstractSelection::moveBy (int dx, int dy) +{ +#if DEBUG_KP_SELECTION && 1 + qCDebug(kpLogLayers) << "kpAbstractSelection::moveBy(" << dx << "," << dy << ")"; +#endif + + if (dx == 0 && dy == 0) { + return; + } + + QRect oldRect = boundingRect (); + +#if DEBUG_KP_SELECTION && 1 + qCDebug(kpLogLayers) << "\toldRect=" << oldRect; +#endif + + d->rect.translate (dx, dy); +#if DEBUG_KP_SELECTION && 1 + qCDebug(kpLogLayers) << "\tnewRect=" << d->rect; +#endif + + emit changed (oldRect); + emit changed (boundingRect ()); +} + +// public +void kpAbstractSelection::moveTo (int dx, int dy) +{ + moveTo (QPoint (dx, dy)); +} + +// public +void kpAbstractSelection::moveTo (const QPoint &topLeftPoint) +{ +#if DEBUG_KP_SELECTION && 1 + qCDebug(kpLogLayers) << "kpAbstractSelection::moveTo(" << topLeftPoint << ")"; +#endif + QRect oldBoundingRect = boundingRect (); +#if DEBUG_KP_SELECTION && 1 + qCDebug(kpLogLayers) << "\toldBoundingRect=" << oldBoundingRect; +#endif + if (topLeftPoint == oldBoundingRect.topLeft ()) { + return; + } + + QPoint delta (topLeftPoint - oldBoundingRect.topLeft ()); + moveBy (delta.x (), delta.y ()); +} + +//--------------------------------------------------------------------- + +// protected +void kpAbstractSelection::paintRectangularBorder (QImage *destPixmap, + const QRect &docRect, + bool selectionFinished) const +{ + (void) selectionFinished; + +#if DEBUG_KP_SELECTION && 1 + qCDebug(kpLogLayers) << "kpAbstractSelection::paintRectangularBorder() boundingRect=" + << boundingRect (); +#endif + +#if DEBUG_KP_SELECTION && 1 + qCDebug(kpLogLayers) << "\tselection border = rectangle"; + qCDebug(kpLogLayers) << "\t\tx=" << boundingRect ().x () - docRect.x () + << " y=" << boundingRect ().y () - docRect.y () + << " w=" << boundingRect ().width () + << " h=" << boundingRect ().height (); +#endif + kpPixmapFX::drawStippleRect(destPixmap, + boundingRect ().x () - docRect.x (), + boundingRect ().y () - docRect.y (), + boundingRect ().width (), + boundingRect ().height (), + kpColor::Blue, + kpColor::Yellow); +} + +//--------------------------------------------------------------------- + +// protected +void kpAbstractSelection::paintPolygonalBorder (const QPolygon &points, + QImage *destPixmap, + const QRect &docRect, + bool selectionFinished) const +{ +#if DEBUG_KP_SELECTION && 1 + qCDebug(kpLogLayers) << "kpAbstractSelection::paintPolygonalBorder() boundingRect=" + << boundingRect (); +#endif + + QPolygon pointsTranslated = points; + pointsTranslated.translate (-docRect.x (), -docRect.y ()); + + if ( !selectionFinished ) + { + kpPixmapFX::drawPolyline(destPixmap, + pointsTranslated, kpColor::Blue, 1/*pen width*/, kpColor::Yellow); + } + else + { + kpPixmapFX::drawPolygon(destPixmap, + pointsTranslated, + kpColor::Blue, 1/*pen width*/, + kpColor::Invalid/*no background*/, + true/*is final*/, + kpColor::Yellow); + + kpPixmapFX::drawStippleRect(destPixmap, + boundingRect ().x () - docRect.x (), + boundingRect ().y () - docRect.y (), + boundingRect ().width (), + boundingRect ().height (), + kpColor::LightGray, + kpColor::DarkGray); + } +} + diff --git a/layers/selections/kpAbstractSelection.h b/layers/selections/kpAbstractSelection.h new file mode 100644 index 0000000..adbee89 --- /dev/null +++ b/layers/selections/kpAbstractSelection.h @@ -0,0 +1,278 @@ + +// OPT: The selection classes should use copy-on-write. + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KPABSTRACTSELECTION_H +#define KPABSTRACTSELECTION_H + + +#include + +#include "commands/kpCommandSize.h" +#include "pixmapfx/kpPixmapFX.h" + + +class QImage; +class QPolygon; +class QPoint; +class QRect; +class QSize; + + +// +// Abstract base class for selections. +// +// Selections consist of: +// +// 1. Bounding rectangle (provided by this base class) relative to the +// source document +// 2. Border (must be on, or inside, the bounding rectangle and does not +// have to be rectangular) +// 3. Optional content (e.g. image or text). +// +// A selection without content is a selection border. +// +// Any content outside the border should not be rendered i.e. the selection +// is transparent in all areas outside of the border. Pixels on, or inside, +// the border are considered to be renderable content. Parts, or all, of +// this content can be transparent. +// +class kpAbstractSelection : public QObject +{ +Q_OBJECT + +// +// Initialization +// + +protected: + // (Call these in subclass constructors) + kpAbstractSelection (); + kpAbstractSelection (const QRect &rect); + + // (Call this in subclass implementations of operator=) + kpAbstractSelection &operator= (const kpAbstractSelection &rhs); + +public: + // To implement, create an instance of your type and then call your + // implementation of operator=(). + virtual kpAbstractSelection *clone () const = 0; + + ~kpAbstractSelection () override; + + +// +// Marshalling +// + +public: + // Returns a unique ID for this type of selection, use for marshalling. + virtual int serialID () const = 0; + + // This is called after your object has been created with the default + // constructor. + // + // Reads the object marshalled in the and returns whether it + // succeeded. This is called by kpSelectionFactory so the serialID() + // has already been read and removed from the . + // + // You must override this. Remember to call this base implementation + // before your code. + virtual bool readFromStream (QDataStream &stream); + + // Marshalls the object into the . This is called by + // operator<<() so the serialID() has already been written into the + // . + // + // You must override this. Remember to call this base implementation + // before your code. + virtual void writeToStream (QDataStream &stream) const; + + // Writes the serialID() of the to the and then + // calls writeToStream() to do the remaining marshalling. + // + // (kpSelectionFactory::FromStream() is the ">>" replacement) + friend QDataStream &operator<< (QDataStream &stream, + const kpAbstractSelection &selection); + + +// +// General Queries +// + +public: + // Returns e.g. i18n ("Selection") or i18n ("Text"). + virtual QString name () const = 0; + + // Returns the memory usage of the selection (like kpCommand's), + // _not_ its dimensions. + // + // You must override this and add the size returned by this implementation. + virtual kpCommandSize::SizeType size () const; + +public: + // e.g. return false for an elliptical selection. + virtual bool isRectangular () const = 0; + + +// +// Position & Dimensions +// + +public: + // Returns the minimum allowed dimensions of your selection type. + // Usually this is 1x1 pixels by pixels. + virtual int minimumWidth () const = 0; + virtual int minimumHeight () const = 0; + QSize minimumSize () const; + +public: + // (in document coordinates) + int x () const; + int y () const; + QPoint topLeft () const; + +public: + // (in document coordinates) + + // Returns the width of the bounding rectangle. + int width () const; + + // Returns the height of the bounding rectangle. + int height () const; + + // Returns the bounding rectangle. + QRect boundingRect () const; + +public: + // Use this to implement calculatePoints() for rectangular selections. + static QPolygon CalculatePointsForRectangle (const QRect &rect); + + // Returns the border. This may be recalculated for every call so + // may be slow. + virtual QPolygon calculatePoints () const = 0; + + +// +// Point Testing +// + +public: + // Returns whether the given is on or inside the -- possibly, + // non-rectangular -- border of the selection. + // + // (for non-rectangular selections, may return false even if + // kpView::onSelectionResizeHandle()) + virtual bool contains (const QPoint &point) const = 0; + bool contains (int x, int y) const; + + +// +// Content +// + +public: + // i.e. Has an image or text - not just a border. + virtual bool hasContent () const = 0; + + // Deletes the content, changing the selection back into a border. + // If the selection has no content, it does nothing. + virtual void deleteContent () = 0; + + +// +// Mutation - Movement +// + +public: + // (You only need to override this if you store your own border + // coordinates) + virtual void moveBy (int dx, int dy); + + // (These call moveBy() so if you only reimplement moveBy(), that should + // be sufficient) + void moveTo (int dx, int dy); + void moveTo (const QPoint &topLeftPoint); + + +// +// Rendering +// + +public: + // Renders the selection on top of <*destPixmap>. This does not render + // the border. + // + // is the document rectangle that <*destPixmap> represents. + // + // You need to clip to boundingRect() or if you are a non-rectangular + // selection, an even smaller region, + // + // However, there is no need to do any explicit clipping to , + // since any drawing outside the bounds of is discarded. + // However, you may choose to clip for whatever reason e.g. performance. + virtual void paint (QImage *destPixmap, const QRect &docRect) const = 0; + + +protected: + // Use this to implement paintBorder() for rectangular selections. + void paintRectangularBorder (QImage *destPixmap, const QRect &docRect, + bool selectionFinished) const; + + // Use this to implement paintBorder() for non-rectangular selections + // (this calls calculatePoints()). + // + // If , this also draws a bounding rectangular box. + void paintPolygonalBorder (const QPolygon &points, + QImage *destPixmap, const QRect &docRect, + bool selectionFinished) const; + +public: + // Renders the selection border on top of <*destPixmap>. + // + // is the same as for paint(). + // + // If is false, the user is still dragging out the + // selection border so it may be drawn differently. + virtual void paintBorder (QImage *destPixmap, const QRect &docRect, + bool selectionFinished) const = 0; + + +signals: + // Signals that a view update is required in the document region , + // due to the selection changing. + void changed (const QRect &docRect); + + +private: + struct kpAbstractSelectionPrivate * const d; +}; + + +#endif // KPABSTRACTSELECTION_H diff --git a/layers/selections/kpSelectionDrag.cpp b/layers/selections/kpSelectionDrag.cpp new file mode 100644 index 0000000..439dab8 --- /dev/null +++ b/layers/selections/kpSelectionDrag.cpp @@ -0,0 +1,166 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2011 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_SELECTION_DRAG 0 + + +#include "kpSelectionDrag.h" + +#include +#include +#include +#include + +#include "kpLogCategories.h" + +#include "kpSelectionFactory.h" +#include "layers/selections/image/kpAbstractImageSelection.h" +#include "layers/selections/image/kpRectangularImageSelection.h" + +//--------------------------------------------------------------------- + +// public static +const char * const kpSelectionDrag::SelectionMimeType = + "application/x-kolourpaint-selection-400"; + +//--------------------------------------------------------------------- + +kpSelectionDrag::kpSelectionDrag (const kpAbstractImageSelection &sel) +{ +#if DEBUG_KP_SELECTION_DRAG && 1 + qCDebug(kpLogLayers) << "kpSelectionDrag() w=" << sel.width () + << " h=" << sel.height (); +#endif + + Q_ASSERT (sel.hasContent ()); + + // Store as selection. + QByteArray ba; + { + QDataStream stream (&ba, QIODevice::WriteOnly); + stream << sel; + } + setData (kpSelectionDrag::SelectionMimeType, ba); + + // Store as image (so that QMimeData::hasImage()) works). + // OPT: an awful waste of memory storing image in both selection and QImage + const QImage image = sel.baseImage (); +#if DEBUG_KP_SELECTION_DRAG && 1 + qCDebug(kpLogLayers) << "\timage: w=" << image.width () + << " h=" << image.height (); +#endif + if (image.isNull ()) + { + // TODO: proper error handling. + qCCritical(kpLogLayers) << "kpSelectionDrag::setSelection() could not convert to image"; + } + else { + setImageData (image); + } +} + +//--------------------------------------------------------------------- +// public static + +bool kpSelectionDrag::canDecode(const QMimeData *mimeData) +{ + Q_ASSERT(mimeData); + +#if DEBUG_KP_SELECTION_DRAG + qCDebug(kpLogLayers) << "kpSelectionDrag::canDecode()" + << "hasSel=" << mimeData->hasFormat(kpSelectionDrag::SelectionMimeType) + << "hasImage=" << mimeData->hasImage(); +#endif + + // mimeData->hasImage() would not check if the data is a valid image + return mimeData->hasFormat(kpSelectionDrag::SelectionMimeType) || + !qvariant_cast(mimeData->imageData()).isNull(); +} + +//--------------------------------------------------------------------- +// public static + +kpAbstractImageSelection *kpSelectionDrag::decode(const QMimeData *mimeData) +{ +#if DEBUG_KP_SELECTION_DRAG + qCDebug(kpLogLayers) << "kpSelectionDrag::decode(kpAbstractSelection)"; +#endif + Q_ASSERT (mimeData); + + if (mimeData->hasFormat (kpSelectionDrag::SelectionMimeType)) + { + #if DEBUG_KP_SELECTION_DRAG + qCDebug(kpLogLayers) << "\tmimeSource hasFormat selection - just return it in QByteArray"; + #endif + QByteArray data = mimeData->data (kpSelectionDrag::SelectionMimeType); + QDataStream stream (&data, QIODevice::ReadOnly); + + return kpSelectionFactory::FromStream (stream); + } + + +#if DEBUG_KP_SELECTION_DRAG + qCDebug(kpLogLayers) << "\tmimeSource doesn't provide selection - try image"; +#endif + + QImage image = qvariant_cast (mimeData->imageData ()); + if (!image.isNull ()) + { +#if DEBUG_KP_SELECTION_DRAG + qCDebug(kpLogLayers) << "\tok w=" << image.width () << " h=" << image.height (); +#endif + + return new kpRectangularImageSelection ( + QRect (0, 0, image.width (), image.height ()), image); + } + + if ( mimeData->hasUrls() ) // no image, check for path to local image file + { + QList urls = mimeData->urls(); + + if ( urls.count() && urls[0].isLocalFile() ) + { + image.load(urls[0].toLocalFile()); + + if ( !image.isNull() ) + { + return new kpRectangularImageSelection( + QRect(0, 0, image.width(), image.height()), image); + } + } + } + + #if DEBUG_KP_SELECTION_DRAG + qCDebug(kpLogLayers) << "kpSelectionDrag::decode(kpAbstractSelection) mimeSource had no sel " + "and could not decode to image"; + #endif + return nullptr; + +} + +//--------------------------------------------------------------------- diff --git a/layers/selections/kpSelectionDrag.h b/layers/selections/kpSelectionDrag.h new file mode 100644 index 0000000..1da2d3f --- /dev/null +++ b/layers/selections/kpSelectionDrag.h @@ -0,0 +1,52 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_SELECTION_DRAG_H +#define KP_SELECTION_DRAG_H + +#include + +class kpAbstractImageSelection; + + +class kpSelectionDrag : public QMimeData +{ + Q_OBJECT + + public: + static const char * const SelectionMimeType; + + // ASSUMPTION: has content (is not just a border). + kpSelectionDrag(const kpAbstractImageSelection &sel); + + public: + static bool canDecode(const QMimeData *mimeData); + static kpAbstractImageSelection *decode(const QMimeData *mimeData); +}; + + +#endif // KP_SELECTION_DRAG_H diff --git a/layers/selections/kpSelectionFactory.cpp b/layers/selections/kpSelectionFactory.cpp new file mode 100644 index 0000000..1c6bba3 --- /dev/null +++ b/layers/selections/kpSelectionFactory.cpp @@ -0,0 +1,93 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_SELECTION 0 + + +#include "kpSelectionFactory.h" + +#include + +#include "layers/selections/image/kpRectangularImageSelection.h" +#include "layers/selections/image/kpEllipticalImageSelection.h" +#include "layers/selections/image/kpFreeFormImageSelection.h" + +//--------------------------------------------------------------------- + +// public static +// TODO: KolourPaint has not been tested against invalid or malicious +// clipboard data [Bug #28]. +kpAbstractImageSelection *kpSelectionFactory::FromStream (QDataStream &stream) +{ +#if DEBUG_KP_SELECTION && 1 + qCDebug(kpLogLayers) << "kpSelectionFactory::FromStream()"; +#endif + int serialID; + stream >> serialID; + +#if DEBUG_KP_SELECTION && 1 + qCDebug(kpLogLayers) << "\tserialID=" << serialID; +#endif + + // Only image selections are marshalled. + // + // Text selections are only ever seen in the clipboard as ordinary text, + // not selections, since copying text formatting over the clipboard doesn't + // seem compelling. + kpAbstractImageSelection *imageSel = nullptr; + switch (serialID) + { + case kpRectangularImageSelection::SerialID: + imageSel = new kpRectangularImageSelection (); + break; + + case kpEllipticalImageSelection::SerialID: + imageSel = new kpEllipticalImageSelection (); + break; + + case kpFreeFormImageSelection::SerialID: + imageSel = new kpFreeFormImageSelection (); + break; + } + + // Unknown selection type? + if (imageSel == nullptr) + { + return nullptr; + } + + if (!imageSel->readFromStream (stream)) + { + delete imageSel; + return nullptr; + } + + return imageSel; +} + +//--------------------------------------------------------------------- diff --git a/layers/selections/kpSelectionFactory.h b/layers/selections/kpSelectionFactory.h new file mode 100644 index 0000000..b492c5b --- /dev/null +++ b/layers/selections/kpSelectionFactory.h @@ -0,0 +1,48 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpSelectionFactory_H +#define kpSelectionFactory_H + + +#include "pixmapfx/kpPixmapFX.h" + + +class QDataStream; + +class kpAbstractImageSelection; + + +class kpSelectionFactory +{ +public: + static kpAbstractImageSelection *FromStream (QDataStream &stream); +}; + + +#endif // kpSelectionFactory_H diff --git a/layers/selections/text/kpPreeditText.cpp b/layers/selections/text/kpPreeditText.cpp new file mode 100644 index 0000000..f5348d8 --- /dev/null +++ b/layers/selections/text/kpPreeditText.cpp @@ -0,0 +1,142 @@ + +/* + Copyright (c) 2010 Tasuku Suzuki + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "layers/selections/text/kpPreeditText.h" + +//--------------------------------------------------------------------- + +bool attributeLessThan (const QInputMethodEvent::Attribute &a1, const QInputMethodEvent::Attribute &a2) +{ + return a1.start < a2.start; +} + +//--------------------------------------------------------------------- + +kpPreeditText::kpPreeditText () + : m_cursorPosition (0), m_cursorColor (Qt::transparent), + m_selectionStart (0), m_selectionLength (0), + m_position (-1, -1) +{ +} + +//--------------------------------------------------------------------- + +kpPreeditText::kpPreeditText (const QInputMethodEvent *event) + : m_cursorPosition (0), m_cursorColor (Qt::transparent), + m_selectionStart (0), m_selectionLength (0), + m_position (-1, -1) +{ + m_preeditString = event->preeditString (); + for (const auto &attr : event->attributes ()) + { + switch (attr.type) + { + case QInputMethodEvent::TextFormat: + m_textFormatList.append (attr); + break; + case QInputMethodEvent::Cursor: + m_cursorPosition = attr.start; + if (attr.length > 0) + { + m_cursorColor = attr.value.value (); + } + break; + case QInputMethodEvent::Selection: + m_selectionStart = attr.start; + m_selectionLength = attr.length; + break; + default: + break; + } + } + std::sort(m_textFormatList.begin(), m_textFormatList.end(), attributeLessThan); +} + +//--------------------------------------------------------------------- + +bool kpPreeditText::isEmpty () const +{ + return m_preeditString.isEmpty (); +} + +//--------------------------------------------------------------------- + +const QString &kpPreeditText::preeditString () const +{ + return m_preeditString; +} + +//--------------------------------------------------------------------- + +int kpPreeditText::cursorPosition () const +{ + return m_cursorPosition; +} + +//--------------------------------------------------------------------- + +const QColor &kpPreeditText::cursorColor () const +{ + return m_cursorColor; +} + +//--------------------------------------------------------------------- + +int kpPreeditText::selectionStart () const +{ + return m_selectionStart; +} + +//--------------------------------------------------------------------- + +int kpPreeditText::selectionLength () const +{ + return m_selectionLength; +} + +//--------------------------------------------------------------------- + +const QList &kpPreeditText::textFormatList () const +{ + return m_textFormatList; +} + +//--------------------------------------------------------------------- + +const QPoint &kpPreeditText::position () const +{ + return m_position; +} + +//--------------------------------------------------------------------- + +void kpPreeditText::setPosition (const QPoint &position) +{ + m_position = position; +} + +//--------------------------------------------------------------------- diff --git a/layers/selections/text/kpPreeditText.h b/layers/selections/text/kpPreeditText.h new file mode 100644 index 0000000..2870a19 --- /dev/null +++ b/layers/selections/text/kpPreeditText.h @@ -0,0 +1,65 @@ + +/* + Copyright (c) 2010 Tasuku Suzuki + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpPreeditText_H +#define kpPreeditText_H + +#include +#include +#include + +class kpPreeditText +{ +public: + kpPreeditText (); + kpPreeditText (const QInputMethodEvent *event); + + bool isEmpty () const; + + const QString &preeditString () const; + int cursorPosition () const; + bool cursorVisible () const; + const QColor &cursorColor () const; + int selectionStart () const; + int selectionLength () const; + const QList &textFormatList () const; + + const QPoint &position () const; + void setPosition (const QPoint &position); + +private: + QString m_preeditString; + int m_cursorPosition; + QColor m_cursorColor; + int m_selectionStart; + int m_selectionLength; + QList m_textFormatList; + QPoint m_position; +}; + +#endif // kpPreeditText_H diff --git a/layers/selections/text/kpTextSelection.cpp b/layers/selections/text/kpTextSelection.cpp new file mode 100644 index 0000000..fec26ec --- /dev/null +++ b/layers/selections/text/kpTextSelection.cpp @@ -0,0 +1,346 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2010 Tasuku Suzuki + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_SELECTION 0 + + +#include "kpTextSelection.h" +#include "kpTextSelectionPrivate.h" + +#include "kpDefs.h" +#include "kpTextStyle.h" + +#include "kpLogCategories.h" + +#include +#include + + +// public +kpTextSelection::kpTextSelection (const QRect &rect, + const kpTextStyle &textStyle) + : kpAbstractSelection (rect), + d (new kpTextSelectionPrivate ()) +{ + d->textStyle = textStyle; +} + +// public +kpTextSelection::kpTextSelection (const QRect &rect, + const QList &textLines, + const kpTextStyle &textStyle) + : kpAbstractSelection (rect), + d (new kpTextSelectionPrivate ()) +{ + d->textLines = textLines; + d->textStyle = textStyle; +} + +// public +kpTextSelection::kpTextSelection (const kpTextSelection &rhs) + : kpAbstractSelection (), + d (new kpTextSelectionPrivate ()) +{ + *this = rhs; +} + +// public +kpTextSelection &kpTextSelection::operator= (const kpTextSelection &rhs) +{ + kpAbstractSelection::operator= (rhs); + + d->textLines = rhs.d->textLines; + d->textStyle = rhs.d->textStyle; + d->preeditText = rhs.d->preeditText; + + return *this; +} + +// public virtual [base kpAbstractSelection] +kpTextSelection *kpTextSelection::clone () const +{ + kpTextSelection *sel = new kpTextSelection (); + *sel = *this; + return sel; +} + +// public +kpTextSelection *kpTextSelection::resized (int newWidth, int newHeight) const +{ + return new kpTextSelection (QRect (x (), y (), newWidth, newHeight), + d->textLines, + d->textStyle); +} + +// public +kpTextSelection::~kpTextSelection () +{ + delete d; +} + + +// public virtual [kpAbstractSelection] +int kpTextSelection::serialID () const +{ + Q_ASSERT (!"Marshalling not supported"); + return -1; +} + +// public virtual [base kpAbstractSelection] +bool kpTextSelection::readFromStream (QDataStream &stream) +{ + (void) stream; + + Q_ASSERT (!"Marshalling not supported"); + return false; +} + +// public virtual [base kpAbstractSelection] +void kpTextSelection::writeToStream (QDataStream &stream) const +{ + (void) stream; + + Q_ASSERT (!"Marshalling not supported"); +} + + +// public virtual [kpAbstractSelection] +QString kpTextSelection::name () const +{ + return i18n ("Text"); +} + + +// public virtual [base kpAbstractSelection] +kpCommandSize::SizeType kpTextSelection::size () const +{ + return kpAbstractSelection::size () + + kpCommandSize::StringSize (text ()); +} + + +// public virtual [kpAbstractSelection] +bool kpTextSelection::isRectangular () const +{ + return true; +} + + +// public static +int kpTextSelection::MinimumWidthForTextStyle (const kpTextStyle &) +{ + return (kpTextSelection::TextBorderSize () * 2 + 5); +} + +// public static +int kpTextSelection::MinimumHeightForTextStyle (const kpTextStyle &) +{ + return (kpTextSelection::TextBorderSize () * 2 + 5); +} + +// public static +QSize kpTextSelection::MinimumSizeForTextStyle (const kpTextStyle &textStyle) +{ + return {kpTextSelection::MinimumWidthForTextStyle (textStyle), + kpTextSelection::MinimumHeightForTextStyle (textStyle)}; +} + + +// public virtual [kpAbstractSelection] +int kpTextSelection::minimumWidth () const +{ + return kpTextSelection::MinimumWidthForTextStyle (textStyle ()); +} + +// public virtual [kpAbstractSelection] +int kpTextSelection::minimumHeight () const +{ + return kpTextSelection::MinimumHeightForTextStyle (textStyle ()); +} + + +// public static +int kpTextSelection::PreferredMinimumWidthForTextStyle (const kpTextStyle &textStyle) +{ + const int about15CharsWidth = + textStyle.fontMetrics().horizontalAdvance(QLatin1String("1234567890abcde")); + + const int preferredMinWidth = + qMax (150, + kpTextSelection::TextBorderSize () * 2 + about15CharsWidth); + + return qMax (kpTextSelection::MinimumWidthForTextStyle (textStyle), + qMin (250, preferredMinWidth)); +} + +// public static +int kpTextSelection::PreferredMinimumHeightForTextStyle (const kpTextStyle &textStyle) +{ + const int preferredMinHeight = + kpTextSelection::TextBorderSize () * 2 + textStyle.fontMetrics ().height (); + + return qMax (kpTextSelection::MinimumHeightForTextStyle (textStyle), + qMin (150, preferredMinHeight)); +} + +// public static +QSize kpTextSelection::PreferredMinimumSizeForTextStyle (const kpTextStyle &textStyle) +{ + return {kpTextSelection::PreferredMinimumWidthForTextStyle (textStyle), + kpTextSelection::PreferredMinimumHeightForTextStyle (textStyle)}; +} + + +// public static +int kpTextSelection::TextBorderSize () +{ + return 1; +} + +// public +QRect kpTextSelection::textAreaRect () const +{ + return {x () + kpTextSelection::TextBorderSize (), + y () + kpTextSelection::TextBorderSize (), + width () - kpTextSelection::TextBorderSize () * 2, + height () - kpTextSelection::TextBorderSize () * 2}; +} + + +// public virtual [kpAbstractSelection] +QPolygon kpTextSelection::calculatePoints () const +{ + return kpAbstractSelection::CalculatePointsForRectangle (boundingRect ()); +} + + +// public virtual [kpAbstractSelection] +bool kpTextSelection::contains (const QPoint &point) const +{ + return boundingRect ().contains (point); +} + + +// public +bool kpTextSelection::pointIsInTextBorderArea (const QPoint &point) const +{ + return (boundingRect ().contains (point) && !pointIsInTextArea (point)); +} + +// public +bool kpTextSelection::pointIsInTextArea (const QPoint &point) const +{ + return textAreaRect ().contains (point); +} + + +// public virtual [kpAbstractSelection] +bool kpTextSelection::hasContent () const +{ + return !d->textLines.isEmpty (); +} + +// public virtual [kpAbstractSelection] +void kpTextSelection::deleteContent () +{ + if (!hasContent ()) { + return; + } + + setTextLines (QList ()); +} + + +// public +QList kpTextSelection::textLines () const +{ + return d->textLines; +} + +// public +void kpTextSelection::setTextLines (const QList &textLines_) +{ + d->textLines = textLines_; + + emit changed (boundingRect ()); +} + +//-------------------------------------------------------------------------------- + +// public static +QString kpTextSelection::textForTextLines(const QList &textLines) +{ + if (textLines.isEmpty ()) + return QString(); + + QString bigString = textLines[0]; + + for (int i = 1; i < textLines.count(); i++) + { + bigString += QLatin1String("\n"); + bigString += textLines[i]; + } + + return bigString; +} + +//-------------------------------------------------------------------------------- + +// public +QString kpTextSelection::text () const +{ + return kpTextSelection::textForTextLines (d->textLines); +} + + +// public +kpTextStyle kpTextSelection::textStyle () const +{ + return d->textStyle; +} + +// public +void kpTextSelection::setTextStyle (const kpTextStyle &textStyle) +{ + d->textStyle = textStyle; + + emit changed (boundingRect ()); +} + +kpPreeditText kpTextSelection::preeditText () const +{ + return d->preeditText; +} + +void kpTextSelection::setPreeditText (const kpPreeditText &preeditText) +{ + d->preeditText = preeditText; + emit changed (boundingRect ()); +} + diff --git a/layers/selections/text/kpTextSelection.h b/layers/selections/text/kpTextSelection.h new file mode 100644 index 0000000..40b50f6 --- /dev/null +++ b/layers/selections/text/kpTextSelection.h @@ -0,0 +1,294 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTextSelection_H +#define kpTextSelection_H + + +#include "layers/selections/kpAbstractSelection.h" +#include "imagelib/kpImage.h" +#include "layers/selections/text/kpTextStyle.h" +#include "layers/selections/text/kpPreeditText.h" + +// +// A rectangular text box containing lines of text, rendered in a given text +// style. +// +// A text selection with an empty list of text lines is just a text box +// border and contains no content. +// +// The minimal text selection that is considered to contain content consists +// of a single empty text line. +// +// The rendered elements are as follows: +// +// ############### +// # # +// # * *---------- Text Border +// # | # +// #####|######### +// | +// | +// Text Area +// (text lines are rendered on top of here; the parts of the lines +// that don't fit here are not rendered) +// +// The text style determines how the text box is drawn. +// +// The foreground color determines the color of the text. Transparent +// foreground text means that the text box is see-through for the pixels +// of the text, exposing the document pixels below. It does not mean +// that the transparent color is drawn onto the document. In other +// words, we are "painting" transparent pixels -- not "setting" them. +// +// If the background color is opaque, the text is drawn on top of a +// filled-in rectangle and the rectangle completely overwrites any +// document pixels below. +// +// Consistent with the behavior of a transparent foreground color, a +// transparent background color does not mean that a transparent-colored +// rectangle is drawn onto the document. Instead, it means that the +// text box background is see-through so that text is drawn directly on +// top of the document pixels. No rectangle is drawn in this case. +// +// A text box with transparent foreground and background colors is +// completely invisible since both the text and background are see-through. +// +// A rendered text cursor is controlled separately by kpViewManager, as +// cursors are only a view concept so do not belong in this document-based +// class. +// +// Marshalling, for copying text selections to the clipboard, is not +// currently supported. This is because generally, users are only interested +// in the text itself, not the border nor formatting. +// +class kpTextSelection : public kpAbstractSelection +{ +Q_OBJECT + +// +// Initialization +// + +public: + kpTextSelection (const QRect &rect = QRect (), + const kpTextStyle &textStyle = kpTextStyle ()); + kpTextSelection (const QRect &rect, const QList &textLines, + const kpTextStyle &textStyle); + kpTextSelection (const kpTextSelection &rhs); + + kpTextSelection &operator= (const kpTextSelection &rhs); + + kpTextSelection *clone () const override; + + // Returns a copy of the text selection but with new dimensions + // x . + kpTextSelection *resized (int newWidth, int newHeight) const; + + ~kpTextSelection () override; + + +// +// Marshalling +// + +public: + int serialID () const override; + + bool readFromStream (QDataStream &stream) override; + + void writeToStream (QDataStream &stream) const override; + + +// +// General Queries +// + +public: + QString name () const override; + + kpCommandSize::SizeType size () const override; + +public: + bool isRectangular () const override; + + +// +// Position & Dimensions +// + +public: + // Returns the absolute minimum size that a textbox must be if it is of + // the given . + // + // This leaves enough room for the border on all 4 sides and also a + // text area big enough to fit a character in an extremely small font. + static int MinimumWidthForTextStyle (const kpTextStyle &textStyle); + static int MinimumHeightForTextStyle (const kpTextStyle &textStyle); + static QSize MinimumSizeForTextStyle (const kpTextStyle &textStyle); + + // REFACTOR: Enforce in kpTextSelection, not just in kpToolSelection & + // when pasting (in kpMainWindow). + // + // Otherwise, if enforcement fails, e.g. textAreaRect() will + // not work. + int minimumWidth () const override; + int minimumHeight () const override; + +public: + // Returns the suggested minimum size that a textbox should be if it is of + // the given . + // + // This leaves enough room for the border on all 4 sides and also for + // a small line of the text in the given text style. + static int PreferredMinimumWidthForTextStyle (const kpTextStyle &textStyle); + static int PreferredMinimumHeightForTextStyle (const kpTextStyle &textStyle); + static QSize PreferredMinimumSizeForTextStyle (const kpTextStyle &textStyle); + +public: + // Returns the size of the text border. Constant. + static int TextBorderSize (); + + // Returns the rectangle that text lines are drawn on top of. + // This will be a sub-rectangle of boundingRect() and is therefore, + // in document coordinates like everything else in this class. + QRect textAreaRect () const; + +public: + QPolygon calculatePoints () const override; + + +// +// Point Testing +// + +public: + bool contains (const QPoint &point) const override; + +public: + bool pointIsInTextBorderArea (const QPoint &point) const; + bool pointIsInTextArea (const QPoint &point) const; + + +// +// Content +// + +public: + // (see class header comment) + bool hasContent () const override; + + void deleteContent () override; + +public: + QList textLines () const; + void setTextLines (const QList &textLines); + + static QString textForTextLines (const QList &textLines); + // Returns textLines() as one long newline-separated string. + // If the last text line is not empty, there is no trailing newline. + QString text () const; + + +// +// Text Style +// + +public: + kpTextStyle textStyle () const; + void setTextStyle (const kpTextStyle &textStyle); + + +// +// Preedit Text +// + +public: + kpPreeditText preeditText () const; + void setPreeditText (const kpPreeditText &preeditText); + +// +// Cursor +// +// A text cursor position is the row and column of a character in +// textLines(), that it is to the left of. As a result, a column value +// of 1 character past the last character of a text line is allowed. +// + +public: + // If the given point is in the text area, it returns the closest + // row/column (in textLines()) for the point. + // + // If the given point is not in the text area, it returns -1. + int closestTextRowForPoint (const QPoint &point) const; + int closestTextColForPoint (const QPoint &point) const; + + // Given a valid row and column in textLines(), returns the top-left + // point of where the text cursor should be rendered. + // TODO: Code is not symmetric to closestTest{Row,Col}ForPoint() + // [look at the Y/row value calculations] + // + // If the row and column is not inside textLines(), it returns + // KP_INVALID_POINT. + QPoint pointForTextRowCol (int row, int col) const; + + +// +// Rendering +// + +private: + void drawPreeditString(QPainter &painter, int &x, int y, const kpPreeditText &preeditText) const; + +public: + void paint(QImage *destPixmap, const QRect &docRect) const override; + + void paintBorder(QImage *destPixmap, const QRect &docRect, + bool selectionFinished) const override; + +public: + // Returns an image that contains the painted text (without a border). + // + // If the text box has a see-through background, the image will be given + // an arbitrarily neutral background (currently, the transparent color). + // As a result, the returned image will be an approximation since text + // boxes are normally rendered -- and antialiased with -- a different + // background, namely the document image. Therefore, it is invalid to + // stamp the returned image onto the document image and expect it to look + // like stamping this text selection onto the document image (the latter + // is achieved via kpDocument::selectionPushOntoDocument(), antialiases + // and is more correct). + kpImage approximateImage () const; + + +private: + struct kpTextSelectionPrivate * const d; +}; + + +#endif // kpTextSelection_H diff --git a/layers/selections/text/kpTextSelectionPrivate.h b/layers/selections/text/kpTextSelectionPrivate.h new file mode 100644 index 0000000..6f3cbed --- /dev/null +++ b/layers/selections/text/kpTextSelectionPrivate.h @@ -0,0 +1,47 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpTextSelectionPrivate_H +#define kpTextSelectionPrivate_H + + +#include + +#include "imagelib/kpImage.h" +#include "layers/selections/text/kpTextStyle.h" +#include "layers/selections/text/kpPreeditText.h" + +struct kpTextSelectionPrivate +{ + QList textLines; + kpTextStyle textStyle; + kpPreeditText preeditText; +}; + + +#endif // kpTextSelectionPrivate_H diff --git a/layers/selections/text/kpTextSelection_Cursor.cpp b/layers/selections/text/kpTextSelection_Cursor.cpp new file mode 100644 index 0000000..8db5b78 --- /dev/null +++ b/layers/selections/text/kpTextSelection_Cursor.cpp @@ -0,0 +1,129 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2010 Tasuku Suzuki + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_SELECTION 0 + + +#include "kpTextSelection.h" +#include "kpTextSelectionPrivate.h" + +#include "kpDefs.h" +#include "kpTextStyle.h" +#include "kpPreeditText.h" + +#include "kpLogCategories.h" + +#include +#include + + +// public +int kpTextSelection::closestTextRowForPoint (const QPoint &point) const +{ + if (!pointIsInTextArea (point)) { + return -1; + } + + const QFontMetrics fontMetrics (d->textStyle.fontMetrics ()); + + int row = (point.y () - textAreaRect ().y ()) / + fontMetrics.lineSpacing (); + if (row >= static_cast (d->textLines.size ())) { + row = d->textLines.size () - 1; + } + + return row; +} + +// public +int kpTextSelection::closestTextColForPoint (const QPoint &point) const +{ + int row = closestTextRowForPoint (point); + if (row < 0 || row >= static_cast (d->textLines.size ())) { + return -1; + } + + const int localX = point.x () - textAreaRect ().x (); + + const QFontMetrics fontMetrics (d->textStyle.fontMetrics ()); + + // (should be 0 but call just in case) + int charLocalLeft = fontMetrics.horizontalAdvance(d->textLines [row], 0); + + // OPT: binary search or guess location then move + for (int col = 0; col < static_cast (d->textLines [row].length ()); col++) + { + // OPT: fontMetrics::charWidth() might be faster + const int nextCharLocalLeft = fontMetrics.horizontalAdvance(d->textLines [row], col + 1); + if (localX <= (charLocalLeft + nextCharLocalLeft) / 2) { + return col; + } + + charLocalLeft = nextCharLocalLeft; + } + + return d->textLines [row].length ()/*past end of line*/; +} + +//--------------------------------------------------------------------- + +// public +QPoint kpTextSelection::pointForTextRowCol (int row, int col) const +{ + kpPreeditText preeditText = d->preeditText; + if ((row < 0 || col < 0) || + (preeditText.isEmpty () && + (row >= static_cast (d->textLines.size ()) || col > static_cast (d->textLines [row].length ())))) + { +#if DEBUG_KP_SELECTION && 1 + qCDebug(kpLogLayers) << "kpTextSelection::pointForTextRowCol(" + << row << "," + << col << ") out of range" + << " textLines='" + << text () + << "'"; +#endif + return KP_INVALID_POINT; + } + + const QFontMetrics fontMetrics (d->textStyle.fontMetrics ()); + + QString line = (d->textLines.count () > row) ? d->textLines[row] : QString (); + if (row == preeditText.position ().y ()) + { + line.insert (preeditText.position ().x (), preeditText.preeditString ()); + } + const int x = fontMetrics.horizontalAdvance(line.left(col)); + const int y = row * fontMetrics.height () + + (row >= 1 ? row * fontMetrics.leading () : 0); + + return textAreaRect ().topLeft () + QPoint (x, y); +} + +//--------------------------------------------------------------------- diff --git a/layers/selections/text/kpTextSelection_Paint.cpp b/layers/selections/text/kpTextSelection_Paint.cpp new file mode 100644 index 0000000..e68c182 --- /dev/null +++ b/layers/selections/text/kpTextSelection_Paint.cpp @@ -0,0 +1,274 @@ + +// REFACTOR: Move into kpPainter + +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2010 Tasuku Suzuki + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_SELECTION 0 + + +#include "kpTextSelection.h" +#include "kpTextSelectionPrivate.h" + +#include "kpTextStyle.h" +#include "kpPreeditText.h" +#include "pixmapfx/kpPixmapFX.h" + +#include "kpLogCategories.h" + +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------- + +void kpTextSelection::drawPreeditString(QPainter &painter, int &x, int y, const kpPreeditText &preeditText) const +{ + int i = 0; + const QString& preeditString = preeditText.preeditString (); + QString str; + for (const auto &attr : preeditText.textFormatList ()) + { + int start = attr.start; + int length = attr.length; + QTextCharFormat format = qvariant_cast (attr.value).toCharFormat (); + + if (i > start) + { + length = length - i + start; + start = i; + } + if (length <= 0) { + continue; + } + + if (i < start) + { + str = preeditString.mid (i, start - i); + painter.drawText (x, y, str); + x += painter.fontMetrics().horizontalAdvance(str); + } + + painter.save(); + str = preeditString.mid (start, length); + int width = painter.fontMetrics().horizontalAdvance(str); + if (format.background ().color () != Qt::black) + { + painter.save (); + painter.setPen (format.background ().color ()); + painter.setBrush (format.background()); + painter.drawRect (x, y - painter.fontMetrics ().ascent (), width, painter.fontMetrics ().height ()); + painter.restore (); + } + if (format.foreground ().color () != Qt::black) + { + painter.setBrush (format.foreground ()); + painter.setPen (format.foreground ().color ()); + } + if (format.underlineStyle ()) + { + painter.drawLine (x, y + painter.fontMetrics ().descent (), x + width, y + painter.fontMetrics ().descent ()); + } + painter.drawText (x, y, str); + + x += width; + painter.restore (); + + i = start + length; + } + if (i < preeditString.length ()) + { + str = preeditString.mid (i); + painter.drawText (x, y, str); + x += painter.fontMetrics().horizontalAdvance(str); + } +} + +//--------------------------------------------------------------------- + +// public virtual [kpAbstractSelection] +void kpTextSelection::paint(QImage *destPixmap, const QRect &docRect) const +{ +#if DEBUG_KP_SELECTION + qCDebug(kpLogLayers) << "kpTextSelection::paint() textStyle: fcol=" + << (int *) d->textStyle.foregroundColor ().toQRgb () + << " bcol=" + << (int *) d->textStyle.backgroundColor ().toQRgb (); +#endif + + // Drawing text is slow so if the text box will be rendered completely + // outside of , don't bother rendering it at all. + const QRect modifyingRect = docRect.intersected (boundingRect ()); + if (modifyingRect.isEmpty ()) { + return; + } + + + // Is the text box completely invisible? + if (textStyle ().foregroundColor ().isTransparent () && + textStyle ().backgroundColor ().isTransparent ()) + { + return; + } + + kpImage floatImage(modifyingRect.size(), QImage::Format_ARGB32_Premultiplied); + floatImage.fill(0); + + QRect theWholeAreaRect, theTextAreaRect; + theWholeAreaRect = boundingRect ().translated (-modifyingRect.topLeft ()); + theTextAreaRect = textAreaRect ().translated (-modifyingRect.topLeft ()); + + QList theTextLines = textLines(); + kpTextStyle theTextStyle = textStyle(); + + const QFontMetrics fontMetrics (theTextStyle.font ()); + +#if DEBUG_KP_SELECTION + qCDebug(kpLogLayers) << "kpTextSelection_Paint.cpp:DrawTextHelper"; + qCDebug(kpLogLayers) << "\theight=" << fontMetrics.height () + << " leading=" << fontMetrics.leading () + << " ascent=" << fontMetrics.ascent () + << " descent=" << fontMetrics.descent () + << " lineSpacing=" << fontMetrics.lineSpacing (); +#endif + + QPainter painter(&floatImage); + + // Fill in the background using the transparent/opaque tool setting + if ( theTextStyle.isBackgroundTransparent() ) { + painter.fillRect(theWholeAreaRect, Qt::transparent); + } + else { + painter.fillRect(theWholeAreaRect, theTextStyle.backgroundColor().toQColor()); + } + + painter.setClipRect(theWholeAreaRect); + painter.setPen(theTextStyle.foregroundColor().toQColor()); + painter.setFont(theTextStyle.font()); + + if ( theTextStyle.foregroundColor().toQColor().alpha() < 255 ) + { + // if the foreground color has an alpha channel, we want to + // see through the background, so we first need to punch holes + // into the background where the text is + painter.setCompositionMode(QPainter::CompositionMode_Clear); + + int baseLine = theTextAreaRect.y () + fontMetrics.ascent (); + for (const auto &str : theTextLines) + { + painter.drawText (theTextAreaRect.x (), baseLine, str); + baseLine += fontMetrics.lineSpacing (); + + // if the next textline would already be below the visible text area, stop drawing + if ( (baseLine - fontMetrics.ascent()) > (theTextAreaRect.y() + theTextAreaRect.height()) ) { + break; + } + } + // the next text drawing will now blend the text foreground color with + // what is really below the text background + painter.setCompositionMode(QPainter::CompositionMode_SourceOver); + } + + // Draw a line at a time instead of using QPainter::drawText(QRect,...). + // Else, the line heights become >QFontMetrics::height() if you type Chinese + // characters (!) and then the cursor gets out of sync. + int baseLine = theTextAreaRect.y () + fontMetrics.ascent (); + + kpPreeditText thePreeditText = preeditText(); + + if ( theTextLines.isEmpty() ) + { + if ( ! thePreeditText.isEmpty() ) + { + int x = theTextAreaRect.x(); + drawPreeditString(painter, x, baseLine, thePreeditText); + } + } + else + { + int i = 0; + int row = thePreeditText.position().y(); + int col = thePreeditText.position().x(); + for (const auto &str : theTextLines) + { + if (row == i && !thePreeditText.isEmpty()) + { + QString left = str.left(col); + QString right = str.mid(col); + int x = theTextAreaRect.x(); + painter.drawText(x, baseLine, left); + x += fontMetrics.horizontalAdvance(left); + drawPreeditString(painter, x, baseLine, thePreeditText); + + painter.drawText(x, baseLine, right); + } + else + { + painter.drawText(theTextAreaRect.x (), baseLine, str); + } + baseLine += fontMetrics.lineSpacing(); + i++; + + // if the next textline would already be below the visible text area, stop drawing + if ( (baseLine - fontMetrics.ascent()) > (theTextAreaRect.y() + theTextAreaRect.height()) ) { + break; + } + } + } + + // ... convert that into "painting" transparent pixels on top of + // the document. + kpPixmapFX::paintPixmapAt (destPixmap, + modifyingRect.topLeft () - docRect.topLeft (), + floatImage); +} + +//--------------------------------------------------------------------- + + +// public virtual [kpAbstractSelection] +void kpTextSelection::paintBorder (QImage *destPixmap, const QRect &docRect, + bool selectionFinished) const +{ + paintRectangularBorder (destPixmap, docRect, selectionFinished); +} + +//--------------------------------------------------------------------- + +// public +kpImage kpTextSelection::approximateImage () const +{ + kpImage retImage (width (), height (), QImage::Format_ARGB32_Premultiplied); + retImage.fill(0); + paint (&retImage, boundingRect ()); + return retImage; +} + +//--------------------------------------------------------------------- diff --git a/layers/selections/text/kpTextStyle.cpp b/layers/selections/text/kpTextStyle.cpp new file mode 100644 index 0000000..5b05caf --- /dev/null +++ b/layers/selections/text/kpTextStyle.cpp @@ -0,0 +1,267 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "layers/selections/text/kpTextStyle.h" + +#include +#include +#include + + +kpTextStyle::kpTextStyle () + : m_fontSize (0), + m_isBold (false), m_isItalic (false), + m_isUnderline (false), m_isStrikeThru (false), + m_isBackgroundOpaque (true) +{ +} + +kpTextStyle::kpTextStyle (const QString &fontFamily, + int fontSize, + bool isBold, bool isItalic, + bool isUnderline, bool isStrikeThru, + const kpColor &fcolor, const kpColor &bcolor, + bool isBackgroundOpaque) + : m_fontFamily (fontFamily), + m_fontSize (fontSize), + m_isBold (isBold), m_isItalic (isItalic), + m_isUnderline (isUnderline), m_isStrikeThru (isStrikeThru), + m_foregroundColor (fcolor), m_backgroundColor (bcolor), + m_isBackgroundOpaque (isBackgroundOpaque) +{ +} + +kpTextStyle::~kpTextStyle () = default; + + +// friend +QDataStream &operator<< (QDataStream &stream, const kpTextStyle &textStyle) +{ + stream << textStyle.m_fontFamily; + stream << textStyle.m_fontSize; + + stream << int (textStyle.m_isBold) << int (textStyle.m_isItalic) + << int (textStyle.m_isUnderline) << int (textStyle.m_isStrikeThru); + + stream << textStyle.m_foregroundColor << textStyle.m_backgroundColor; + + stream << int (textStyle.m_isBackgroundOpaque); + + return stream; +} + +// friend +QDataStream &operator>> (QDataStream &stream, kpTextStyle &textStyle) +{ + stream >> textStyle.m_fontFamily; + stream >> textStyle.m_fontSize; + + int a, b, c, d; + stream >> a >> b >> c >> d; + textStyle.m_isBold = a; + textStyle.m_isItalic = b; + textStyle.m_isUnderline = c; + textStyle.m_isStrikeThru = d; + + stream >> textStyle.m_foregroundColor >> textStyle.m_backgroundColor; + + int e; + stream >> e; + textStyle.m_isBackgroundOpaque = e; + + return stream; +} + +// public +bool kpTextStyle::operator== (const kpTextStyle &rhs) const +{ + return (m_fontFamily == rhs.m_fontFamily && + m_fontSize == rhs.m_fontSize && + m_isBold == rhs.m_isBold && + m_isItalic == rhs.m_isItalic && + m_isUnderline == rhs.m_isUnderline && + m_isStrikeThru == rhs.m_isStrikeThru && + m_foregroundColor == rhs.m_foregroundColor && + m_backgroundColor == rhs.m_backgroundColor && + m_isBackgroundOpaque == rhs.m_isBackgroundOpaque); +} + +// public +bool kpTextStyle::operator!= (const kpTextStyle &rhs) const +{ + return !(*this == rhs); +} + + +// public +QString kpTextStyle::fontFamily () const +{ + return m_fontFamily; +} + +// public +void kpTextStyle::setFontFamily (const QString &f) +{ + m_fontFamily = f; +} + + +// public +int kpTextStyle::fontSize () const +{ + return m_fontSize; +} + +// public +void kpTextStyle::setFontSize (int s) +{ + m_fontSize = s; +} + + +// public +bool kpTextStyle::isBold () const +{ + return m_isBold; +} + +// public +void kpTextStyle::setBold (bool yes) +{ + m_isBold = yes; +} + + +// public +bool kpTextStyle::isItalic () const +{ + return m_isItalic; +} + +// public +void kpTextStyle::setItalic (bool yes) +{ + m_isItalic = yes; +} + + +// public +bool kpTextStyle::isUnderline () const +{ + return m_isUnderline; +} + +// public +void kpTextStyle::setUnderline (bool yes) +{ + m_isUnderline = yes; +} + + +// public +bool kpTextStyle::isStrikeThru () const +{ + return m_isStrikeThru; +} + +// public +void kpTextStyle::setStrikeThru (bool yes) +{ + m_isStrikeThru = yes; +} + + +// public +kpColor kpTextStyle::foregroundColor () const +{ + return m_foregroundColor; +} + +// public +void kpTextStyle::setForegroundColor (const kpColor &fcolor) +{ + m_foregroundColor = fcolor; +} + + +// public +kpColor kpTextStyle::backgroundColor () const +{ + return m_backgroundColor; +} + +// public +void kpTextStyle::setBackgroundColor (const kpColor &bcolor) +{ + m_backgroundColor = bcolor; +} + + +// public +bool kpTextStyle::isBackgroundOpaque () const +{ + return m_isBackgroundOpaque; +} + +// public +void kpTextStyle::setBackgroundOpaque (bool yes) +{ + m_isBackgroundOpaque = yes; +} + + +// public +bool kpTextStyle::isBackgroundTransparent () const +{ + return !m_isBackgroundOpaque; +} + +// public +void kpTextStyle::setBackgroundTransparent (bool yes) +{ + m_isBackgroundOpaque = !yes; +} + + +// public +QFont kpTextStyle::font () const +{ + QFont fnt (m_fontFamily, m_fontSize); + fnt.setBold (m_isBold); + fnt.setItalic (m_isItalic); + fnt.setUnderline (m_isUnderline); + fnt.setStrikeOut (m_isStrikeThru); + + return fnt; +} + +// public +QFontMetrics kpTextStyle::fontMetrics () const +{ + return QFontMetrics (font ()); +} diff --git a/layers/selections/text/kpTextStyle.h b/layers/selections/text/kpTextStyle.h new file mode 100644 index 0000000..c7ebfa2 --- /dev/null +++ b/layers/selections/text/kpTextStyle.h @@ -0,0 +1,107 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_TEXT_STYLE_H +#define KP_TEXT_STYLE_H + + +#include + +#include "imagelib/kpColor.h" + + +class QDataStream; +class QFont; +class QFontMetrics; + + +class kpTextStyle +{ +public: + kpTextStyle (); + kpTextStyle (const QString &fontFamily, + int fontSize, + bool isBold, bool isItalic, + bool isUnderline, bool isStrikeThru, + const kpColor &fcolor, + const kpColor &bcolor, + bool isBackgroundOpaque); + ~kpTextStyle (); + + + friend QDataStream &operator<< (QDataStream &stream, const kpTextStyle &textStyle); + friend QDataStream &operator>> (QDataStream &stream, kpTextStyle &textStyle); + bool operator== (const kpTextStyle &rhs) const; + bool operator!= (const kpTextStyle &rhs) const; + + + QString fontFamily () const; + void setFontFamily (const QString &f); + + int fontSize () const; + void setFontSize (int s); + + bool isBold () const; + void setBold (bool yes = true); + + bool isItalic () const; + void setItalic (bool yes = true); + + bool isUnderline () const; + void setUnderline (bool yes = true); + + bool isStrikeThru () const; + void setStrikeThru (bool yes = true); + + kpColor foregroundColor () const; + void setForegroundColor (const kpColor &fcolor); + + // Note: This is the _input_ backgroundColor + kpColor backgroundColor () const; + void setBackgroundColor (const kpColor &bcolor); + + bool isBackgroundOpaque () const; + void setBackgroundOpaque (bool yes = true); + + bool isBackgroundTransparent () const; + void setBackgroundTransparent (bool yes = true); + + + QFont font () const; + QFontMetrics fontMetrics () const; + +private: + QString m_fontFamily; + int m_fontSize; + bool m_isBold, m_isItalic, m_isUnderline, m_isStrikeThru; + kpColor m_foregroundColor, m_backgroundColor; + bool m_isBackgroundOpaque; +}; + + +#endif // KP_TEXT_STYLE_H diff --git a/layers/tempImage/kpTempImage.cpp b/layers/tempImage/kpTempImage.cpp new file mode 100644 index 0000000..32d9b35 --- /dev/null +++ b/layers/tempImage/kpTempImage.cpp @@ -0,0 +1,218 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "layers/tempImage/kpTempImage.h" + +#include "pixmapfx/kpPixmapFX.h" +#include "views/manager/kpViewManager.h" + +#include + +//--------------------------------------------------------------------- + +kpTempImage::kpTempImage (bool isBrush, RenderMode renderMode, + const QPoint &topLeft, const kpImage &image) + : m_isBrush (isBrush), + m_renderMode (renderMode), + m_topLeft (topLeft), + m_image (image), + m_width (image.width ()), m_height (image.height ()), + m_userFunction (nullptr), + m_userData (nullptr) +{ + // Use below constructor for that. + Q_ASSERT (renderMode != UserFunction); +} + +//--------------------------------------------------------------------- + +kpTempImage::kpTempImage (bool isBrush, const QPoint &topLeft, + UserFunctionType userFunction, void *userData, + int width, int height) + : m_isBrush (isBrush), + m_renderMode (UserFunction), + m_topLeft (topLeft), + m_width (width), m_height (height), + m_userFunction (userFunction), + m_userData (userData) +{ + Q_ASSERT (m_userFunction); +} + +//--------------------------------------------------------------------- + +kpTempImage::kpTempImage (const kpTempImage &rhs) + : m_isBrush (rhs.m_isBrush), + m_renderMode (rhs.m_renderMode), + m_topLeft (rhs.m_topLeft), + m_image (rhs.m_image), + m_width (rhs.m_width), m_height (rhs.m_height), + m_userFunction (rhs.m_userFunction), + m_userData (rhs.m_userData) +{ +} + +//--------------------------------------------------------------------- + +kpTempImage &kpTempImage::operator= (const kpTempImage &rhs) +{ + if (this == &rhs) { + return *this; + } + + m_isBrush = rhs.m_isBrush; + m_renderMode = rhs.m_renderMode; + m_topLeft = rhs.m_topLeft; + m_image = rhs.m_image; + m_width = rhs.m_width; + m_height = rhs.m_height; + m_userFunction = rhs.m_userFunction; + m_userData = rhs.m_userData; + + return *this; +} + +//--------------------------------------------------------------------- + +// public +bool kpTempImage::isBrush () const +{ + return m_isBrush; +} + +//--------------------------------------------------------------------- + +// public +kpTempImage::RenderMode kpTempImage::renderMode () const +{ + return m_renderMode; +} + +//--------------------------------------------------------------------- + +// public +QPoint kpTempImage::topLeft () const +{ + return m_topLeft; +} + +//--------------------------------------------------------------------- + +// public +kpImage kpTempImage::image () const +{ + return m_image; +} + +//--------------------------------------------------------------------- + +// public +kpTempImage::UserFunctionType kpTempImage::userFunction () const +{ + return m_userFunction; +} + +//--------------------------------------------------------------------- + +// public +void *kpTempImage::userData () const +{ + return m_userData; +} + +//--------------------------------------------------------------------- + +// public +bool kpTempImage::isVisible (const kpViewManager *vm) const +{ + return m_isBrush ? static_cast (vm->viewUnderCursor ()) : true; +} + +//--------------------------------------------------------------------- + +// public +QRect kpTempImage::rect () const +{ + return {m_topLeft.x (), m_topLeft.y (), m_width, m_height}; +} + +//--------------------------------------------------------------------- + +// public +int kpTempImage::width () const +{ + return m_width; +} + +//--------------------------------------------------------------------- + +// public +int kpTempImage::height () const +{ + return m_height; +} + +//--------------------------------------------------------------------- + +// public +bool kpTempImage::paintMayAddMask () const +{ + return (m_renderMode == SetImage || + m_renderMode == UserFunction); +} + +//--------------------------------------------------------------------- + +// public +void kpTempImage::paint (kpImage *destImage, const QRect &docRect) const +{ + const QPoint REL_TOP_LEFT = m_topLeft - docRect.topLeft (); + + switch (m_renderMode) + { + case SetImage: + { + kpPixmapFX::setPixmapAt(destImage, REL_TOP_LEFT, m_image); + break; + } + + case PaintImage: + { + kpPixmapFX::paintPixmapAt(destImage, REL_TOP_LEFT, m_image); + break; + } + + case UserFunction: + { + m_userFunction(destImage, REL_TOP_LEFT, m_userData); + break; + } + } +} + +//--------------------------------------------------------------------- diff --git a/layers/tempImage/kpTempImage.h b/layers/tempImage/kpTempImage.h new file mode 100644 index 0000000..331f11e --- /dev/null +++ b/layers/tempImage/kpTempImage.h @@ -0,0 +1,110 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// REFACTOR: maybe make us and kpAbstractSelection share a new kpLayer base? + + +#ifndef kpTempImage_H +#define kpTempImage_H + + +#include + +#include "imagelib/kpImage.h" + + +class kpViewManager; + + +class kpTempImage +{ +public: + // REFACTOR: Extract into class hierarchy. + enum RenderMode + { + SetImage, + PaintImage, + UserFunction + }; + + // REFACTOR: Function pointers imply a need for a proper class hierarchy. + typedef void (*UserFunctionType) (kpImage * /*destImage*/, + const QPoint & /*topLeft*/, + void * /*userData*/); + + /* + * Specifies that its visibility is dependent on whether or + * not the mouse cursor is inside a view. If false, the + * image is always displayed. + * + * This is the only way of specifying the "UserFunction" + * . must not draw outside + * the claimed rectangle. + */ + kpTempImage (bool isBrush, RenderMode renderMode, const QPoint &topLeft, const kpImage &image); + kpTempImage (bool isBrush, const QPoint &topLeft, + UserFunctionType userFunction, void *userData, + int width, int height); + kpTempImage (const kpTempImage &rhs); + kpTempImage &operator= (const kpTempImage &rhs); + + bool isBrush () const; + RenderMode renderMode () const; + QPoint topLeft () const; + kpImage image () const; + UserFunctionType userFunction () const; + void *userData () const; + + bool isVisible (const kpViewManager *vm) const; + QRect rect () const; + int width () const; + int height () const; + + + // Returns whether a call to paint() may add a mask to <*destImage>. + bool paintMayAddMask () const; + + /* + * Draws itself onto <*destImage>, given that <*destImage> represents + * the unzoomed of the kpDocument. You should check for + * visibility before calling this function. + */ + void paint (kpImage *destImage, const QRect &docRect) const; + +private: + bool m_isBrush; + RenderMode m_renderMode; + QPoint m_topLeft; + kpImage m_image; + // == m_image.{width,height}() unless m_renderMode == UserFunction. + int m_width, m_height; + UserFunctionType m_userFunction; + void *m_userData; +}; + + +#endif // kpTempImage_H diff --git a/lgpl/CMakeLists.txt b/lgpl/CMakeLists.txt new file mode 100644 index 0000000..8789eba --- /dev/null +++ b/lgpl/CMakeLists.txt @@ -0,0 +1,36 @@ +# +# LGPL Library +# +# This MUST be a dynamic link library to avoid LGPL license infection. +# + +include(GenerateExportHeader) + +set(kolourpaint_lgpl_SRCS + generic/kpUrlFormatter.cpp + generic/kpColorCollection.cpp + generic/widgets/kpColorCellsBase.cpp +) + +add_library(kolourpaint_lgpl SHARED ${kolourpaint_lgpl_SRCS}) + +set(kolourpaint_lgpl_version 5) + +target_link_libraries(kolourpaint_lgpl + KF5::I18n + KF5::GuiAddons + KF5::WidgetsAddons + KF5::KIOCore + KF5::JobWidgets + Qt${QT_MAJOR_VERSION}::Widgets +) + +set_target_properties(kolourpaint_lgpl + PROPERTIES + VERSION ${kolourpaint_lgpl_version} + DEFINE_SYMBOL MAKE_KOLOURPAINT4_LGPL_LIB +) + +generate_export_header(kolourpaint_lgpl BASE_NAME kolourpaint_lgpl) + +install(TARGETS kolourpaint_lgpl ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/lgpl/generic/kpColorCollection.cpp b/lgpl/generic/kpColorCollection.cpp new file mode 100644 index 0000000..116cd03 --- /dev/null +++ b/lgpl/generic/kpColorCollection.cpp @@ -0,0 +1,518 @@ + +// REFACT0R: Remote open/save file logic is duplicated in kpDocument. +// HITODO: Test when remote file support in KDE 4 stabilizes + +/* This file is part of the KDE libraries + Copyright (C) 1999 Waldo Bastian (bastian@kde.org) + Copyright (C) 2007 Clarence Dang (dang@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +//----------------------------------------------------------------------------- +// KDE color collection + +#define DEBUG_KP_COLOR_COLLECTION 0 + +#include "kpColorCollection.h" + +#include "kpUrlFormatter.h" + +#include +#include +#include +#include +#include +#include "kpLogCategories.h" +#include + +#include +#include +#include +#include +#include +#include +#include + +struct ColorNode +{ + ColorNode(const QColor &c, const QString &n) + : color(c), name(n) {} + + QColor color; + QString name; +}; + +//--------------------------------------------------------------------- + +Q_LOGGING_CATEGORY(kpLogColorCollection, "kp.colorCollection") + +//BEGIN kpColorCollectionPrivate +class kpColorCollectionPrivate +{ +public: + kpColorCollectionPrivate(); + kpColorCollectionPrivate(const kpColorCollectionPrivate&); + + QList colorList; + QString name; + QString desc; + kpColorCollection::Editable editable; +}; + +kpColorCollectionPrivate::kpColorCollectionPrivate() + : editable(kpColorCollection::Yes) +{ +} + +kpColorCollectionPrivate::kpColorCollectionPrivate(const kpColorCollectionPrivate& p) + : colorList(p.colorList), name(p.name), desc(p.desc), editable(p.editable) +{ +} +//END kpColorCollectionPrivate + +//--------------------------------------------------------------------- + +QStringList +kpColorCollection::installedCollections() +{ + QStringList paletteList; + + QStringList paths = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("colors"), + QStandardPaths::LocateDirectory); + for (const auto &path : paths) { + paletteList.append(QDir(path).entryList(QStringList(), QDir::Files)); + } + + return paletteList; +} + +kpColorCollection::kpColorCollection() +{ + d = new kpColorCollectionPrivate(); +} + +kpColorCollection::kpColorCollection(const kpColorCollection &p) +{ + d = new kpColorCollectionPrivate(*p.d); +} + +kpColorCollection::~kpColorCollection() +{ + // Need auto-save? + delete d; +} + +static void CouldNotOpenDialog (const QUrl &url, QWidget *parent) +{ + KMessageBox::sorry (parent, + i18n ("Could not open color palette \"%1\".", + kpUrlFormatter::PrettyFilename (url))); +} + +// TODO: Set d->editable? +bool +kpColorCollection::open(const QUrl &url, QWidget *parent) +{ + if (url.isEmpty()) { + return false; + } + + KIO::StoredTransferJob *job = KIO::storedGet (url); + KJobWidgets::setWindow (job, parent); + + if (!job->exec ()) + { +#if DEBUG_KP_COLOR_COLLECTION + qCDebug(kpLogColorCollection) << "\tcould not download"; +#endif + ::CouldNotOpenDialog (url, parent); + return false; + } + + const QByteArray &data = job->data(); + QTextStream stream(data); + + // Read first line + // Expected "GIMP Palette" or "KDE RGB Palette" or "KDE RGBA Palette" + QString line = stream.readLine(); + if (line.indexOf(QLatin1String(" Palette")) == -1) + { + KMessageBox::sorry (parent, + i18n ("Could not open color palette \"%1\" - unsupported format.\n" + "The file may be corrupt.", + kpUrlFormatter::PrettyFilename (url))); + return false; + } + + bool hasAlpha = line == QLatin1String("KDE RGBA Palette"); // new format includes alpha + + QList newColorList; + QString newDesc; + + while( !stream.atEnd() ) + { + line = stream.readLine(); + if ( !line.isEmpty() && (line[0] == '#') ) + { + // This is a comment line + line = line.mid(1); // Strip '#' + line = line.trimmed(); // Strip remaining white space.. + if (!line.isEmpty()) + { + newDesc += line+'\n'; // Add comment to description + } + } + else + { + // This is a color line, hopefully + line = line.trimmed(); + if (line.isEmpty()) continue; + int r, g, b, a = 255; + int pos = 0; + bool ok = false; + + if ( hasAlpha ) + ok = (sscanf(line.toLatin1(), "%d %d %d %d%n", &r, &g, &b, &a, &pos) >= 4); + else + ok = (sscanf(line.toLatin1(), "%d %d %d%n", &r, &g, &b, &pos) >= 3); + + if ( ok ) + { + r = qBound(0, r, 255); + g = qBound(0, g, 255); + b = qBound(0, b, 255); + a = qBound(0, a, 255); + QString name = line.mid(pos).trimmed(); + newColorList.append(ColorNode(QColor(r, g, b, a), name)); + } + } + } + + d->colorList = newColorList; + d->name.clear (); + d->desc = newDesc; + + return true; +} + +static void CouldNotOpenKDEDialog (const QString &name, QWidget *parent) +{ + KMessageBox::sorry (parent, + i18n ("Could not open KDE color palette \"%1\".", name)); +} + +bool +kpColorCollection::openKDE(const QString &name, QWidget *parent) +{ +#if DEBUG_KP_COLOR_COLLECTION + qCDebug(kpLogColorCollection) << "name=" << name; +#endif + + if (name.isEmpty()) + { + #if DEBUG_KP_COLOR_COLLECTION + qCDebug(kpLogColorCollection) << "name.isEmpty"; + #endif + ::CouldNotOpenKDEDialog (name, parent); + return false; + } + + QString filename = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, + "colors/" + name); + if (filename.isEmpty()) + { + #if DEBUG_KP_COLOR_COLLECTION + qCDebug(kpLogColorCollection) << "could not find file"; + #endif + ::CouldNotOpenKDEDialog (name, parent); + return false; + } + + // (this will pop up an error dialog on failure) + if (!open (QUrl::fromLocalFile (filename), parent)) + { + #if DEBUG_KP_COLOR_COLLECTION + qCDebug(kpLogColorCollection) << "could not open"; + #endif + return false; + } + + d->name = name; +#if DEBUG_KP_COLOR_COLLECTION + qCDebug(kpLogColorCollection) << "opened"; +#endif + return true; +} + +static void CouldNotSaveDialog (const QUrl &url, QWidget *parent) +{ + // TODO: use file.errorString() + KMessageBox::error (parent, + i18n ("Could not save color palette as \"%1\".", + kpUrlFormatter::PrettyFilename (url))); +} + +static void SaveToFile (kpColorCollectionPrivate *d, QIODevice *device) +{ + // HITODO: QTextStream can fail but does not report errors. + // Bug in KColorCollection too. + QTextStream str (device); + + QString description = d->desc.trimmed(); + description = '#' + description.split('\n', Qt::KeepEmptyParts).join(QLatin1String("\n#")); + + str << "KDE RGBA Palette\n"; + str << description << "\n"; + for (const auto &node : d->colorList) + { + // Added for KolourPaint. + if ( !node.color.isValid() ) + continue; + + int r, g, b, a; + node.color.getRgb(&r, &g, &b, &a); + str << r << " " << g << " " << b << " " << a << " " << node.name << "\n"; + } + + str.flush(); +} + +bool +kpColorCollection::saveAs(const QUrl &url, QWidget *parent) const +{ + if (url.isLocalFile ()) + { + const QString filename = url.toLocalFile (); + + // sync: All failure exit paths _must_ call QSaveFile::cancelWriting() or + // else, the QSaveFile destructor will overwrite the file, + // , despite the failure. + QSaveFile atomicFileWriter (filename); + { + if (!atomicFileWriter.open (QIODevice::WriteOnly)) + { + // We probably don't need this as has not been + // opened. + atomicFileWriter.cancelWriting (); + + #if DEBUG_KP_COLOR_COLLECTION + qCDebug(kpLogColorCollection) << "\treturning false because could not open QSaveFile" + << " error=" << atomicFileWriter.error (); + #endif + ::CouldNotSaveDialog (url, parent); + return false; + } + + // Write to local temporary file. + ::SaveToFile (d, &atomicFileWriter); + + // Atomically overwrite local file with the temporary file + // we saved to. + if (!atomicFileWriter.commit ()) + { + atomicFileWriter.cancelWriting (); + + #if DEBUG_KP_COLOR_COLLECTION + qCDebug(kpLogColorCollection) << "\tcould not close QSaveFile"; + #endif + ::CouldNotSaveDialog (url, parent); + return false; + } + } // sync QSaveFile.cancelWriting() + } + // Remote file? + else + { + // Create temporary file that is deleted when the variable goes + // out of scope. + QTemporaryFile tempFile; + if (!tempFile.open ()) + { + #if DEBUG_KP_COLOR_COLLECTION + qCDebug(kpLogColorCollection) << "\treturning false because could not open tempFile"; + #endif + ::CouldNotSaveDialog (url, parent); + return false; + } + + // Write to local temporary file. + ::SaveToFile (d, &tempFile); + + // Collect name of temporary file now, as QTemporaryFile::fileName() + // stops working after close() is called. + const QString tempFileName = tempFile.fileName (); + #if DEBUG_KP_COLOR_COLLECTION + qCDebug(kpLogColorCollection) << "\ttempFileName='" << tempFileName << "'"; + #endif + Q_ASSERT (!tempFileName.isEmpty ()); + + tempFile.close (); + if (tempFile.error () != QFile::NoError) + { + #if DEBUG_KP_COLOR_COLLECTION + qCDebug(kpLogColorCollection) << "\treturning false because could not close"; + #endif + ::CouldNotSaveDialog (url, parent); + return false; + } + + // Copy local temporary file to overwrite remote. + KIO::FileCopyJob *job = KIO::file_copy (QUrl::fromLocalFile (tempFileName), + url, + -1, + KIO::Overwrite); + KJobWidgets::setWindow (job, parent); + if (!job->exec ()) + { + #if DEBUG_KP_COLOR_COLLECTION + qCDebug(kpLogColorCollection) << "\treturning false because could not upload"; + #endif + ::CouldNotSaveDialog (url, parent); + return false; + } + } + + d->name.clear (); + return true; +} + +QString kpColorCollection::description() const +{ + return d->desc; +} + +void kpColorCollection::setDescription(const QString &desc) +{ + d->desc = desc; +} + +QString kpColorCollection::name() const +{ + return d->name; +} + +void kpColorCollection::setName(const QString &name) +{ + d->name = name; +} + +kpColorCollection::Editable kpColorCollection::editable() const +{ + return d->editable; +} + +void kpColorCollection::setEditable(Editable editable) +{ + d->editable = editable; +} + +int kpColorCollection::count() const +{ + return (int) d->colorList.count(); +} + +void kpColorCollection::resize(int newCount) +{ + if (newCount == count()) + return; + else if (newCount < count()) + { + d->colorList.erase(d->colorList.begin() + newCount, d->colorList.end()); + } + else if (newCount > count()) + { + while(newCount > count()) + { + const int ret = addColor(QColor(), QString()/*color name*/); + Q_ASSERT(ret == count() - 1); + } + } +} + +kpColorCollection& +kpColorCollection::operator=( const kpColorCollection &p) +{ + if (&p == this) return *this; + d->colorList = p.d->colorList; + d->name = p.d->name; + d->desc = p.d->desc; + d->editable = p.d->editable; + return *this; +} + +QColor +kpColorCollection::color(int index) const +{ + if ((index < 0) || (index >= count())) + return {}; + + return d->colorList[index].color; +} + +int +kpColorCollection::findColor(const QColor &color) const +{ + for (int i = 0; i < d->colorList.size(); ++i) + { + if (d->colorList[i].color == color) + return i; + } + return -1; +} + +QString +kpColorCollection::name(int index) const +{ + if ((index < 0) || (index >= count())) + return {}; + + return d->colorList[index].name; +} + +QString kpColorCollection::name(const QColor &color) const +{ + return name(findColor(color)); +} + +int +kpColorCollection::addColor(const QColor &newColor, const QString &newColorName) +{ + d->colorList.append(ColorNode(newColor, newColorName)); + return count() - 1; +} + +int +kpColorCollection::changeColor(int index, + const QColor &newColor, + const QString &newColorName) +{ + if ((index < 0) || (index >= count())) + return -1; + + ColorNode& node = d->colorList[index]; + node.color = newColor; + node.name = newColorName; + + return index; +} + +int kpColorCollection::changeColor(const QColor &oldColor, + const QColor &newColor, + const QString &newColorName) +{ + return changeColor( findColor(oldColor), newColor, newColorName); +} + diff --git a/lgpl/generic/kpColorCollection.h b/lgpl/generic/kpColorCollection.h new file mode 100644 index 0000000..2455084 --- /dev/null +++ b/lgpl/generic/kpColorCollection.h @@ -0,0 +1,254 @@ + +// SYNC: Periodically merge in changes from: +// +// trunk/KDE/kdelibs/kdeui/colors/kcolorcollection.{h,cpp} +// +// which this is a fork of. +// +// Our changes can be merged back into KDE (grep for "Added for KolourPaint" and similar). + +/* This file is part of the KDE libraries + Copyright (C) 1999 Waldo Bastian (bastian@kde.org) + Copyright (C) 2007 Clarence Dang (dang@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; version + 2 of the License. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +//----------------------------------------------------------------------------- +// KDE color collection. + +#ifndef kpColorCollection_H +#define kpColorCollection_H + +#include + +#include +#include +#include +#include +#include + +class QUrl; + +/** + * Class for handling color collections ("palettes"). + * + * This class makes it easy to handle color collections, sometimes referred to + * as "palettes". This class can read and write collections from and to a file. + * + * Collections that are managed by KDE have a non-empty name(). Collections + * stored in regular files have an empty name(). + * + * This class uses the "GIMP" palette file format. + * + * @author Waldo Bastian (bastian@kde.org), Clarence Dang (dang@kde.org) + **/ +class KOLOURPAINT_LGPL_EXPORT kpColorCollection +{ +public: + /** + * Query which KDE color collections are installed. + * + * @return A list with installed color collection names. + */ + static QStringList installedCollections(); + + /** + * kpColorCollection constructor + * + * argument removed for KolourPaint. + * Use openKDE() instead, which also has error handling. + **/ + explicit kpColorCollection(); + + /** + * kpColorCollection copy constructor. + **/ + kpColorCollection(const kpColorCollection &); + + /** + * kpColorCollection destructor. + **/ + ~kpColorCollection(); + + /** + * kpColorCollection assignment operator + **/ + kpColorCollection& operator=( const kpColorCollection &); + + // On failure, this prints an error dialog and returns false. + // On success, it sets the name() to an empty string and returns true. + // + // Added for KolourPaint. + bool open(const QUrl &url, QWidget *parent); + + // Same as open() but is given the name of a KDE palette, not a filename. + // + // @param name The name of collection as returned by installedCollections(). + // name() is set to this. + // + // Added for KolourPaint. + bool openKDE(const QString &name, QWidget *parent); + + // On failure, this prints an error dialog and returns false. + // If the user cancels any presented overwrite dialog, it also returns false. + // On success, it returns true. + // + // The file can be overwritten without displaying any warning dialog, if + // is set to false. + // + // name() is set to an empty string. + // + // Added for KolourPaint. + bool saveAs(const QUrl &url, QWidget *parent) const; + + /** + * Get the description of the collection. + * @return the description of the collection. + **/ + QString description() const; + + /** + * Set the description of the collection. + * @param desc the new description + **/ + void setDescription(const QString &desc); + + /** + * Get the name of the collection. + * @return the name of the collection + **/ + QString name() const; + + /** + * Set the name of the collection. + * @param name the name of the collection + **/ + void setName(const QString &name); + + /** + * Used to specify whether a collection may be edited. + * @see editable() + * @see setEditable() + */ + enum Editable { Yes, ///< Collection may be edited + No, ///< Collection may not be edited + Ask ///< Ask user before editing + }; + + /** + * Returns whether the collection may be edited. + * @return the state of the collection + **/ + Editable editable() const; + + /** + * Change whether the collection may be edited. + * @param editable the state of the collection + **/ + void setEditable(Editable editable); + + /** + * Return the number of colors in the collection. + * @return the number of colors + **/ + int count() const; + + /** + * Adds invalid colors or removes colors so that there will be @p newCount + * colors in the color collection. + * + * @param target number of colors + * + * Added for KolourPaint. + */ + void resize(int newCount); + + /** + * Find color by index. + * @param index the index of the desired color + * @return The @p index -th color of the collection, null if not found. + **/ + QColor color(int index) const; + + /** + * Find index by @p color. + * @param color the color to find + * @return The index of the color in the collection or -1 if the + * color is not found. + **/ + int findColor(const QColor &color) const; + + /** + * Find color name by @p index. + * @param index the index of the color + * @return The name of the @p index -th color. + * Note that not all collections have named the colors. Null is + * returned if the color does not exist or has no name. + **/ + QString name(int index) const; + + /** + * Find color name by @p color. + * @return The name of color according to this collection. + * Note that not all collections have named the colors. + * Note also that each collection can give the same color + * a different name. + **/ + QString name(const QColor &color) const; + + /** + * Add a color. + * @param newColor The color to add. + * @param newColorName The name of the color, null to remove + * the name. + * @return The index of the added color. + **/ + int addColor(const QColor &newColor, + const QString &newColorName = QString()); + + /** + * Change a color. + * @param index Index of the color to change + * @param newColor The new color. + * @param newColorName The new color name, null to remove + * the name. + * @return The index of the new color or -1 if the color couldn't + * be changed. + **/ + int changeColor(int index, + const QColor &newColor, + const QString &newColorName = QString()); + + /** + * Change a color. + * @param oldColor The original color + * @param newColor The new color. + * @param newColorName The new color name, null to remove + * the name. + * @return The index of the new color or -1 if the color couldn't + * be changed. + **/ + int changeColor(const QColor &oldColor, + const QColor &newColor, + const QString &newColorName = QString()); + +private: + class kpColorCollectionPrivate *d; +}; + + +#endif // kpColorCollection_H + diff --git a/lgpl/generic/kpUrlFormatter.cpp b/lgpl/generic/kpUrlFormatter.cpp new file mode 100644 index 0000000..1ee98c4 --- /dev/null +++ b/lgpl/generic/kpUrlFormatter.cpp @@ -0,0 +1,65 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "kpUrlFormatter.h" + +#include +#include + +//--------------------------------------------------------------------- + +// public static +QString kpUrlFormatter::PrettyUrl (const QUrl &url) +{ + if (url.isEmpty ()) + { + return i18n ("Untitled"); + } + + return url.url (QUrl::PreferLocalFile); +} + +//--------------------------------------------------------------------- + +// public static +QString kpUrlFormatter::PrettyFilename (const QUrl &url) +{ + if (url.isEmpty ()) + { + return i18n ("Untitled"); + } + + if (url.fileName ().isEmpty ()) + { + return kpUrlFormatter::PrettyUrl (url); // better than the name "" + } + + return url.fileName (); +} + +//--------------------------------------------------------------------- diff --git a/lgpl/generic/kpUrlFormatter.h b/lgpl/generic/kpUrlFormatter.h new file mode 100644 index 0000000..5465450 --- /dev/null +++ b/lgpl/generic/kpUrlFormatter.h @@ -0,0 +1,57 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +// +// This class is in the lpgl/ folder because other code in the folder needs +// to access it. But it is not under the LPGL. +// + + +#ifndef kpUrlFormatter_H +#define kpUrlFormatter_H + +#include + + +class QString; + +class QUrl; + + +class KOLOURPAINT_LGPL_EXPORT kpUrlFormatter +{ +public: + // (will convert: empty Url --> "Untitled") + static QString PrettyUrl (const QUrl &url); + + // (will convert: empty Url --> "Untitled") + static QString PrettyFilename (const QUrl &url); +}; + + +#endif // kpUrlFormatter_H diff --git a/lgpl/generic/widgets/kpColorCellsBase.cpp b/lgpl/generic/widgets/kpColorCellsBase.cpp new file mode 100644 index 0000000..c9c041c --- /dev/null +++ b/lgpl/generic/widgets/kpColorCellsBase.cpp @@ -0,0 +1,563 @@ + +/* This file is part of the KDE libraries + Copyright (C) 1997 Martin Jones (mjones@kde.org) + Copyright (C) 2007 Roberto Raggi (roberto@kdevelop.org) + Copyright (C) 2007 Clarence Dang (dang@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +//----------------------------------------------------------------------------- + +#define DEBUG_KP_COLOR_CELLS_BASE 0 + +#include "kpColorCellsBase.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "kpLogCategories.h" + +#include + + +class kpColorCellsBase::kpColorCellsBasePrivate +{ +public: + kpColorCellsBasePrivate(kpColorCellsBase *q): q(q) + { + colors = nullptr; + inMouse = false; + selected = -1; + shade = false; + acceptDrags = false; + cellsResizable = true; + } + + kpColorCellsBase *q; + + // Note: This is a good thing and is _not_ data duplication with the + // colors of QTableWidget cells, for the following reasons: + // + // 1. QColor in Qt4 is full-quality RGB. However, QTableWidget + // cells are lossy as their colors may be dithered on the screen. + // + // Eventually, this field will be changed to a kpColor. + // + // 2. We change the QTableWidget cells' colors when the widget is + // disabled (see changeEvent()). + // + // Therefore, do not remove this field without better reasons. + QColor *colors; + + QPoint mousePos; + int selected; + bool shade; + bool acceptDrags; + bool cellsResizable; + bool inMouse; +}; + +kpColorCellsBase::kpColorCellsBase( QWidget *parent, int rows, int cols ) + : QTableWidget( parent ), d(new kpColorCellsBasePrivate(this)) +{ + setItemDelegate(new QItemDelegate(this)); + + setFrameShape(QFrame::NoFrame); + d->shade = true; + setRowCount( rows ); + setColumnCount( cols ); + + verticalHeader()->setMinimumSectionSize(16); + verticalHeader()->hide(); + horizontalHeader()->setMinimumSectionSize(16); + horizontalHeader()->hide(); + + d->colors = new QColor [ rows * cols ]; + + d->selected = 0; + d->inMouse = false; + + // Drag'n'Drop + setAcceptDrops( true); + + setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); + setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); + viewport()->setBackgroundRole( QPalette::Window ); + setBackgroundRole( QPalette::Window ); +} + +kpColorCellsBase::~kpColorCellsBase() +{ + delete [] d->colors; + + delete d; +} + +void kpColorCellsBase::invalidateAllColors () +{ + for (int r = 0; r < rowCount (); r++) + for (int c = 0; c < columnCount (); c++) + d->colors [r * columnCount () + c] = QColor (); +} + +void kpColorCellsBase::clear() +{ + invalidateAllColors (); + QTableWidget::clear (); +} + +void kpColorCellsBase::clearContents() +{ + invalidateAllColors (); + QTableWidget::clearContents (); +} + +void kpColorCellsBase::setRowColumnCounts (int rows, int columns) +{ + const int oldRows = rowCount (), oldCols = columnCount (); + const int newRows = rows, newCols = columns; +#if DEBUG_KP_COLOR_CELLS_BASE + qCDebug(kpLogColorCollection) << "oldRows=" << oldRows << "oldCols=" << oldCols + << "newRows=" << newRows << "newCols=" << newCols; +#endif + + if (oldRows == newRows && oldCols == newCols) + return; + + QTableWidget::setColumnCount (newCols); + QTableWidget::setRowCount (newRows); + + QColor *oldColors = d->colors; + d->colors = new QColor [newRows * newCols]; + + for (int r = 0; r < qMin (oldRows, newRows); r++) + for (int c = 0; c < qMin (oldCols, newCols); c++) + d->colors [r * newCols + c] = oldColors [r * oldCols + c]; + + delete [] oldColors; +} + +void kpColorCellsBase::setColumnCount (int newColumns) +{ + setRowColumnCounts (rowCount (), newColumns); +} + +void kpColorCellsBase::setRowCount (int newRows) +{ + setRowColumnCounts (newRows, columnCount ()); +} + +QColor kpColorCellsBase::color(int index) const +{ + return d->colors[index]; +} + +int kpColorCellsBase::count() const +{ + return rowCount() * columnCount(); +} + +void kpColorCellsBase::setShading(bool _shade) +{ + d->shade = _shade; +} + +void kpColorCellsBase::setAcceptDrags(bool _acceptDrags) +{ + d->acceptDrags = _acceptDrags; +} + +void kpColorCellsBase::setCellsResizable(bool yes) +{ + d->cellsResizable = yes; +} + +void kpColorCellsBase::setSelected(int index) +{ + Q_ASSERT( index >= 0 && index < count() ); + + d->selected = index; +} + +int kpColorCellsBase::selectedIndex() const +{ + return d->selected; +} + +//--------------------------------------------------------------------- + +static void TableWidgetItemSetColor (QTableWidgetItem *tableItem, const QColor &color) +{ + Q_ASSERT (tableItem); + QImage image(16, 16, QImage::Format_ARGB32_Premultiplied); + QPainter painter(&image); + const int StippleSize = 4; + QColor useColor; + + for (int dy = 0; dy < 16; dy += StippleSize) + { + for (int dx = 0; dx < 16; dx += StippleSize) + { + const bool parity = ((dy + dx) / StippleSize) % 2; + + if (!parity) + useColor = Qt::white; + else + useColor = Qt::lightGray; + + painter.fillRect(dx, dy, StippleSize, StippleSize, useColor); + } + } + + painter.fillRect(image.rect(), color); + painter.end(); + + tableItem->setData(Qt::BackgroundRole , QBrush(image)); +} + +//--------------------------------------------------------------------- + +void kpColorCellsBase::setColor( int column, const QColor &colorIn ) +{ + const int tableRow = column / columnCount(); + const int tableColumn = column % columnCount(); + + Q_ASSERT( tableRow >= 0 && tableRow < rowCount() ); + Q_ASSERT( tableColumn >= 0 && tableColumn < columnCount() ); + + QColor color = colorIn; + + d->colors[column] = color; + + QTableWidgetItem* tableItem = item(tableRow,tableColumn); + + if (color.isValid ()) + { + if ( tableItem == nullptr ) { + tableItem = new QTableWidgetItem(); + setItem(tableRow,tableColumn,tableItem); + } + + if (isEnabled ()) + ::TableWidgetItemSetColor (tableItem, color); + } + else + { + delete tableItem; + } + + emit colorChanged (column, color); +} + +void kpColorCellsBase::changeEvent( QEvent* event ) +{ + QTableWidget::changeEvent (event); + + if (event->type () != QEvent::EnabledChange) + return; + + for (int r = 0; r < rowCount (); r++) + { + for (int c = 0; c < columnCount (); c++) + { + const int index = r * columnCount () + c; + + QTableWidgetItem* tableItem = item(r, c); + + // See API Doc for this invariant. + Q_ASSERT (!!tableItem == d->colors [index].isValid ()); + + if (!tableItem) + continue; + + + QColor color; + if (isEnabled ()) + color = d->colors [index]; + else + color = palette ().color (backgroundRole ()); + + ::TableWidgetItemSetColor (tableItem, color); + } + } +} + +/*void kpColorCellsBase::paintCell( QPainter *painter, int row, int col ) +{ + painter->setRenderHint( QPainter::Antialiasing , true ); + + QBrush brush; + int w = 1; + + if (shade) + { + qDrawShadePanel( painter, 1, 1, cellWidth()-2, + cellHeight()-2, palette(), true, 1, &brush ); + w = 2; + } + QColor color = colors[ row * numCols() + col ]; + if (!color.isValid()) + { + if (!shade) return; + color = palette().color(backgroundRole()); + } + + const QRect colorRect( w, w, cellWidth()-w*2, cellHeight()-w*2 ); + painter->fillRect( colorRect, color ); + + if ( row * numCols() + col == selected ) { + painter->setPen( qGray(color.rgb())>=127 ? Qt::black : Qt::white ); + painter->drawLine( colorRect.topLeft(), colorRect.bottomRight() ); + painter->drawLine( colorRect.topRight(), colorRect.bottomLeft() ); + } +}*/ + +void kpColorCellsBase::resizeEvent( QResizeEvent* e ) +{ + if (d->cellsResizable) + { + // According to the Qt doc: + // If you need to set the width of a given column to a fixed value, call + // QHeaderView::resizeSection() on the table's {horizontal,vertical} + // header. + // Therefore we iterate over each row and column and set the header section + // size, as the sizeHint does indeed appear to be ignored in favor of a + // minimum size that is larger than what we want. + for ( int index = 0 ; index < columnCount() ; index++ ) + horizontalHeader()->resizeSection( index, sizeHintForColumn(index) ); + for ( int index = 0 ; index < rowCount() ; index++ ) + verticalHeader()->resizeSection( index, sizeHintForRow(index) ); + } + else + { + // Update scrollbars if they're forced on by a subclass. + // TODO: Should the d->cellsResizable path (from kdelibs) do this as well? + QTableWidget::resizeEvent (e); + } +} + +int kpColorCellsBase::sizeHintForColumn(int /*column*/) const +{ + // TODO: Should it be "(width() - frameWidth() * 2) / columnCount()"? + return width() / columnCount() ; +} + +int kpColorCellsBase::sizeHintForRow(int /*row*/) const +{ + // TODO: Should be "(height() - frameWidth() * 2) / rowCount()"? + return height() / rowCount() ; +} + +void kpColorCellsBase::mousePressEvent( QMouseEvent *e ) +{ + d->inMouse = true; + d->mousePos = e->pos(); +} + + +int kpColorCellsBase::positionToCell(const QPoint &pos, bool ignoreBorders, + bool allowEmptyCell) const +{ + //TODO ignoreBorders not yet handled + Q_UNUSED( ignoreBorders ) + + const int r = indexAt (pos).row (), c = indexAt (pos).column (); +#if DEBUG_KP_COLOR_CELLS_BASE + qCDebug(kpLogColorCollection) << "r=" << r << "c=" << c; +#endif + + if (r == -1 || c == -1) + return -1; + + if (!allowEmptyCell && !itemAt(pos)) + return -1; + + const int cell = r * columnCount() + c; + + /*if (!ignoreBorders) + { + int border = 2; + int x = pos.x() - col * cellWidth(); + int y = pos.y() - row * cellHeight(); + if ( (x < border) || (x > cellWidth()-border) || + (y < border) || (y > cellHeight()-border)) + return -1; + }*/ + + return cell; +} + + +void kpColorCellsBase::mouseMoveEvent( QMouseEvent *e ) +{ + if( !(e->buttons() & Qt::LeftButton)) return; + + if(d->inMouse) { + int delay = QApplication::startDragDistance(); + if(e->x() > d->mousePos.x()+delay || e->x() < d->mousePos.x()-delay || + e->y() > d->mousePos.y()+delay || e->y() < d->mousePos.y()-delay){ + // Drag color object + int cell = positionToCell(d->mousePos); + if (cell != -1) + { + #if DEBUG_KP_COLOR_CELLS_BASE + qCDebug(kpLogColorCollection) << "beginning drag from cell=" << cell + << "color: isValid=" << d->colors [cell].isValid () + << " rgba=" << (int *) d->colors [cell].rgba(); + #endif + Q_ASSERT (d->colors[cell].isValid()); + KColorMimeData::createDrag(d->colors[cell], this)->exec(Qt::CopyAction | Qt::MoveAction); + #if DEBUG_KP_COLOR_CELLS_BASE + qCDebug(kpLogColorCollection) << "finished drag"; + #endif + } + } + } +} + + +// LOTODO: I'm not quite clear on how the drop actions logic is supposed +// to be done e.g.: +// +// 1. Who is supposed to call setDropAction(). +// 2. Which variant of accept(), setAccepted(), acceptProposedAction() etc. +// is supposed to be called to accept a move -- rather than copy -- +// action. +// +// Nevertheless, it appears to work -- probably because we restrict +// the non-Qt-default move/swap action to be intrawidget. +static void SetDropAction (QWidget *self, QDropEvent *event) +{ + // TODO: Would be nice to default to CopyAction if the destination cell + // is null. + if (event->source () == self && (event->keyboardModifiers () & Qt::ControlModifier) == 0) + event->setDropAction(Qt::MoveAction); + else + event->setDropAction(Qt::CopyAction); +} + +void kpColorCellsBase::dragEnterEvent( QDragEnterEvent *event) +{ +#if DEBUG_KP_COLOR_CELLS_BASE + qCDebug(kpLogColorCollection) << "kpColorCellsBase::dragEnterEvent() acceptDrags=" + << d->acceptDrags + << " canDecode=" << KColorMimeData::canDecode(event->mimeData()); +#endif + event->setAccepted( d->acceptDrags && KColorMimeData::canDecode( event->mimeData())); + if (event->isAccepted ()) + ::SetDropAction (this, event); +} + +// Reimplemented to override QTableWidget's override. Else dropping doesn't work. +void kpColorCellsBase::dragMoveEvent (QDragMoveEvent *event) +{ +#if DEBUG_KP_COLOR_CELLS_BASE + qCDebug(kpLogColorCollection) << "kpColorCellsBase::dragMoveEvent() acceptDrags=" + << d->acceptDrags + << " canDecode=" << KColorMimeData::canDecode(event->mimeData()); +#endif + // TODO: Disallow drag that isn't onto a cell. + event->setAccepted( d->acceptDrags && KColorMimeData::canDecode( event->mimeData())); + if (event->isAccepted ()) + ::SetDropAction (this, event); +} + +void kpColorCellsBase::dropEvent( QDropEvent *event) +{ + QColor c=KColorMimeData::fromMimeData(event->mimeData()); + + const int dragSourceCell = event->source () == this ? + positionToCell (d->mousePos, true) : + -1; +#if DEBUG_KP_COLOR_CELLS_BASE + qCDebug(kpLogColorCollection) << "kpColorCellsBase::dropEvent()" + << "color: rgba=" << (const int *) c.rgba () << "isValid=" << c.isValid() + << "source=" << event->source () << "dragSourceCell=" << dragSourceCell; +#endif + if( c.isValid()) { + ::SetDropAction (this, event); + + int cell = positionToCell(event->pos(), true, true/*allow empty cell*/); + #if DEBUG_KP_COLOR_CELLS_BASE + qCDebug(kpLogColorCollection) << "\tcell=" << cell; + #endif + // TODO: I believe kdelibs forgets to do this. + if (cell == -1) + return; + + // Avoid NOP. + if (cell == dragSourceCell) + return; + + QColor destOldColor = d->colors [cell]; + setColor(cell,c); + + #if DEBUG_KP_COLOR_CELLS_BASE + qCDebug(kpLogColorCollection) << "\tdropAction=" << event->dropAction () + << "destOldColor.rgba=" << (const int *) destOldColor.rgba (); + #endif + if (event->dropAction () == Qt::MoveAction && dragSourceCell != -1) { + setColor(dragSourceCell, destOldColor); + } + } +} + +void kpColorCellsBase::mouseReleaseEvent( QMouseEvent *e ) +{ + int cell = positionToCell(d->mousePos); + int currentCell = positionToCell(e->pos()); + + // If we release the mouse in another cell and we don't have + // a drag we should ignore this event. + if (currentCell != cell) + cell = -1; + + if ( (cell != -1) && (d->selected != cell) ) + { + d->selected = cell; + + const int newRow = cell/columnCount(); + const int newColumn = cell%columnCount(); + + clearSelection(); // we do not want old violet selected cells + + item(newRow,newColumn)->setSelected(true); + } + + d->inMouse = false; + if (cell != -1) + { + emit colorSelected( cell , color(cell) ); + emit colorSelectedWhitButton( cell , color(cell), e->button() ); + } +} + +void kpColorCellsBase::mouseDoubleClickEvent( QMouseEvent * /*e*/ ) +{ + int cell = positionToCell(d->mousePos, false, true/*allow empty cell*/); + + if (cell != -1) + emit colorDoubleClicked( cell , color(cell) ); +} + + diff --git a/lgpl/generic/widgets/kpColorCellsBase.h b/lgpl/generic/widgets/kpColorCellsBase.h new file mode 100644 index 0000000..e05b5d6 --- /dev/null +++ b/lgpl/generic/widgets/kpColorCellsBase.h @@ -0,0 +1,188 @@ + +// SYNC: Periodically merge in changes from: +// +// trunk/KDE/kdelibs/kdeui/colors/kcolordialog.{h,cpp} +// +// which this is a fork of. +// +// Our changes can be merged back into KDE (grep for "Added for KolourPaint" and similar). + +/* This file is part of the KDE libraries + Copyright (C) 1997 Martin Jones (mjones@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +//---------------------------------------------------------------------- +// KDE color selection dialog. + +// layout management added Oct 1997 by Mario Weilguni +// + +#ifndef kpColorCellsBase_H +#define kpColorCellsBase_H + +#include + +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(kpLogColorCollection) + +/** +* A table of editable color cells. +* +* @author Martin Jones +* +* Added for KolourPaint: +* +* If you have not called setColor() for a cell, its widget will not exist. +* So it is possible to have "holes" in this rectangular table of cells. +* You can delete a cell widget by calling setColor() with an invalid QColor. +* +* An invariant is that color() returns an invalid color iff the cells' widget +* does not exist. Note that: +* +* 1. You can double click on cells that don't contain a widget +* 2. You can drop onto -- but not drag from -- a cell that doesn't contain a +* widget +* +* If a color is dragged and dropped to-and-from the same instance of this +* widget, then the colors in the source and destination cells are swapped +* (this is a "move action"). +* +* If CTRL is held or they are not from the same instance, then the source +* cell's color is copied into the destination cell, without any change to +* the source cell (this is a "copy action"). +*/ +class KOLOURPAINT_LGPL_EXPORT kpColorCellsBase : public QTableWidget +{ + Q_OBJECT +public: + /** + * Constructs a new table of color cells, consisting of + * @p rows * @p columns colors. + * + * @param parent The parent of the new widget + * @param rows The number of rows in the table + * @param columns The number of columns in the table + * + * Specifying and was made optional for KolourPaint. + */ + kpColorCellsBase( QWidget *parent, int rows = 0, int columns = 0 ); + ~kpColorCellsBase() override; + +private: + /** Added for KolourPaint. */ + void invalidateAllColors (); + +public: + /** Added for KolourPaint. + WARNING: These are not virtual in QTableWidget. + */ + void clear (); + void clearContents (); + + /** Added for KolourPaint. */ + void setRowColumnCounts (int rows, int columns); + + /** Added for KolourPaint. + WARNING: These are not virtual in QTableWidget. + */ + void setColumnCount (int columns); + void setRowCount (int rows); + + /** Sets the color in the given index in the table. + + The following behavior change was added for KolourPaint: + + If is not valid, the cell widget at is deleted. + */ + void setColor( int index, const QColor &col ); + /** Returns the color at a given index in the table. + If a cell widget does not exist at , the invalid color is + returned. + */ + QColor color( int index ) const; + /** Returns the total number of color cells in the table */ + int count() const; + + void setShading(bool shade); + void setAcceptDrags(bool acceptDrags); + + /** Whether component cells should resize with the entire widget. + Default is true. + + Added for KolourPaint. + */ + void setCellsResizable(bool yes); + + /** Sets the currently selected cell to @p index */ + void setSelected(int index); + /** Returns the index of the cell which is currently selected */ + int selectedIndex() const; + +Q_SIGNALS: + /** Emitted when a color is selected in the table */ + void colorSelected( int index , const QColor& color ); + /** Emitted with the above. + + Added for KolourPaint. + */ + void colorSelectedWhitButton( int index , const QColor& color, Qt::MouseButton button ); + + /** Emitted when a color in the table is double-clicked */ + void colorDoubleClicked( int index , const QColor& color ); + + /** Emitted when setColor() is called. + This includes when a color is dropped onto the table, via drag-and-drop. + + Added for KolourPaint. + */ + void colorChanged( int index , const QColor& color ); + +protected: + /** Grays out the cells, when the object is disabled. + Added for KolourPaint. + */ + void changeEvent( QEvent* event ) override; + + // the three methods below are used to ensure equal column widths and row heights + // for all cells and to update the widths/heights when the widget is resized + int sizeHintForColumn(int column) const override; + int sizeHintForRow(int column) const override; + void resizeEvent( QResizeEvent* event ) override; + + void mouseReleaseEvent( QMouseEvent * ) override; + void mousePressEvent( QMouseEvent * ) override; + void mouseMoveEvent( QMouseEvent * ) override; + void dragEnterEvent( QDragEnterEvent * ) override; + void dragMoveEvent( QDragMoveEvent * ) override; + void dropEvent( QDropEvent *) override; + void mouseDoubleClickEvent( QMouseEvent * ) override; + + /** was added for KolourPaint. */ + int positionToCell(const QPoint &pos, bool ignoreBorders=false, + bool allowEmptyCell=false) const; + +private: + class kpColorCellsBasePrivate; + friend class kpColorCellsBasePrivate; + kpColorCellsBasePrivate *const d; + + Q_DISABLE_COPY(kpColorCellsBase) +}; + +#endif // kpColorCellsBase_H diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ba290c422f623bf5fa32bc98a8f9446f19c8ae2b GIT binary patch literal 7198 zcmZu$Wl$7=vp?|YRHRF~LF(uRk?t;$IF9a+Mj8$fkS=LyI5@hbyOeGakd!?7{{G+l z_U7%(=Im^I+L`^uXsRpXVN+lO002Ct_ww5RbjW`h6aAm;le8TBr+^-^O1hZ;C;-zs z`k#;G`rgO`0AR8BFQZULw%Pv+l6xu`dg{2?diq$p+W>rge7GUb4jxvPt~T5*?%?cG zaS8x{GFwSrM%Q=wWDUbG#V-%T)!7AcO6B7MK6CcT&s|wtY0q7v28M;;q4o2zC%xgi zGDiik?Ll|7-rt;ORCAZ-(_Qbq#f5 zsDZfT#$}S$0Ob=h!?Pk~6ZiPNRL$xRpP)c0|+Dc-d2GBtwlN=u&UTf_Hv;FbY??3Fn20tS-ca zrYLj)h46?j`7YpVu$E`;Pgxt7)VP5qsB7)k0QYBU@HC*x3;Y2b&hEtV@d0h~tCdy& z*F<95-KCzPXz_>vy8W{qrgUeS=yK67yRihyw}4a@URkwC#pM7ie2DARhY4Cd6ua$; zodRhn+_bj<#kUE>iZec)_g70`ebeJ}aBk&~3aX0pA|?QrxEGa*W)YVKs|uk1=KPK# zA9_h(Pol~=_iSsFEk%J^$0SX?QCu+tS7!78NUIYEMeRJ` z#_c&wfRUeiRm+?7TqQBW$g8JPB(?(|HtD4K~>zVwRH5Tu1hJW+CH!;R~S zpw=cRQbljHMRTgBAlv5RYGD>78wo?^DY3TuJ&#Q`eFbaO%o{S{TIlaf;$O9|qNQ#mgz>u7+Q!7~? zyXDQ_eun!UL&|gw=Z;bbX9dwKL^-9@sELY?&ROIsT1ioXq^~ zZKO>0_nmkeH-O=sMR{H61oab2#(@1B6zKs+?)k|Za`*|q-Zbw{$^w-@y-x}4Rw6#m z@2-_%;j{0NEA=o)duDYkWeF0oZ11jLXO5O`!n~urR{SqvF%^V0ARB3D#r^_p!0RfY zh*!Bop1gu#wI@J~#030q7?nLil6C5I=Y8RW1o04u_)7TyobaZEQ-8Afn19N`v!wcC zeHnOcmVK*jzIo*IqGecbz$}#8srmA!D{LEQ8N_2C14$OuXQ|vNV;3ygE3vx6JwK7- z^*AL0e0=wt)%=!xNN1uS zkp86Ws!=Srazft_S$-$xWHOAaomQG~=owjjm+CR_KXrWq#Q-1oiueI-RS5vVbv9Y&XeHAJMxHANbvv>|UL+Ocb8n*n6#37?e7h zgi^4lRmSDMQ9EOr2sRy%_5jJbKYZX^Ee7`Z-j`_k*}u;UdYAxcc3d0UW%j*ZafCp* zCh-@o?butLSRZho!-}e6`gvxwn54xZ;x@qzTnSkL_lZu{B$zo0&ZL$WAW}s~Bx~!F zat3yr4tUnP*`3c>===1%abaWyl~+ubDIaCW`K2bOc=06Q^_Cs3^wbKA}nhF2?c$CKE7ARd3mJOpu99)GYjhGFm^7A)rAO8@KQa~G$N`wGuMbDU97Y{G@% z$W3QNudupv5!@^;BOC*=+_+Xf(|@9dyX>v|wysn<7X;rALAh0$uscLTYZMB&D<4h( zn)IaP->L=D1SGri;e||#y|AJA+N*l61ZSxzHDq)OJpZGMR?@DIy6ZFPhM`)j@LtjC znH#|nH=#@W%j)>3On$p|gR%%Q6P7tR%$Cnyqx(U3K6yD*hPy9S~{2-yOs1@zIl6%{|Q+E8lam^(!w%xyU8eXRK z;<_mX0=u9V0Q5J8&3NQ)$TBcMPzrw9Dt;7#jQ_=&Cg!K!?~oi2IosG2wonXMSwV<> z?HjmWT=K0mB~5GqZ&}vRoTk4WIQ>etrC)DJ>k~^_*e*?=aZ!48?z%$n>62CXAS{vF zZfiNVKw$@&k=D7iZY|lfr=wTEk->uX5{-Y3DSH_&@-E}7V>2>qpbB3PuD;@#w?wO` zhG{z~n0(Lo!O|*f?Q7V}IsKPHn{e8NJ-`u%g*`qRNNl*Oc?4A>u5=3$nzoqo+4Rwq zEfn7QuwUr5(D>3S$vW&0FNq5;X?zxd)3jy!y;q3Q8_+mqJ3H{~=Uomr&-j3(lOb{e>W%= zsqU%IRD_t=(*X^kRCl@mae@!PwDIc8qB^38L#U(FwCcf=(SLdP2jdMfzLyRxQ$n)< zbxlh|rBgPNsU)b8G&p17&x=MhLfdUevG<_-1_j_TZkoy&vtkFU0jd8dUtg5PfMI-v z6P$P9!6hOP_?Yye;xD$XLIuv;k>Vu}lFXi!Ibu-t&0NaLN@hX~iYEpQ1&K znwJgj@#ZBOd4?})Q{mO5$1wsa?&Gxb$it{@>KV2tmr=i?_;zrS+mVizS$6HpW#>(9 zp%wgT;Yd3XqSl(p=Hiu#uorLU>1sYN_xBghI%2$lfu!lD&`sj3Hjj^4u~1f)@vAGX zqMS4`lR9n;-sAOrK2Zq`WNUEPPZ{Lj#qQY|V`~WxCOQ=tiF{Zy8JSW_cDiN}Px{p3 z-p|x%V(A^m1L;6I1R#HC-LYBfr#h5(LoHq6qeQaY`|5`Km8k%vq%5;N8J1#tU%8v^ zQlr}WLSKwoH~(sy8iF?fH`n`eTdud0``4nJlAQ$2)}y7Yk?Lfe2nIERpgBFJ_*n-G zep#wjRHw|&Ep_Wd3ySEUWEPGbF}*jlTkYB@bKQ4%@(#SNtOk;J9>4K>a#_p*QKkJ{ zE0sH{FBl1U6-^@}V7-)ZJ#j;W4r@8G-@q?T(%}O6iOV?e+>1;}-U(-mY1ed8EIHlGrQK66DDfH-RvM|bU0l44U9Y>=%&2eHFatM>6hGnLc4dtGzRfH1y@_i)KFg@)tNwe^3^UUo$m#mY^0C(cG5$SvL+$V*`GBPyGp^V--yY zG}pK;laLI_nPku>VptvWMDu;lb(E!s`unD#565_?hyfHUhrh3UcVgIYua<_zFak6Z z?}y1kd*}n`aSvNlnpQ)GST%eU{Cm2 zE^3pprOe^&;%=(03>SvgjmT1kC@RWXWA}Vs5q!$hF!Cv(O54D;%eF|4%>2RGdJvs< z#0qWNzWf@Lb_~`zQ?w0p1rt?`;r7@z04PSgmUTB*Q8LBvY)Mb4?0c^R@mk9N+8?8eS9U0mRI~~)-=Bo#t$;KIO?=1D!;yT9KwTJNtCG*$L(~!Phmh;m_YSO zbjFyZO|L*iS+%To(VYBJl9Qa5_O4ay&GZl}2KlmnMRY}O>f<@t>M<4fqP-xh2`iO* zqkBc7vTm7MY(-TikmjIJe3`DYs(KcyLHzmn{JP?=%n3W`)>bn{Xk6KS%f_$JPC&X7)v&WJ}S*!YEb9;Ay9!n?JwP z{IBZ_CgZuOt7%8~VoDXf5UiE^s#)KPl4oxk8TdG<7I&$g>C1SvUCu4Jou0(3i-CYhE2goI!dFTu+pm*00xoCFlBMVJF?#^;VK`-uwfR#~&JZBZ4%A z{3pj=hfEM86zr-~I)UzpGrmqQMwRM}hfI(Lk2??01fviJS5a9DGW7ShDE2qW;usMH zu&!`~KT!nUox0_Swi7q!SsZ&jz%hn2-N3X?aHELgKNGL`W#dMEcKiMX)N!eEKo>?a zJu|4ShXS++_Y?MVk_fit!8-ZO7A;B%7v&V<-9Eog4Ug(D){S?pj5(Lk$E2{P4Y6w*K!7|iy}fZ~kl??+lz68E|u z_bJb32%&UzWmM@_&;1PRl`ahMj5|VX*^wb__~(^S>#~s(B?4tQar=>uVe#}F-QE6H z(vgj5^RQ@6oAi?y1(Ep_fR5j72a2}+Eo#61WF67Cq}V@94M@mUD4HG8ERj{;dBE;L zq~GAT%t{42Nn!VS4xrdMMW4Y=4*FCU0}~~OqJ>+tYI(DjP(S2dX#qdxC zj2(0$#n;4wp8#!b^9U1KPl>kJKt%1#9#wBT2SC&#)3phdf1w2|fY@mEJYe>0-Pjw` z0=2sQJ&6=gH@-~>%CZ^&NI0#p*#kL$We`uxZkma$FTEE7fN9rc7M?H~TNR^su|pza zMs^a5@5G^lzn`bDIvf-ud0#yKJ)lwdl9E|SG?@0gH=(+_A;YJZik>`cb3KlN3HzNy z`aVdv6pGt%x>F>Vh7w&F;=97&EuyE{-Mm8&LdCrM6z@IOqfQ)>?JzcXo2eHwx~->& zHZn+KoSHah>aAX=2L;nec_Ng7wT>Zq{tc>K?lEZTJ6&F;Lfs$+SJ^)I?lruz$G?(Z zqvu$g**DJSOb8DrHjCJrIk4+DO3X1xSQj`;bv)+<;qY&6islK7)MrQ_v&+-Hs#E}G zb(6~Z)9*lcw9UVH_Qq4c>c(jw2#)2f*WZoraHrI%Is(dEX{pj(hEThYC0BCHI`a~b zin=A|c#0)cq6nOZ+B{9WR+w}EU3bnLC;Je?mp~bBrmhPUy1m9i;+Yj^CE%~z-mnnI zb9h9@@4xOIor2EU-Oia(MUtNpsXeYgOWnT^{f=*xHeqz-8Ze+NxnKEU6HZi-(qR*n z1FfWYyWaXk_rmpjZ^jauPMV$&5!(83Ya;yt81j+Mm}T8|s|hGhTYLV1|G_NlY`)FY zV!BCm{e>Hqho%9kR(hsVAIvnI)j;QUf7|F)28NFTE;-;3`mN2A=ETi^sJQoSqqM3q zpz75^Tyv{vdnhdlND?`ea>UnC)#gb%UC#}Tv(EckJkonpHB^6E+8&Wdl8NeqlNVyO zHG&YZpxC#Zr#iI@IQ#XqEo@t^bWU?szXGx)_}AlJrp>BMc*y!QxfcCi*1|l3soFa`hI;My5GPRb{zkyoUlsiBKiJyBX?c$_6m87+Qm&2c6zI# z9cD>kAQ7(YE3%A0M@LVXLf#21y)1091`l&^O{1fL-tlY)j^q826xx`$z0~yIV+bv4 z{kbpf)?qUZXbd(3h*||Ru^9n9ww7f?fXpHLCthx=>PQEjPK{SdiZ-Yiq4SZQG3Jg@ zTaw*L;0hXRhpP_O`I3#x#@7lvheiBP3HP)?;vu<5=`!nX-4i^mAvuUzT0>b0k^2)u za*DSOrl9Vi?+a)4BMC_cvH5~c(I!&bJA|&|VODDs#gYrg7ouz0uj14y(d@VrdPN)D z%lP-2tJU$s)s%^i)$0HR==V2Yg^rx1RVC!!F#2gy)8r9v9 zN#6s)lF~lzPMZ%9%1yO|XQr2Qr8`0ZrMxXhVA7C1iU{q^TN9yKtC#NMfgV4o-<b_$J>VTQaxUu))3?g+#o!wX?tS{I9O-_~`ldqaDL@{?!w zFJil%yJpZRac^Ge`EW5S6R;yz{Z^kQo!grnm1^7~I;;wLH3NJj(bHD1y^Ah#t;_6( z+*=z5IF~`<712+lxtDOe1vlOgn9Kn4h>2pvRR~HFzi&%SRE|bCmq(|kOiy?h4{3in z;8Qwv8@7Wu)1l7iCfE3q`oc@2uNxtH_lxYIV>eTAjVxj#dgdo&TeWIDq^)~%WJ5{r z{5vl@o;lrW@(V^8HbqwGOmYBK19 zu2!Zz{F)otICWRc*QIjwHqU^2Am;h~1eJL*AcDH_QP!XeRm3wsuZES&dSF8#K;azG2jvf{qW#lBh&@Qgu)}2)akh& zyg&0|W`&2gPVBnoavOxpe(gr{S8leejuPxYW_^P4#CSi4jlYT;4%AP2Fb&9umRSQ0 z!|s!h2~CPvWL@BCg*)2pqa(K;3!BN6t*D2f$z+O{B0ROm_%NZi^aHDVA%!xGEVPa# z*i#kmCxaw3x~82$QTtH>AL}s1EZUQ{Tfz+^32UEc)c}KW~VH5#x|SO&-DYXj&wAg z89fn|{-$T0w?>>=Vhz$%s1I5^Qa?yg6JmVZ%1Hd{o#TWqe9@5=2h3~NAyI#hr5*bA z88L^kVDDyLwQI3S{fOkeP<=mV@!Uz5+3AcWVF)9prusq<8@~@+B$GfigQVMNDHXK5 zi{3eBKz+T=5Q*LL_;B7&^sW{;6$i`jqk8(R`9uOgNuOBrRnzmVQbcIMY2{^7@n#=6 z34iy$(zyPa5$1^Je@inM@iSW`*15B2)YS^9n2K`*Y?^x-8O{1s`+?YRf-61%fgNoV znzjv*oW8$C;D;xn(QK@>Xu8=W%r#y@i$>T!68s7Q%;LU;D0|ltV-FwweLsrT^sXXN zq|nT8yQkA$H1pIyUAVptc*+clbq**{b`yNoi?2K%1UU4OZo51dtXndGaj?uw4M}n7x2Qt>kf9$WjWby$`@cD04ST*Sp9ekaS({%nmDf zJ}5LQCKx?iLZ-f1(QhDWu7+%X;OGZ+Jhx6+!izwu=@mtgQI7;(z12FC;_ZwfsV%Qy z6WC*^TlaW|*IY$THCU0v&NiTVfG$8N<<@XvV8r@~kmShrSUxB(ZQK%RT%Z2`phH07?q#@-?y_!u|tk>;uUF literal 0 HcmV?d00001 diff --git a/mainWindow/kpMainWindow.cpp b/mainWindow/kpMainWindow.cpp new file mode 100644 index 0000000..04d06ed --- /dev/null +++ b/mainWindow/kpMainWindow.cpp @@ -0,0 +1,923 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "kpMainWindow.h" +#include "kpMainWindowPrivate.h" + +#include "layers/selections/image/kpAbstractImageSelection.h" +#include "environments/commands/kpCommandEnvironment.h" +#include "environments/tools/kpToolEnvironment.h" +#include "widgets/kpColorCells.h" +#include "widgets/toolbars/kpColorToolBar.h" +#include "commands/kpCommandHistory.h" +#include "document/kpDocument.h" +#include "environments/document/kpDocumentEnvironment.h" +#include "layers/selections/kpSelectionDrag.h" +#include "kpThumbnail.h" +#include "tools/kpTool.h" +#include "widgets/toolbars/kpToolToolBar.h" +#include "views/manager/kpViewManager.h" +#include "kpViewScrollableContainer.h" +#include "generic/kpWidgetMapper.h" +#include "views/kpZoomedThumbnailView.h" +#include "views/kpZoomedView.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include "kpLogCategories.h" + + +//--------------------------------------------------------------------- + +kpMainWindow::kpMainWindow () + : KXmlGuiWindow (nullptr/*parent*/) +{ + init (); + open (QUrl (), true/*create an empty doc*/); + + d->isFullyConstructed = true; +} + +//--------------------------------------------------------------------- + +kpMainWindow::kpMainWindow (const QUrl &url) + : KXmlGuiWindow (nullptr/*parent*/) +{ + init (); + open (url, true/*create an empty doc with the same url if url !exist*/); + + d->isFullyConstructed = true; +} + +//--------------------------------------------------------------------- + +kpMainWindow::kpMainWindow (kpDocument *newDoc) + : KXmlGuiWindow (nullptr/*parent*/) +{ + init (); + setDocument (newDoc); + + d->isFullyConstructed = true; +} + +//--------------------------------------------------------------------- + + +// TODO: Move into appropriate kpMainWindow_*.cpp or another class + +// private +void kpMainWindow::readGeneralSettings () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tkpMainWindow(" << objectName () << ")::readGeneralSettings()"; +#endif + + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral); + + d->configFirstTime = cfg.readEntry (kpSettingFirstTime, true); + d->configShowGrid = cfg.readEntry (kpSettingShowGrid, false); + d->configShowPath = cfg.readEntry (kpSettingShowPath, false); + d->moreEffectsDialogLastEffect = cfg.readEntry (kpSettingMoreEffectsLastEffect, 0); + kpToolEnvironment::drawAntiAliased = cfg.readEntry(kpSettingDrawAntiAliased, true); + + if (cfg.hasKey (kpSettingOpenImagesInSameWindow)) + { + d->configOpenImagesInSameWindow = cfg.readEntry (kpSettingOpenImagesInSameWindow, false); + } + else + { + d->configOpenImagesInSameWindow = false; +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tconfigOpenImagesInSameWindow: first time" + << " - writing default: " << d->configOpenImagesInSameWindow; +#endif + // TODO: More hidden options have to write themselves out on startup, + // not on use, to be discoverable (e.g. printing centered on page). + cfg.writeEntry (kpSettingOpenImagesInSameWindow, + d->configOpenImagesInSameWindow); + cfg.sync (); + } + + d->configPrintImageCenteredOnPage = cfg.readEntry (kpSettingPrintImageCenteredOnPage, true); + + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\tGeneral Settings: firstTime=" << d->configFirstTime + << " showGrid=" << d->configShowGrid + << " showPath=" << d->configShowPath + << " moreEffectsDialogLastEffect=" << d->moreEffectsDialogLastEffect + << " openImagesInSameWindow=" << d->configOpenImagesInSameWindow + << " printImageCenteredOnPage=" << d->configPrintImageCenteredOnPage; +#endif +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::readThumbnailSettings () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tkpMainWindow(" << objectName () << ")::readThumbnailSettings()"; +#endif + + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupThumbnail); + + d->configThumbnailShown = cfg.readEntry (kpSettingThumbnailShown, false); + d->configThumbnailGeometry = cfg.readEntry (kpSettingThumbnailGeometry, QRect ()); + d->configZoomedThumbnail = cfg.readEntry (kpSettingThumbnailZoomed, true); + d->configThumbnailShowRectangle = cfg.readEntry (kpSettingThumbnailShowRectangle, true); + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\tThumbnail Settings: shown=" << d->configThumbnailShown + << " geometry=" << d->configThumbnailGeometry + << " zoomed=" << d->configZoomedThumbnail + << " showRectangle=" << d->configThumbnailShowRectangle; +#endif +} + +//--------------------------------------------------------------------- + +void kpMainWindow::finalizeGUI(KXMLGUIClient *client) +{ + if ( client == this ) + { + const QList menuToHide = findChildren(QStringLiteral("toolToolBarHiddenMenu")); + // should only contain one but... + for (auto *menu : menuToHide) + { + menu->menuAction()->setVisible(false); + } + } +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::init () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow(" << objectName () << ")::init()"; + QTime totalTime; totalTime.start (); +#endif + + d = new kpMainWindowPrivate; + + // + // set mainwindow properties + // + + setMinimumSize (320, 260); + setAcceptDrops (true); + + // + // read config + // + + // KConfig::readEntry() does not actually reread from disk, hence doesn't + // realize what other processes have done e.g. Settings / Show Path + KSharedConfig::openConfig ()->reparseConfiguration (); + + readGeneralSettings (); + readThumbnailSettings (); + + // + // create GUI + // + setupActions (); + createStatusBar (); + createGUI (); + + createColorBox (); + createToolBox (); + + + // Let the Tool Box take all the vertical space, since it can be quite + // tall with all its tool option widgets. This also avoids occasional + // bugs like the Tool Box overlapping the Color Tool Bar. + setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); + setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); + + // no tabbed docks; does not make sense with only 2 dock widgets + setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks); + + addDockWidget(Qt::BottomDockWidgetArea, d->colorToolBar, Qt::Horizontal); + + d->scrollView = new kpViewScrollableContainer (this); + d->scrollView->setObjectName ( QStringLiteral("scrollView" )); + + connect (d->scrollView, &kpViewScrollableContainer::beganDocResize, + this, &kpMainWindow::slotBeganDocResize); + + connect (d->scrollView, &kpViewScrollableContainer::continuedDocResize, + this, &kpMainWindow::slotContinuedDocResize); + + connect (d->scrollView, &kpViewScrollableContainer::cancelledDocResize, + this, &kpMainWindow::slotCancelledDocResize); + + connect (d->scrollView, &kpViewScrollableContainer::endedDocResize, + this, &kpMainWindow::slotEndedDocResize); + + connect (d->scrollView, &kpViewScrollableContainer::statusMessageChanged, + this, &kpMainWindow::slotDocResizeMessageChanged); + + connect (d->scrollView, &kpViewScrollableContainer::contentsMoved, + this, &kpMainWindow::slotScrollViewAfterScroll); + + setCentralWidget (d->scrollView); + + // + // set initial pos/size of GUI + // + + setAutoSaveSettings (); + + // our non-XMLGUI tools-toolbar will get initially the toolButtonStyle as + // all other toolbars, but we want to show only icons for the tools by default + // (have to do this _after_ setAutoSaveSettings as that applies the default settings) + if (d->configFirstTime) + { + d->toolToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); + + KConfigGroup cfg(KSharedConfig::openConfig(), kpSettingsGroupGeneral); + + cfg.writeEntry(kpSettingFirstTime, d->configFirstTime = false); + cfg.sync(); + } + + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tall done in " << totalTime.elapsed () << "msec"; +#endif +} + +//--------------------------------------------------------------------- + +// private virtual [base KMainWindow] +void kpMainWindow::readProperties (const KConfigGroup &configGroup) +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow<" << this << ">::readProperties()"; +#endif + + // No document at all? + if (!configGroup.hasKey (kpSessionSettingDocumentUrl)) + { + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tno url - no document"; + #endif + setDocument (nullptr); + } + // Have a document. + else + { + const QUrl url = QUrl (configGroup.readEntry (kpSessionSettingDocumentUrl, + QString ())); + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\turl=" << url; + #endif + + const QSize notFromURLDocSize = + configGroup.readEntry (kpSessionSettingNotFromUrlDocumentSize, + QSize ()); + + // Is from URL? + if (notFromURLDocSize.isEmpty ()) + { + // If this fails, the empty document that kpMainWindow::kpMainWindow() + // created is left untouched. + openInternal (url, defaultDocSize (), + false/*show error message if url !exist*/); + } + // Not from URL? + else + { + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tnot from url; doc size=" << notFromURLDocSize; + #endif + // Either we have an empty URL or we have a "kolourpaint doesnotexist.png" + // URL. Regarding the latter case, if a file now actually exists at that + // URL, we do open it - ignoring notFromURLDocSize - to avoid putting + // the user in a situation where he might accidentally overwrite an + // existing file. + openInternal (url, notFromURLDocSize, + true/*create an empty doc with the same url if url !exist*/); + } + } + +} + +//--------------------------------------------------------------------- + +// private virtual [base KMainWindow] +// WARNING: KMainWindow API Doc says "No user interaction is allowed +// in this function!" +void kpMainWindow::saveProperties (KConfigGroup &configGroup) +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow<" << this << ">::saveProperties()"; +#endif + + // No document at all? + if (!d->document) + { + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tno url - no document"; + #endif + } + // Have a document. + else + { + // Save URL in all cases: + // + // a) d->document->isFromExistingURL() + // b) !d->document->isFromExistingURL() [save size in this case] + // i) No URL + // ii) URL (from "kolourpaint doesnotexist.png") + + const QUrl url = d->document->url (); + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\turl=" << url; + #endif + configGroup.writeEntry (kpSessionSettingDocumentUrl, url.url ()); + + // Not from URL e.g. "kolourpaint doesnotexist.png"? + // + // Note that "kolourpaint doesexist.png" is considered to be from + // a URL even if it was deleted in the background. This is because the user expects + // it to be from a URL, so when we session restore, we pop up a + // "cannot find file" dialog, instead of silently creating a new, + // blank document. + if (!d->document->isFromExistingURL ()) + { + // If we don't have a URL either: + // + // a) it was not modified - so we can use either width() or + // constructorWidth() (they'll be equal). + // b) the changes were discarded so we use the initial width, + // constructorWidth(). + // + // Similarly for height() and constructorHeight(). + const QSize docSize (d->document->constructorWidth (), + d->document->constructorHeight ()); + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tnot from url; doc size=" << docSize; + #endif + configGroup.writeEntry (kpSessionSettingNotFromUrlDocumentSize, docSize); + } + } +} + +//--------------------------------------------------------------------- + + +kpMainWindow::~kpMainWindow () +{ + d->isFullyConstructed = false; + + // Get the kpTool to finish up. This makes sure that the kpTool destructor + // will not need to access any other class (that might be deleted before + // the destructor is called by the QObject child-deletion mechanism). + if (tool ()) { + tool ()->endInternal (); + } + + // Delete document & views. + // Note: This will disconnects signals from the current kpTool, so kpTool + // must not be destructed yet. + setDocument (nullptr); + + delete d->commandHistory; d->commandHistory = nullptr; + delete d->scrollView; d->scrollView = nullptr; + + delete d; d = nullptr; +} + +//--------------------------------------------------------------------- + + +// public +kpDocument *kpMainWindow::document () const +{ + return d->document; +} + +//--------------------------------------------------------------------- + +// public +kpDocumentEnvironment *kpMainWindow::documentEnvironment () +{ + if (!d->documentEnvironment) { + d->documentEnvironment = new kpDocumentEnvironment (this); + } + + return d->documentEnvironment; +} + +//--------------------------------------------------------------------- + +// public +kpViewManager *kpMainWindow::viewManager () const +{ + return d->viewManager; +} + +//--------------------------------------------------------------------- + +// public +kpColorToolBar *kpMainWindow::colorToolBar () const +{ + return d->colorToolBar; +} + +//--------------------------------------------------------------------- + +// public +kpColorCells *kpMainWindow::colorCells () const +{ + return d->colorToolBar ? d->colorToolBar->colorCells () : nullptr; +} + +//--------------------------------------------------------------------- + +// public +kpToolToolBar *kpMainWindow::toolToolBar () const +{ + return d->toolToolBar; +} + +//--------------------------------------------------------------------- + +// public +kpCommandHistory *kpMainWindow::commandHistory () const +{ + return d->commandHistory; +} + +//--------------------------------------------------------------------- + +kpCommandEnvironment *kpMainWindow::commandEnvironment () +{ + if (!d->commandEnvironment) { + d->commandEnvironment = new kpCommandEnvironment (this); + } + + return d->commandEnvironment; +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::setupActions () +{ + setupFileMenuActions (); + setupEditMenuActions (); + setupViewMenuActions (); + setupImageMenuActions (); + setupColorsMenuActions (); + setupSettingsMenuActions (); + + setupTextToolBarActions (); + setupToolActions (); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::enableDocumentActions (bool enable) +{ + enableFileMenuDocumentActions (enable); + enableEditMenuDocumentActions (enable); + enableViewMenuDocumentActions (enable); + enableImageMenuDocumentActions (enable); + enableColorsMenuDocumentActions (enable); + enableSettingsMenuDocumentActions (enable); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::setDocument (kpDocument *newDoc) +{ + //qCDebug(kpLogMainWindow) << newDoc; + + // is it a close operation? + if (!newDoc) + { + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tdisabling actions"; + #endif + + // sync with the bit marked "sync" below + + // TODO: Never disable the Color Box because the user should be + // able to manipulate the colors, even without a currently + // open document. + // + // We just have to make sure that signals from the Color + // Box aren't fired and received unexpectedly when there's + // no document. + Q_ASSERT (d->colorToolBar); + d->colorToolBar->setEnabled (false); + + enableTextToolBarActions (false); + } + + // Always disable the tools. + // If we decide to open a new document/mainView we want + // kpTool::begin() to be called again e.g. in case it sets the cursor. + // kpViewManager won't do this because we nuke it to avoid stale state. + enableToolsDocumentActions (false); + + if (!newDoc) + { + enableDocumentActions (false); + } + + delete d->mainView; d->mainView = nullptr; + slotDestroyThumbnail (); + + // viewManager will die and so will the selection + d->actionCopy->setEnabled (false); + d->actionCut->setEnabled (false); + d->actionDelete->setEnabled (false); + d->actionDeselect->setEnabled (false); + d->actionCopyToFile->setEnabled (false); + + delete d->viewManager; d->viewManager = nullptr; + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tdestroying document"; + qCDebug(kpLogMainWindow) << "\t\td->document=" << d->document; +#endif + // destroy current document + delete d->document; + d->document = newDoc; + + + if (!d->lastCopyToURL.isEmpty ()) + { + // remove file name from path + QString path = d->lastCopyToURL.path (); + path = path.left (path.lastIndexOf (QLatin1Char ('/')) + 1); + d->lastCopyToURL.setPath (path); + } + d->copyToFirstTime = true; + + if (!d->lastExportURL.isEmpty ()) + { + QString path = d->lastExportURL.path (); + path = path.left (path.lastIndexOf (QLatin1Char ('/')) + 1); + d->lastExportURL.setPath (path); + } + d->exportFirstTime = true; + + + // not a close operation? + if (d->document) + { + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\treparenting doc that may have been created into a" + << " different mainWindiow"; + #endif + d->document->setEnviron (documentEnvironment ()); + + d->viewManager = new kpViewManager (this); + + d->mainView = new kpZoomedView (d->document, d->toolToolBar, d->viewManager, + nullptr/*buddyView*/, + d->scrollView, + d->scrollView->viewport ()); + d->mainView->setObjectName ( QStringLiteral("mainView" )); + + d->viewManager->registerView (d->mainView); + d->scrollView->setView (d->mainView); + d->mainView->show (); + + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\thooking up document signals"; + #endif + + // Copy/Cut/Deselect/Delete + connect (d->document, &kpDocument::selectionEnabled, + d->actionCut, &QAction::setEnabled); + + connect (d->document, &kpDocument::selectionEnabled, + d->actionCopy, &QAction::setEnabled); + + connect (d->document, &kpDocument::selectionEnabled, + d->actionDelete, &QAction::setEnabled); + + connect (d->document, &kpDocument::selectionEnabled, + d->actionDeselect, &QAction::setEnabled); + + connect (d->document, &kpDocument::selectionEnabled, + d->actionCopyToFile, &QAction::setEnabled); + + // this code won't actually enable any actions at this stage + // (fresh document) but better safe than sorry + d->actionCopy->setEnabled (d->document->selection ()); + d->actionCut->setEnabled (d->document->selection ()); + d->actionDeselect->setEnabled (d->document->selection ()); + d->actionDelete->setEnabled (d->document->selection ()); + d->actionCopyToFile->setEnabled (d->document->selection ()); + + connect (d->document, &kpDocument::selectionEnabled, + this, &kpMainWindow::slotImageMenuUpdateDueToSelection); + + connect (d->document, &kpDocument::selectionIsTextChanged, + this, &kpMainWindow::slotImageMenuUpdateDueToSelection); + + // Status bar + connect (d->document, &kpDocument::documentOpened, + this, &kpMainWindow::recalculateStatusBar); + + connect (d->document, SIGNAL (sizeChanged(QSize)), + this, SLOT (setStatusBarDocSize(QSize))); + + // Caption (url, modified) + connect (d->document, &kpDocument::documentModified, + this, &kpMainWindow::slotUpdateCaption); + + connect (d->document, &kpDocument::documentOpened, + this, &kpMainWindow::slotUpdateCaption); + + connect (d->document, &kpDocument::documentSaved, + this, &kpMainWindow::slotUpdateCaption); + + // File/Reload action only available with non-empty URL + connect (d->document, &kpDocument::documentSaved, + this, &kpMainWindow::slotEnableReload); + + connect (d->document, &kpDocument::documentSaved, + this, &kpMainWindow::slotEnableSettingsShowPath); + + // Command history + Q_ASSERT (d->commandHistory); + connect (d->commandHistory, &kpCommandHistory::documentRestored, + this, &kpMainWindow::slotDocumentRestored); // caption "!modified" + + connect (d->document, &kpDocument::documentSaved, + d->commandHistory, &kpCommandHistory::documentSaved); + + // Sync document -> views + connect (d->document, &kpDocument::contentsChanged, + d->viewManager, &kpViewManager::updateViews); + + connect (d->document, static_cast(&kpDocument::sizeChanged), + d->viewManager, &kpViewManager::adjustViewsToEnvironment); + + connect (d->document, + static_cast(&kpDocument::sizeChanged), + d->viewManager, &kpViewManager::adjustViewsToEnvironment); + + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tenabling actions"; + #endif + + // sync with the bit marked "sync" above + + Q_ASSERT (d->colorToolBar); + d->colorToolBar->setEnabled (true); + + + // Hide the text toolbar - it will be shown by kpToolText::begin() + enableTextToolBarActions (false); + + enableToolsDocumentActions (true); + + enableDocumentActions (true); + } + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tupdating mainWindow elements"; +#endif + + slotImageMenuUpdateDueToSelection (); + recalculateStatusBar (); + slotUpdateCaption (); // Untitled to start with + slotEnableReload (); + slotEnableSettingsShowPath (); + + if (d->commandHistory) { + d->commandHistory->clear (); + } + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tdocument and views ready to go!"; +#endif +} + +//--------------------------------------------------------------------- + +// private virtual [base QWidget] +void kpMainWindow::dragEnterEvent (QDragEnterEvent *e) +{ + // It's faster to test for QMimeData::hasText() first due to the + // lazy evaluation of the '||' operator. + e->setAccepted (e->mimeData ()->hasText () || + e->mimeData ()->hasUrls () || + kpSelectionDrag::canDecode (e->mimeData ())); +} + +//--------------------------------------------------------------------- + +// private virtual [base QWidget] +void kpMainWindow::dropEvent (QDropEvent *e) +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::dropEvent" << e->pos (); +#endif + + QList urls; + + kpAbstractImageSelection *sel = kpSelectionDrag::decode (e->mimeData ()); + if (sel) + { + // TODO: How do you actually drop a selection or ordinary images on + // the clipboard)? Will this code path _ever_ execute? + sel->setTransparency (imageSelectionTransparency ()); + // TODO: drop at point like with QTextDrag below? + paste (*sel); + delete sel; + } + else if (!(urls = e->mimeData ()->urls ()).isEmpty ()) + { + // LOTODO: kpSetOverrideCursorSaver cursorSaver (Qt::waitCursor); + // + // However, you would need to prefix all possible error/warning + // dialogs that might be called, with Qt::arrowCursor e.g. in + // kpDocument and probably a lot more places. + for (const auto &u : urls) + open (u); + } + else if (e->mimeData ()->hasText ()) + { + const QString text = e->mimeData ()->text (); + + QPoint selTopLeft = KP_INVALID_POINT; + const QPoint globalPos = QWidget::mapToGlobal (e->pos ()); + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tpos toGlobal=" << globalPos; + #endif + + kpView *view = nullptr; + + if (d->viewManager) + { + view = d->viewManager->viewUnderCursor (); + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\tviewUnderCursor=" << view; + #endif + if (!view) + { + // HACK: see kpViewManager::setViewUnderCursor() to see why + // it's not reliable + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\tattempting to discover view"; + + if (d->mainView && d->scrollView) + { + qCDebug(kpLogMainWindow) << "\t\t\tmainView->globalRect=" + << kpWidgetMapper::toGlobal (d->mainView, d->mainView->rect ()) + << " scrollView->globalRect=" + << kpWidgetMapper::toGlobal (d->scrollView, + QRect (0, 0, + d->scrollView->viewport()->width (), + d->scrollView->viewport()->height ())); + } + #endif + if (d->thumbnailView && + kpWidgetMapper::toGlobal (d->thumbnailView, d->thumbnailView->rect ()) + .contains (globalPos)) + { + // TODO: Code will never get executed. + // Thumbnail doesn't accept drops. + view = d->thumbnailView; + } + else if (d->mainView && + kpWidgetMapper::toGlobal (d->mainView, d->mainView->rect ()) + .contains (globalPos) && + d->scrollView && + kpWidgetMapper::toGlobal (d->scrollView, + QRect (0, 0, + d->scrollView->viewport()->width (), + d->scrollView->viewport()->height ())) + .contains (globalPos)) + { + view = d->mainView; + } + } + } + + if (view) + { + const QPoint viewPos = view->mapFromGlobal (globalPos); + const QPoint docPoint = view->transformViewToDoc (viewPos); + + // viewUnderCursor() is hacky and can return a view when we aren't + // over one thanks to drags. + if (d->document && d->document->rect ().contains (docPoint)) + { + selTopLeft = docPoint; + + // TODO: In terms of doc pixels, would be inconsistent behaviour + // based on zoomLevel of view. + // selTopLeft -= QPoint (-view->selectionResizeHandleAtomicSize (), + // -view->selectionResizeHandleAtomicSize ()); + } + } + + pasteText (text, true/*force new text selection*/, selTopLeft); + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotScrollViewAfterScroll () +{ + // OPT: Why can't this be moved into kpViewScrollableContainer::slotDragScroll(), + // grouping all drag-scroll-related repaints, which would potentially avoid + // double repainting? + if (tool ()) + { + tool ()->somethingBelowTheCursorChanged (); + } +} + +//--------------------------------------------------------------------- + +// private virtual [base QWidget] +void kpMainWindow::moveEvent (QMoveEvent * /*e*/) +{ + if (d->thumbnail) + { + // Disabled because it lags too far behind the mainWindow + // d->thumbnail->move (d->thumbnail->pos () + (e->pos () - e->oldPos ())); + + notifyThumbnailGeometryChanged (); + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotUpdateCaption () +{ + if (d->document) + { + setCaption (d->configShowPath ? d->document->prettyUrl () + : d->document->prettyFilename (), + d->document->isModified ()); + } + else + { + setCaption (QString(), false); + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotDocumentRestored () +{ + if (d->document) { + d->document->setModified (false); + } + slotUpdateCaption (); +} + +//--------------------------------------------------------------------- + diff --git a/mainWindow/kpMainWindow.h b/mainWindow/kpMainWindow.h new file mode 100644 index 0000000..4261525 --- /dev/null +++ b/mainWindow/kpMainWindow.h @@ -0,0 +1,692 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_MAIN_WINDOW_H +#define KP_MAIN_WINDOW_H + + +#include + +#include + +#include "kpDefs.h" +#include "pixmapfx/kpPixmapFX.h" +#include "imagelib/kpImage.h" + + +class QAction; +class QActionGroup; +class QDragEnterEvent; +class QDropEvent; +class QMenu; +class QMoveEvent; +class QPoint; +class QRect; +class QSize; + +class KConfigGroup; +class KToolBar; +class QPrinter; + +class kpColor; +class kpColorCells; +class kpColorToolBar; +class kpCommand; +class kpCommandEnvironment; +class kpCommandHistory; +class kpDocument; +class kpDocumentEnvironment; +class kpDocumentMetaInfo; +class kpDocumentSaveOptions; +class kpViewManager; +class kpImageSelectionTransparency; +class kpTextStyle; +class kpThumbnail; +class kpTool; +class kpToolEnvironment; +class kpToolSelectionEnvironment; +class kpToolToolBar; +class kpTransformDialogEnvironment; +class kpAbstractSelection; + +class kpMainWindow : public KXmlGuiWindow +{ +Q_OBJECT + +public: + // Opens a new window with a blank document. + kpMainWindow (); + + // Opens a new window with the document specified by + // or creates a blank document if could not be opened. + kpMainWindow (const QUrl &url); + + // Opens a new window with the document + // ( can be 0 although this would result in a new + // window without a document at all). + kpMainWindow (kpDocument *newDoc); + + void finalizeGUI(KXMLGUIClient *client) override; + +private: + void readGeneralSettings (); + void readThumbnailSettings (); + + void init (); + + // (only called for restoring a previous session e.g. starting KDE with + // a previously saved session; it's not called on normal KolourPaint + // startup) + void readProperties (const KConfigGroup &configGroup) override; + // (only called for saving the current session e.g. logging out of KDE + // with the KolourPaint window open; it's not called on normal KolourPaint + // exit) + void saveProperties (KConfigGroup &configGroup) override; + +public: + ~kpMainWindow () override; + +public: + kpDocument *document () const; + kpDocumentEnvironment *documentEnvironment (); + kpViewManager *viewManager () const; + kpColorToolBar *colorToolBar () const; + kpColorCells *colorCells () const; + kpToolToolBar *toolToolBar () const; + kpCommandHistory *commandHistory () const; + kpCommandEnvironment *commandEnvironment (); + +private: + void setupActions (); + void enableDocumentActions (bool enable = true); + + void setDocument (kpDocument *newDoc); + + void dragEnterEvent (QDragEnterEvent *e) override; + void dropEvent (QDropEvent *e) override; + void moveEvent (QMoveEvent *e) override; + +private slots: + void slotScrollViewAfterScroll (); + void slotUpdateCaption (); + void slotDocumentRestored (); + + +// +// Tools +// + +private: + kpToolSelectionEnvironment *toolSelectionEnvironment (); + kpToolEnvironment *toolEnvironment (); + + void setupToolActions (); + void createToolBox (); + void enableToolsDocumentActions (bool enable = true); + +private slots: + void updateToolOptionPrevNextActionsEnabled (); + void updateActionDrawOpaqueChecked (); +private: + void updateActionDrawOpaqueEnabled (); + +public: + QActionGroup *toolsActionGroup (); + + kpTool *tool () const; + + bool toolHasBegunShape () const; + bool toolIsASelectionTool (bool includingTextTool = true) const; + bool toolIsTextTool () const; + +private: + // Ends the current shape. If there is no shape currently being drawn, + // it does nothing. + // + // In general, call this at the start of every kpMainWindow slot, + // directly invoked by the _user_ (by activating an action or via another + // way), so that: + // + // 1. The document contains the pixels of that shape: + // + // Most tools have the shape, currently being drawn, layered above the + // document as a kpTempImage. In other words, the document does not + // yet contain the pixels of that shape. By ending the shape, the layer + // is pushed down onto the document so that it now contains those + // pixels. Your slot can now safely read the document as it's now + // consistent with what's on the screen. + // + // For example, consider the case where a line is being dragged out and + // CTRL+I is pressed to invert the image, while the mouse is still held + // down. The CTRL+I invert code (kpMainWindow::slotInvertColors()) must + // push the line kpTempImage onto the document before the invert can + // meaningfully proceed (else the invert will see the state of the document + // before the line was dragged out). + // + // Note that selection layers are not pushed down by this method. + // This is a feature, not a bug. The user would be annoyed if e.g. + // slotSave() happened to push down the selection. Use + // kpDocument::imageWithSelection() to get around this problem. You + // should still call toolEndShape() even if a selection is active + // -- this ends selection "shapes", which are actually things like + // selection moves or smearing operations, rather than the selections + // themselves. + // + // AND/OR: + // + // 2. The current tool is no longer in a drawing state: + // + // If your slot is going to bring up a new main window or modal dialog + // or at least some widget that acquires mouse or keyboard focus, this + // could confuse the tool if the tool is in the middle of a drawing + // operation. + // + // Do not call this in slots not invoked by the user. For instance, + // calling this method in response to an internal timer tick would be + // wrong. The user's drawing operation would unexpectedly finish and + // this would bewilder and irritate the user. + // + // TODO: Help / KolourPaint Handbook does not call this. I'm sure there + // are a few other actions that don't call this but should. + void toolEndShape (); + +public: + kpImageSelectionTransparency imageSelectionTransparency () const; + // The drawing background color is set to .transparentColor() + // if the is in Transparent mode or if + // is true (not the default). [x] + // + // If is in Opaque mode and is false, + // the background color is not changed because: + // + // 1. It is ignored by the selection in Opaque mode anyway. + // 2. This avoids irritating the user with an unnecessary background + // color change. + // + // The only case where you should set to true is in + // kpToolImageSelectionTransparencyCommand to ensure that the state + // is identical to when the command was constructed. + // Later: I don't think setting it to true is ever necessary since: + // + // 1. The background color only counts in Transparent mode. + // + // 2. Any kpToolImageSelectionTransparencyCommand that switches to + // Transparent mode will automatically set the background + // color due to the first part of [x] anyway. + // + // The other fields of are copied into the main window + // as expected. + void setImageSelectionTransparency (const kpImageSelectionTransparency &transparency, + bool forceColorChange = false); + int settingImageSelectionTransparency () const; + +private slots: + void slotToolSelected (kpTool *tool); + +private: + void readLastTool (); + int toolNumber () const; + void saveLastTool (); + +private: + bool maybeDragScrollingMainView () const; +private slots: + bool slotDragScroll (const QPoint &docPoint, + const QPoint &docLastPoint, + int zoomLevel, + bool *didSomething); + bool slotEndDragScroll (); + +private slots: + void slotBeganDocResize (); + void slotContinuedDocResize (const QSize &size); + void slotCancelledDocResize (); + void slotEndedDocResize (const QSize &size); + + void slotDocResizeMessageChanged (const QString &string); + +private slots: + void slotActionPrevToolOptionGroup1 (); + void slotActionNextToolOptionGroup1 (); + void slotActionPrevToolOptionGroup2 (); + void slotActionNextToolOptionGroup2 (); + + void slotActionDrawOpaqueToggled (); + void slotActionDrawColorSimilarity (); + +public slots: + void slotToolRectSelection(); + void slotToolEllipticalSelection(); + void slotToolFreeFormSelection(); + void slotToolText(); + +// +// File Menu +// + +private: + void setupFileMenuActions (); + void enableFileMenuDocumentActions (bool enable = true); + + void addRecentURL (const QUrl &url); + +private slots: + void slotNew (); + +private: + QSize defaultDocSize () const; + void saveDefaultDocSize (const QSize &size); + +private: + bool shouldOpen (); + void setDocumentChoosingWindow (kpDocument *doc); + +private: + kpDocument *openInternal (const QUrl &url, + const QSize &fallbackDocSize, + bool newDocSameNameIfNotExist); + // Same as above except that it: + // + // 1. Assumes a default fallback document size. + // 2. If the URL is successfully opened (with the special exception of + // the "kolourpaint doesnotexist.png" case), it is bubbled up to the + // top in the Recent Files Action. + // + // As a result of this behavior, this should only be called in response + // to a user open request e.g. File / Open or "kolourpaint doesexist.png". + // It should not be used for session restore - in that case, it does not + // make sense to bubble the Recent Files list. + bool open (const QUrl &url, bool newDocSameNameIfNotExist = false); + + QList askForOpenURLs(const QString &caption, + bool allowMultipleURLs = true); + +private slots: + void slotOpen (); + void slotOpenRecent (const QUrl &url); + void slotRecentListCleared(); + +#if HAVE_KSANE + void slotScan (); + void slotScanned (const QImage &image, int); +#endif // HAVE_KSANE + + void slotScreenshot(); + void slotMakeScreenshot(); + + void slotProperties (); + + bool save (bool localOnly = false); + bool slotSave (); + +private: + QUrl askForSaveURL (const QString &caption, + const QString &startURL, + const kpImage &imageToBeSaved, + const kpDocumentSaveOptions &startSaveOptions, + const kpDocumentMetaInfo &docMetaInfo, + const QString &forcedSaveOptionsGroup, + bool localOnly, + kpDocumentSaveOptions *chosenSaveOptions, + bool isSavingForFirstTime, + bool *allowLossyPrompt); + +private slots: + bool saveAs (bool localOnly = false); + bool slotSaveAs (); + + bool slotExport (); + + void slotEnableReload (); + bool slotReload (); + void sendPreviewToPrinter(QPrinter *printer); + +private: + void sendDocumentNameToPrinter (QPrinter *printer); + void setPrinterPageOrientation(QPrinter *printer); + void sendImageToPrinter(QPrinter *printer, bool showPrinterSetupDialog); + +private slots: + void slotPrint (); + void slotPrintPreview (); + + void slotMail (); + + bool queryCloseDocument (); + bool queryClose () override; + + void slotClose (); + void slotQuit (); + + +// +// Edit Menu +// + +private: + void setupEditMenuActions (); + void enableEditMenuDocumentActions (bool enable = true); + +public: + QMenu *selectionToolRMBMenu (); + +private slots: + void slotCut (); + void slotCopy (); + void slotEnablePaste (); +private: + QRect calcUsefulPasteRect (int imageWidth, int imageHeight); + // (it is possible to paste a selection border i.e. a selection with no content) + void paste (const kpAbstractSelection &sel, + bool forceTopLeft = false); +public: + // ( is ignored if is empty) + void pasteText (const QString &text, + bool forceNewTextSelection = false, + const QPoint &newTextSelectionTopLeft = KP_INVALID_POINT); + void pasteTextAt (const QString &text, const QPoint &point, + // Allow tiny adjustment of so that mouse + // pointer is not exactly on top of the topLeft of + // any new text selection (so that it doesn't look + // weird by being on top of a resize handle just after + // a paste). + bool allowNewTextSelectionPointShift = false); +public slots: + void slotPaste (); +private slots: + void slotPasteInNewWindow (); +public slots: + void slotDelete (); + + void slotSelectAll (); +private: + void addDeselectFirstCommand (kpCommand *cmd); +public slots: + void slotDeselect (); +private slots: + void slotCopyToFile (); + void slotPasteFromFile (); + + +// +// View Menu +// + +private: + void setupViewMenuActions (); + + bool viewMenuDocumentActionsEnabled () const; + void enableViewMenuDocumentActions (bool enable = true); + void actionShowGridUpdate (); + void updateMainViewGrid (); + QRect mapToGlobal (const QRect &rect) const; + QRect mapFromGlobal (const QRect &rect) const; + +private slots: + void slotShowGridToggled (); + + +// +// View Menu - Zoom +// + +private: + void setupViewMenuZoomActions (); + void enableViewMenuZoomDocumentActions (bool enable); + + void sendZoomListToActionZoom (); + + void zoomToPre (int zoomLevel); + void zoomToPost (); + +public: + void zoomTo (int zoomLevel, bool centerUnderCursor = false); + void zoomToRect (const QRect &normalizedDocRect, + bool accountForGrips, + bool careAboutWidth, bool careAboutHeight); + +public slots: + void slotActualSize (); + void slotFitToPage (); + void slotFitToWidth (); + void slotFitToHeight (); + +public: + void zoomIn (bool centerUnderCursor = false); + void zoomOut (bool centerUnderCursor = false); + +public slots: + void slotZoomIn (); + void slotZoomOut (); + +private: + void zoomAccordingToZoomAction (bool centerUnderCursor = false); + +private slots: + void slotZoom (); + + +// +// View Menu - Thumbnail +// + +private: + void setupViewMenuThumbnailActions (); + void enableViewMenuThumbnailDocumentActions (bool enable); + +private slots: + void slotDestroyThumbnail (); + void slotDestroyThumbnailInitatedByUser (); + void slotCreateThumbnail (); + +public: + void notifyThumbnailGeometryChanged (); + +private slots: + void slotSaveThumbnailGeometry (); + void slotShowThumbnailToggled (); + void updateThumbnailZoomed (); + void slotZoomedThumbnailToggled (); + void slotThumbnailShowRectangleToggled (); + +private: + void enableViewZoomedThumbnail (bool enable = true); + void enableViewShowThumbnailRectangle (bool enable = true); + void enableThumbnailOptionActions (bool enable = true); + void createThumbnailView (); + void destroyThumbnailView (); + void updateThumbnail (); + + +// +// Image Menu +// + +private: + kpTransformDialogEnvironment *transformDialogEnvironment (); + + bool isSelectionActive () const; + bool isTextSelection () const; + + QString autoCropText () const; + + void setupImageMenuActions (); + void enableImageMenuDocumentActions (bool enable = true); + +private slots: + void slotImageMenuUpdateDueToSelection (); + +public: + kpColor backgroundColor (bool ofSelection = false) const; + void addImageOrSelectionCommand (kpCommand *cmd, + bool addSelCreateCmdIfSelAvail = true, + bool addSelContentCmdIfSelAvail = true); + +public slots: + void slotCrop (); + +private slots: + void slotResizeScale (); + void slotAutoCrop (); + void slotFlip (); + void slotMirror (); + + void slotRotate (); + void slotRotate270 (); + void slotRotate90 (); + + void slotSkew (); + void slotConvertToBlackAndWhite (); + void slotConvertToGrayscale (); + void slotInvertColors (); + void slotClear (); + void slotMakeConfidential(); + void slotMoreEffects (); + + +// +// Colors Menu +// + +private: + void setupColorsMenuActions (); + void createColorBox (); + void enableColorsMenuDocumentActions (bool enable); +private slots: + void slotUpdateColorsDeleteRowActionEnabled (); + +private: + void deselectActionColorsKDE (); + + bool queryCloseColors (); + +private: + void openDefaultColors (); +private slots: + void slotColorsDefault (); + +private: + bool openKDEColors (const QString &name); +private slots: + void slotColorsKDE (); + +private: + bool openColors (const QUrl &url); +private slots: + void slotColorsOpen (); + + void slotColorsReload (); + + bool slotColorsSave (); + bool slotColorsSaveAs (); + + void slotColorsAppendRow (); + void slotColorsDeleteRow (); + + +// +// Settings Menu +// + +private: + void setupSettingsMenuActions (); + void enableSettingsMenuDocumentActions (bool enable = true); + +private slots: + void slotFullScreen (); + + void slotEnableSettingsShowPath (); + void slotShowPathToggled (); + void slotDrawAntiAliasedToggled(bool on); + + void slotKeyBindings (); + +// +// Status Bar +// + +private: + enum + { + StatusBarItemShapePoints, + StatusBarItemShapeSize, + StatusBarItemDocSize, + StatusBarItemDocDepth, + StatusBarItemZoom + }; + + void addPermanentStatusBarItem (int id, int maxTextLen); + void createStatusBar (); + + void setStatusBarDocDepth (int depth = 0); + +private slots: + void setStatusBarMessage (const QString &message = QString()); + void setStatusBarShapePoints (const QPoint &startPoint = KP_INVALID_POINT, + const QPoint &endPoint = KP_INVALID_POINT); + void setStatusBarShapeSize (const QSize &size = KP_INVALID_SIZE); + void setStatusBarDocSize (const QSize &size = KP_INVALID_SIZE); + void setStatusBarZoom (int zoom = 0); + + void recalculateStatusBarMessage (); + void recalculateStatusBarShape (); + + void recalculateStatusBar (); + + +// +// Text ToolBar +// + +private: + void setupTextToolBarActions (); + void readAndApplyTextSettings (); + +public: + void enableTextToolBarActions (bool enable = true); + +private slots: + void slotTextFontFamilyChanged (); + void slotTextFontSizeChanged (); + void slotTextBoldChanged (); + void slotTextItalicChanged (); + void slotTextUnderlineChanged (); + void slotTextStrikeThruChanged (); + +public: + KToolBar *textToolBar (); + bool isTextStyleBackgroundOpaque () const; + kpTextStyle textStyle () const; + void setTextStyle (const kpTextStyle &textStyle_); + int settingTextStyle () const; + +private: + struct kpMainWindowPrivate *d; +}; + +#endif // KP_MAIN_WINDOW_H diff --git a/mainWindow/kpMainWindowPrivate.h b/mainWindow/kpMainWindowPrivate.h new file mode 100644 index 0000000..64fb604 --- /dev/null +++ b/mainWindow/kpMainWindowPrivate.h @@ -0,0 +1,445 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2014 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef kpMainWindowPrivate_H +#define kpMainWindowPrivate_H + + +#define DEBUG_KP_MAIN_WINDOW 0 + + +#include "document/kpDocumentSaveOptions.h" + + +class QAction; +class QActionGroup; +class QLabel; + +class KSelectAction; +class KToggleAction; +class KSqueezedTextLabel; +class KRecentFilesAction; +class KFontAction; +class KFontSizeAction; +class KToggleFullScreenAction; +class kpCommandEnvironment; +class kpDocumentEnvironment; +class kpToolSelectionEnvironment; +class kpTransformDialogEnvironment; +class kpViewScrollableContainer; +class kpZoomedView; +class kpThumbnail; +class kpThumbnailView; +class kpDocument; +class kpViewManager; +class kpColorToolBar; +class kpToolToolBar; +class kpCommandHistory; +class kpTool; +class kpToolText; +class SaneDialog; + + +struct kpMainWindowPrivate +{ + kpMainWindowPrivate () + : isFullyConstructed(false), + scrollView(nullptr), + mainView(nullptr), + thumbnail(nullptr), + thumbnailView(nullptr), + document(nullptr), + viewManager(nullptr), + colorToolBar(nullptr), + toolToolBar(nullptr), + commandHistory(nullptr), + + configFirstTime(false), + configShowGrid(false), + configShowPath(false), + configThumbnailShown(false), + configZoomedThumbnail(false), + + documentEnvironment(nullptr), + commandEnvironment(nullptr), + + // Tools + + toolSelectionEnvironment(nullptr), + toolsActionGroup(nullptr), + + toolSpraycan(nullptr), + toolBrush(nullptr), + toolColorEraser(nullptr), + toolColorPicker(nullptr), + toolCurve(nullptr), + toolEllipse(nullptr), + toolEllipticalSelection(nullptr), + toolEraser(nullptr), + toolFloodFill(nullptr), + toolFreeFormSelection(nullptr), + toolLine(nullptr), + toolPen(nullptr), + toolPolygon(nullptr), + toolPolyline(nullptr), + toolRectangle(nullptr), + toolRectSelection(nullptr), + toolRoundedRectangle(nullptr), + toolZoom(nullptr), + toolText(nullptr), + + lastToolNumber(0), + toolActionsEnabled(false), + actionPrevToolOptionGroup1(nullptr), + actionNextToolOptionGroup1(nullptr), + actionPrevToolOptionGroup2(nullptr), + actionNextToolOptionGroup2(nullptr), + + settingImageSelectionTransparency(0), + + docResizeWidth(0), + docResizeHeight(0), + docResizeToBeCompleted(false), + + configOpenImagesInSameWindow(false), + configPrintImageCenteredOnPage(false), + + actionNew(nullptr), + actionOpen(nullptr), + actionOpenRecent(nullptr), + actionScan(nullptr), + actionScreenshot(nullptr), + actionProperties(nullptr), + actionSave(nullptr), + actionSaveAs(nullptr), + actionExport(nullptr), + actionReload(nullptr), + actionPrint(nullptr), + actionPrintPreview(nullptr), + actionMail(nullptr), + actionClose(nullptr), + actionQuit(nullptr), + + scanDialog(nullptr), + + exportFirstTime(false), + + // Edit Menu + + editMenuDocumentActionsEnabled(false), + + actionUndo(nullptr), + actionRedo(nullptr), + actionCut(nullptr), + actionCopy(nullptr), + actionPaste(nullptr), + actionPasteInNewWindow(nullptr), + actionDelete(nullptr), + actionSelectAll(nullptr), + actionDeselect(nullptr), + actionCopyToFile(nullptr), + actionPasteFromFile(nullptr), + + copyToFirstTime(false), + + // View Menu + + configThumbnailShowRectangle(false), + actionShowThumbnailRectangle(nullptr), + + viewMenuDocumentActionsEnabled(false), + + actionActualSize(nullptr), + actionFitToPage(nullptr), + actionFitToWidth(nullptr), + actionFitToHeight(nullptr), + actionZoomIn(nullptr), + actionZoomOut(nullptr), + actionZoom(nullptr), + actionShowGrid(nullptr), + actionShowThumbnail(nullptr), + actionZoomedThumbnail(nullptr), + + thumbnailSaveConfigTimer(nullptr), + + // Image Menu + + transformDialogEnvironment(nullptr), + + imageMenuDocumentActionsEnabled(false), + + actionResizeScale(nullptr), + actionCrop(nullptr), + actionAutoCrop(nullptr), + actionFlip(nullptr), + actionMirror(nullptr), + actionRotate(nullptr), + actionRotateLeft(nullptr), + actionRotateRight(nullptr), + actionSkew(nullptr), + actionConvertToBlackAndWhite(nullptr), + actionConvertToGrayscale(nullptr), + actionBlur(nullptr), + actionMoreEffects(nullptr), + actionInvertColors(nullptr), + actionClear(nullptr), + + actionDrawOpaque(nullptr), + actionDrawColorSimilarity(nullptr), + + moreEffectsDialogLastEffect(0), + + // Colors Menu + + colorMenuDocumentActionsEnabled(false), + + actionColorsDefault(nullptr), + actionColorsKDE(nullptr), + actionColorsOpen(nullptr), + actionColorsReload(nullptr), + actionColorsSave(nullptr), + actionColorsSaveAs(nullptr), + actionColorsAppendRow(nullptr), + actionColorsDeleteRow(nullptr), + + // Settings Menu + + actionShowPath(nullptr), + actionKeyBindings(nullptr), + actionConfigureToolbars(nullptr), + actionConfigure(nullptr), + actionFullScreen(nullptr), + + // Status Bar + + statusBarCreated(false), + statusBarMessageLabel(nullptr), + statusBarShapeLastPointsInitialised(false), + statusBarShapeLastSizeInitialised(false), + + // Text ToolBar + + actionTextFontFamily(nullptr), + actionTextFontSize(nullptr), + actionTextBold(nullptr), + actionTextItalic(nullptr), + actionTextUnderline(nullptr), + actionTextStrikeThru(nullptr), + settingTextStyle(0), + textOldFontSize(0) + { + } + + bool isFullyConstructed; + + kpViewScrollableContainer *scrollView; + kpZoomedView *mainView; + kpThumbnail *thumbnail; + kpThumbnailView *thumbnailView; + kpDocument *document; + kpViewManager *viewManager; + kpColorToolBar *colorToolBar; + kpToolToolBar *toolToolBar; + kpCommandHistory *commandHistory; + + bool configFirstTime; + bool configShowGrid; + bool configShowPath; + + bool configThumbnailShown; + QRect configThumbnailGeometry; + bool configZoomedThumbnail; + + kpDocumentEnvironment *documentEnvironment; + kpCommandEnvironment *commandEnvironment; + + // + // Tools + // + + kpToolSelectionEnvironment *toolSelectionEnvironment; + QActionGroup *toolsActionGroup; + + kpTool *toolSpraycan, *toolBrush, + *toolColorEraser, *toolColorPicker, + *toolCurve, *toolEllipse, + *toolEllipticalSelection, *toolEraser, + *toolFloodFill, *toolFreeFormSelection, + *toolLine, *toolPen, *toolPolygon, + *toolPolyline, *toolRectangle, *toolRectSelection, + *toolRoundedRectangle, *toolZoom; + kpToolText *toolText; + + QList tools; + int lastToolNumber; + + bool toolActionsEnabled; + QAction *actionPrevToolOptionGroup1, + *actionNextToolOptionGroup1, + *actionPrevToolOptionGroup2, + *actionNextToolOptionGroup2; + + int settingImageSelectionTransparency; + + int docResizeWidth, docResizeHeight; + bool docResizeToBeCompleted; + + // + // File Menu + // + + bool configOpenImagesInSameWindow, configPrintImageCenteredOnPage; + + QAction *actionNew, *actionOpen; + KRecentFilesAction *actionOpenRecent; + QAction *actionScan, *actionScreenshot, *actionProperties, + *actionSave, *actionSaveAs, *actionExport, + *actionReload, + *actionPrint, *actionPrintPreview, + *actionMail, + *actionClose, *actionQuit; + + SaneDialog *scanDialog; + + QUrl lastExportURL; + kpDocumentSaveOptions lastExportSaveOptions; + bool exportFirstTime; + + // + // Edit Menu + // + + bool editMenuDocumentActionsEnabled; + + QAction *actionUndo, *actionRedo, + *actionCut, *actionCopy, + *actionPaste, *actionPasteInNewWindow, + *actionDelete, + *actionSelectAll, *actionDeselect, + *actionCopyToFile, *actionPasteFromFile; + + QUrl lastCopyToURL; + kpDocumentSaveOptions lastCopyToSaveOptions; + bool copyToFirstTime; + + // + // View Menu + // + + bool configThumbnailShowRectangle; + KToggleAction *actionShowThumbnailRectangle; + + bool viewMenuDocumentActionsEnabled; + + QAction *actionActualSize, + *actionFitToPage, *actionFitToWidth, *actionFitToHeight, + *actionZoomIn, *actionZoomOut; + KSelectAction *actionZoom; + KToggleAction *actionShowGrid, + *actionShowThumbnail, *actionZoomedThumbnail; + + QList zoomList; + + QTimer *thumbnailSaveConfigTimer; + + // + // Image Menu + // + + kpTransformDialogEnvironment *transformDialogEnvironment; + + bool imageMenuDocumentActionsEnabled; + + QAction *actionResizeScale, + *actionCrop, *actionAutoCrop, + *actionFlip, *actionMirror, + *actionRotate, *actionRotateLeft, *actionRotateRight, + *actionSkew, + *actionConvertToBlackAndWhite, *actionConvertToGrayscale, + *actionBlur, *actionMoreEffects, + *actionInvertColors, *actionClear; + + // Implemented in kpMainWindow_Tools.cpp, not kpImageWindow_Image.cpp + // since they're really setting tool options. + KToggleAction *actionDrawOpaque; + QAction *actionDrawColorSimilarity; + + int moreEffectsDialogLastEffect; + + // + // Colors Menu + // + + bool colorMenuDocumentActionsEnabled; + + QAction *actionColorsDefault; + KSelectAction *actionColorsKDE; + QAction *actionColorsOpen, *actionColorsReload; + + QAction *actionColorsSave, *actionColorsSaveAs; + + QAction *actionColorsAppendRow; + QAction *actionColorsDeleteRow; + + // + // Settings Menu + // + + KToggleAction *actionShowPath; + QAction *actionKeyBindings, *actionConfigureToolbars, *actionConfigure; + KToggleFullScreenAction *actionFullScreen; + + // + // Status Bar + // + + bool statusBarCreated; + KSqueezedTextLabel *statusBarMessageLabel; + QList statusBarLabels; + + bool statusBarShapeLastPointsInitialised; + QPoint statusBarShapeLastStartPoint, statusBarShapeLastEndPoint; + bool statusBarShapeLastSizeInitialised; + QSize statusBarShapeLastSize; + + // + // Text ToolBar + // + + KFontAction *actionTextFontFamily; + KFontSizeAction *actionTextFontSize; + KToggleAction *actionTextBold, *actionTextItalic, + *actionTextUnderline, *actionTextStrikeThru; + + int settingTextStyle; + QString textOldFontFamily; + int textOldFontSize; +}; + + +#endif // kpMainWindowPrivate_H diff --git a/mainWindow/kpMainWindow_Colors.cpp b/mainWindow/kpMainWindow_Colors.cpp new file mode 100644 index 0000000..172a0ab --- /dev/null +++ b/mainWindow/kpMainWindow_Colors.cpp @@ -0,0 +1,495 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "kpMainWindow.h" +#include "kpMainWindowPrivate.h" + +#include "widgets/kpColorCells.h" +#include "lgpl/generic/kpColorCollection.h" +#include "lgpl/generic/kpUrlFormatter.h" +#include "widgets/toolbars/kpColorToolBar.h" + +#include +#include +#include +#include +#include "kpLogCategories.h" + +#include +#include + +//--------------------------------------------------------------------- + +static QStringList KDEColorCollectionNames () +{ + return kpColorCollection::installedCollections (); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::setupColorsMenuActions () +{ + KActionCollection *ac = actionCollection (); + + + d->actionColorsDefault = ac->addAction (QStringLiteral("colors_default")); + d->actionColorsDefault->setText (i18n ("Use KolourPaint Defaults")); + connect (d->actionColorsDefault, &QAction::triggered, + this, &kpMainWindow::slotColorsDefault); + + d->actionColorsKDE = ac->add (QStringLiteral("colors_kde")); + d->actionColorsKDE->setText (i18nc ("@item:inmenu colors", "Use KDE's")); + // TODO: Will this slot be called spuriously if there are no colors + // installed? + connect (d->actionColorsKDE, + static_cast(&KSelectAction::triggered), + this, &kpMainWindow::slotColorsKDE); + + for (const auto &colName : ::KDEColorCollectionNames ()) { + d->actionColorsKDE->addAction (colName); + } + + d->actionColorsOpen = ac->addAction (QStringLiteral("colors_open")); + d->actionColorsOpen->setText (i18nc ("@item:inmenu colors", "&Open...")); + connect (d->actionColorsOpen, &QAction::triggered, this, &kpMainWindow::slotColorsOpen); + + d->actionColorsReload = ac->addAction (QStringLiteral("colors_reload")); + d->actionColorsReload->setText (i18nc ("@item:inmenu colors", "Reloa&d")); + connect (d->actionColorsReload, &QAction::triggered, + this, &kpMainWindow::slotColorsReload); + + d->actionColorsSave = ac->addAction (QStringLiteral("colors_save")); + d->actionColorsSave->setText (i18nc ("@item:inmenu colors", "&Save")); + connect (d->actionColorsSave, &QAction::triggered, + this, &kpMainWindow::slotColorsSave); + + d->actionColorsSaveAs = ac->addAction (QStringLiteral("colors_save_as")); + d->actionColorsSaveAs->setText (i18nc ("@item:inmenu colors", "Save &As...")); + connect (d->actionColorsSaveAs, &QAction::triggered, + this, &kpMainWindow::slotColorsSaveAs); + + d->actionColorsAppendRow = ac->addAction (QStringLiteral("colors_append_row")); + d->actionColorsAppendRow->setText (i18nc ("@item:inmenu colors", "Add Row")); + connect (d->actionColorsAppendRow, &QAction::triggered, + this, &kpMainWindow::slotColorsAppendRow); + + d->actionColorsDeleteRow = ac->addAction (QStringLiteral("colors_delete_row")); + d->actionColorsDeleteRow->setText (i18nc ("@item:inmenu colors", "Delete Last Row")); + connect (d->actionColorsDeleteRow, &QAction::triggered, + this, &kpMainWindow::slotColorsDeleteRow); + + + enableColorsMenuDocumentActions (false); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::createColorBox () +{ + d->colorToolBar = new kpColorToolBar (i18n ("Color Box"), this); + + // (needed for QMainWindow::saveState()) + d->colorToolBar->setObjectName ( QStringLiteral("Color Box" )); + + connect (colorCells (), &kpColorCells::rowCountChanged, + this, &kpMainWindow::slotUpdateColorsDeleteRowActionEnabled); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::enableColorsMenuDocumentActions (bool enable) +{ + d->actionColorsDefault->setEnabled (enable); + d->actionColorsKDE->setEnabled (enable); + d->actionColorsOpen->setEnabled (enable); + d->actionColorsReload->setEnabled (enable); + + d->actionColorsSave->setEnabled (enable); + d->actionColorsSaveAs->setEnabled (enable); + + d->actionColorsAppendRow->setEnabled (enable); + + d->colorMenuDocumentActionsEnabled = enable; + + slotUpdateColorsDeleteRowActionEnabled (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotUpdateColorsDeleteRowActionEnabled () +{ + // Currently, this is always enabled since kpColorCells guarantees that + // there will be at least one row of cells (which might all be of the + // invalid color). + // + // But this method is left here for future extensibility. + d->actionColorsDeleteRow->setEnabled ( + d->colorMenuDocumentActionsEnabled && (colorCells ()->rowCount () > 0)); +} + +//--------------------------------------------------------------------- + + +// Used in 2 situations: +// +// 1. User opens a color without using the "Use KDE's" submenu. +// 2. User attempts to open a color using the "Use KDE's" submenu but the +// opening fails. +// +// TODO: Maybe we could put the 3 actions (for different ways of opening +// colors) in an exclusive group -- this might eliminate the need for +// this hack. +// +// private +void kpMainWindow::deselectActionColorsKDE () +{ + d->actionColorsKDE->setCurrentItem (-1); +} + +//--------------------------------------------------------------------- + + +// private +bool kpMainWindow::queryCloseColors () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::queryCloseColors() colorCells.modified=" + << colorCells ()->isModified (); +#endif + + toolEndShape (); + + if (!colorCells ()->isModified ()) { + return true; // ok to close + } + + int result = KMessageBox::Cancel; + + + if (!colorCells ()->url ().isEmpty ()) + { + result = KMessageBox::warningYesNoCancel (this, + i18n ("The color palette \"%1\" has been modified.\n" + "Do you want to save it?", + kpUrlFormatter::PrettyFilename (colorCells ()->url ())), + QString ()/*caption*/, + KStandardGuiItem::save (), KStandardGuiItem::discard ()); + } + else + { + const QString name = colorCells ()->colorCollection ()->name (); + if (!name.isEmpty ()) + { + result = KMessageBox::warningYesNoCancel (this, + i18n ("The KDE color palette \"%1\" has been modified.\n" + "Do you want to save it to a file?", + name), + QString ()/*caption*/, + KStandardGuiItem::save (), KStandardGuiItem::discard ()); + } + else + { + result = KMessageBox::warningYesNoCancel (this, + i18n ("The default color palette has been modified.\n" + "Do you want to save it to a file?"), + QString ()/*caption*/, + KStandardGuiItem::save (), KStandardGuiItem::discard ()); + } + } + + switch (result) + { + case KMessageBox::Yes: + return slotColorsSave (); // close only if save succeeds + case KMessageBox::No: + return true; // close without saving + default: + return false; // don't close current doc + } +} + +//--------------------------------------------------------------------- + + +// private +void kpMainWindow::openDefaultColors () +{ + colorCells ()->setColorCollection ( + kpColorCells::DefaultColorCollection ()); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotColorsDefault () +{ + // Call just in case. + toolEndShape (); + + if (!queryCloseColors ()) { + return; + } + + openDefaultColors (); + + deselectActionColorsKDE (); +} + +//--------------------------------------------------------------------- + +// private +bool kpMainWindow::openKDEColors (const QString &name) +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::openKDEColors(" << name << ")"; +#endif + + kpColorCollection colorCol; + if (colorCol.openKDE (name, this)) + { + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "opened"; + #endif + colorCells ()->setColorCollection (colorCol); + return true; + } + else + { + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "failed to open"; + #endif + return false; + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotColorsKDE () +{ + // Call in case an error dialog appears. + toolEndShape (); + + const int curItem = d->actionColorsKDE->currentItem (); + + if (!queryCloseColors ()) + { + deselectActionColorsKDE (); + return; + } + + // queryCloseColors() calls slotColorSave(), which can call + // slotColorSaveAs(), which can call deselectActionColorsKDE(). + d->actionColorsKDE->setCurrentItem (curItem); + + const QStringList colNames = ::KDEColorCollectionNames (); + const int selected = d->actionColorsKDE->currentItem (); + Q_ASSERT (selected >= 0 && selected < colNames.size ()); + + if (!openKDEColors (colNames [selected])) { + deselectActionColorsKDE (); + } +} + +//--------------------------------------------------------------------- + +// private +bool kpMainWindow::openColors (const QUrl &url) +{ + return colorCells ()->openColorCollection (url); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotColorsOpen () +{ + // Call due to dialog. + toolEndShape (); + + QFileDialog fd(this); + fd.setDirectoryUrl(colorCells ()->url()); + fd.setWindowTitle(i18nc ("@title:window", "Open Color Palette")); + + if (fd.exec ()) + { + if (!queryCloseColors ()) { + return; + } + + QList selected = fd.selectedUrls(); + if ( selected.count() && openColors(selected[0]) ) { + deselectActionColorsKDE(); + } + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotColorsReload () +{ + toolEndShape (); + + if (colorCells ()->isModified ()) + { + int result = KMessageBox::Cancel; + + if (!colorCells ()->url ().isEmpty ()) + { + result = KMessageBox::warningContinueCancel (this, + i18n ("The color palette \"%1\" has been modified.\n" + "Reloading will lose all changes since you last saved it.\n" + "Are you sure?", + kpUrlFormatter::PrettyFilename (colorCells ()->url ())), + QString ()/*caption*/, + KGuiItem(i18n ("&Reload"))); + } + else + { + const QString name = colorCells ()->colorCollection ()->name (); + if (!name.isEmpty ()) + { + result = KMessageBox::warningContinueCancel (this, + i18n ("The KDE color palette \"%1\" has been modified.\n" + "Reloading will lose all changes.\n" + "Are you sure?", + colorCells ()->colorCollection ()->name ()), + QString ()/*caption*/, + KGuiItem (i18n ("&Reload"))); + } + else + { + result = KMessageBox::warningContinueCancel (this, + i18n ("The default color palette has been modified.\n" + "Reloading will lose all changes.\n" + "Are you sure?"), + QString ()/*caption*/, + KGuiItem (i18n ("&Reload"))); + } + } + + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "result=" << result + << "vs KMessageBox::Continue" << KMessageBox::Continue; + #endif + if (result != KMessageBox::Continue) { + return; + } + } + + + if (!colorCells ()->url ().isEmpty ()) + { + openColors (colorCells ()->url ()); + } + else + { + const QString name = colorCells ()->colorCollection ()->name (); + if (!name.isEmpty ()) { + openKDEColors (name); + } + else { + openDefaultColors (); + } + } +} + +//--------------------------------------------------------------------- + + +// private slot +bool kpMainWindow::slotColorsSave () +{ + // Call due to dialog. + toolEndShape (); + + if (colorCells ()->url ().isEmpty ()) + { + return slotColorsSaveAs (); + } + + return colorCells ()->saveColorCollection (); +} + +//--------------------------------------------------------------------- + +// private slot +bool kpMainWindow::slotColorsSaveAs () +{ + // Call due to dialog. + toolEndShape (); + + QFileDialog fd(this); + fd.setDirectoryUrl(colorCells ()->url()); + fd.setWindowTitle(i18n("Save Color Palette As")); + fd.setAcceptMode(QFileDialog::AcceptSave); + // Note that QFileDialog takes care of asking the user to confirm overwriting. + + if (fd.exec ()) + { + QList selected = fd.selectedUrls(); + if ( !selected.count() || !colorCells ()->saveColorCollectionAs(selected[0]) ) { + return false; + } + + // We're definitely using our own color collection now. + deselectActionColorsKDE (); + + return true; + } + + return false; +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotColorsAppendRow () +{ + // Call just in case. + toolEndShape (); + + kpColorCells *colorCells = d->colorToolBar->colorCells (); + colorCells->appendRow (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotColorsDeleteRow () +{ + // Call just in case. + toolEndShape (); + + kpColorCells *colorCells = d->colorToolBar->colorCells (); + colorCells->deleteLastRow (); +} diff --git a/mainWindow/kpMainWindow_Edit.cpp b/mainWindow/kpMainWindow_Edit.cpp new file mode 100644 index 0000000..7ccd9e7 --- /dev/null +++ b/mainWindow/kpMainWindow_Edit.cpp @@ -0,0 +1,923 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2011 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "kpMainWindow.h" +#include "kpMainWindowPrivate.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kpLogCategories.h" +#include +#include +#include +#include +#include + +#include "layers/selections/image/kpAbstractImageSelection.h" +#include "widgets/toolbars/kpColorToolBar.h" +#include "commands/kpCommandHistory.h" +#include "document/kpDocument.h" +#include "imagelib/kpDocumentMetaInfo.h" +#include "document/kpDocumentSaveOptions.h" +#include "layers/selections/image/kpImageSelectionTransparency.h" +#include "commands/kpMacroCommand.h" +#include "pixmapfx/kpPixmapFX.h" +#include "layers/selections/image/kpRectangularImageSelection.h" +#include "layers/selections/kpSelectionDrag.h" +#include "generic/kpSetOverrideCursorSaver.h" +#include "layers/selections/text/kpTextSelection.h" +#include "tools/kpTool.h" +#include "commands/tools/selection/text/kpToolTextGiveContentCommand.h" +#include "commands/tools/selection/kpToolSelectionCreateCommand.h" +#include "commands/tools/selection/kpToolSelectionDestroyCommand.h" +#include "commands/tools/selection/text/kpToolTextEnterCommand.h" +#include "commands/tools/selection/text/kpToolTextInsertCommand.h" +#include "imagelib/transforms/kpTransformCrop.h" +#include "commands/imagelib/transforms/kpTransformResizeScaleCommand.h" +#include "views/manager/kpViewManager.h" +#include "kpViewScrollableContainer.h" +#include "views/kpZoomedView.h" + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::setupEditMenuActions () +{ + KActionCollection *ac = actionCollection (); + + + // Undo/Redo + // CONFIG: Need GUI for config history size. + d->commandHistory = new kpCommandHistory (true/*read config*/, this); + + if (d->configFirstTime) + { + // (so that cfg-file-editing user can modify in the meantime) + d->commandHistory->writeConfig (); + } + + + d->actionCut = KStandardAction::cut (this, SLOT (slotCut()), ac); + d->actionCopy = KStandardAction::copy (this, SLOT (slotCopy()), ac); + d->actionPaste = KStandardAction::paste (this, SLOT (slotPaste()), ac); + d->actionPasteInNewWindow = ac->addAction (QStringLiteral("edit_paste_in_new_window")); + d->actionPasteInNewWindow->setText (i18n ("Paste in &New Window")); + connect (d->actionPasteInNewWindow, &QAction::triggered, + this, &kpMainWindow::slotPasteInNewWindow); + ac->setDefaultShortcut (d->actionPasteInNewWindow, Qt::CTRL | Qt::SHIFT | Qt::Key_V); + + //d->actionDelete = KStandardAction::clear (this, SLOT (slotDelete()), ac); + d->actionDelete = ac->addAction (QStringLiteral("edit_clear")); + d->actionDelete->setText (i18n ("&Delete Selection")); + connect (d->actionDelete, &QAction::triggered, this, &kpMainWindow::slotDelete); + + d->actionSelectAll = KStandardAction::selectAll (this, SLOT (slotSelectAll()), ac); + d->actionDeselect = KStandardAction::deselect (this, SLOT (slotDeselect()), ac); + + + d->actionCopyToFile = ac->addAction (QStringLiteral("edit_copy_to_file")); + d->actionCopyToFile->setText (i18n ("C&opy to File...")); + connect (d->actionCopyToFile, &QAction::triggered, this, &kpMainWindow::slotCopyToFile); + + d->actionPasteFromFile = ac->addAction (QStringLiteral("edit_paste_from_file")); + d->actionPasteFromFile->setText (i18n ("Paste &From File...")); + connect (d->actionPasteFromFile, &QAction::triggered, this, &kpMainWindow::slotPasteFromFile); + + + d->editMenuDocumentActionsEnabled = false; + enableEditMenuDocumentActions (false); + + // Paste should always be enabled, as long as there is something to paste + // (independent of whether we have a document or not) + connect (QApplication::clipboard(), &QClipboard::dataChanged, + this, &kpMainWindow::slotEnablePaste); + + slotEnablePaste (); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::enableEditMenuDocumentActions (bool enable) +{ + // d->actionCut + // d->actionCopy + // d->actionPaste + // d->actionPasteInNewWindow + + // d->actionDelete + + d->actionSelectAll->setEnabled (enable); + // d->actionDeselect + + d->editMenuDocumentActionsEnabled = enable; + + // d->actionCopyToFile + + // Unlike d->actionPaste, we disable this if there is no document. + // This is because "File / Open" would do the same thing, if there is + // no document. + d->actionPasteFromFile->setEnabled (enable); +} + +//--------------------------------------------------------------------- + +// public +QMenu *kpMainWindow::selectionToolRMBMenu () +{ + return qobject_cast (guiFactory ()->container (QStringLiteral("selectionToolRMBMenu"), this)); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotCut () +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "kpMainWindow::slotCut() CALLED"; +#endif + + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + Q_ASSERT (d->document && d->document->selection ()); + + toolEndShape (); + + slotCopy (); + slotDelete (); +} + +//--------------------------------------------------------------------- + +static QMimeData *NewTextMimeData (const QString &text) +{ + auto *md = new QMimeData (); + md->setText (text); + return md; +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotCopy () +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "kpMainWindow::slotCopy() CALLED"; +#endif + + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + Q_ASSERT (d->document && d->document->selection ()); + + toolEndShape (); + + kpAbstractSelection *sel = d->document->selection ()->clone (); + + if (dynamic_cast (sel)) + { + auto *textSel = dynamic_cast (sel); + if (!textSel->text ().isEmpty ()) + { + QApplication::clipboard ()->setMimeData ( + ::NewTextMimeData (textSel->text ()), + QClipboard::Clipboard); + + // SYNC: Normally, users highlight text and press CTRL+C. + // Highlighting text copies it to the X11 "middle + // mouse button" clipboard. CTRL+C copies it to the + // separate, Windows-like "CTRL+V" clipboard. + // + // However, KolourPaint doesn't support highlighting. + // So when they press CTRL+C to copy all text, simulate + // the highlighting by copying the text to the "middle + // mouse button" clipboard. We don't do this for images + // as no one ever middle-mouse-pastes images. + // + // Note that we don't share the QMimeData pointer with + // the above in case Qt doesn't expect it. + // + // Once we change KolourPaint to support highlighted text + // and CTRL+C to copy only the highlighted text, delete + // this code. + QApplication::clipboard ()->setMimeData ( + ::NewTextMimeData (textSel->text ()), + QClipboard::Selection); + } + } + else if (dynamic_cast (sel)) + { + auto *imageSel = dynamic_cast (sel); + + // Transparency doesn't get sent across the aether so nuke it now + // so that transparency mask doesn't get needlessly recalculated + // if we ever call sel.setBaseImage(). + imageSel->setTransparency (kpImageSelectionTransparency ()); + + kpImage rawImage; + + if (imageSel->hasContent ()) { + rawImage = imageSel->baseImage (); + } + else { + rawImage = d->document->getSelectedBaseImage (); + } + + imageSel->setBaseImage ( rawImage ); + + QApplication::clipboard ()->setMimeData ( + new kpSelectionDrag (*imageSel), + QClipboard::Clipboard); + } + else { + Q_ASSERT (!"Unknown selection type"); + } + + delete sel; +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotEnablePaste () +{ + const QMimeData *md = + QApplication::clipboard()->mimeData(QClipboard::Clipboard); + + // It's faster to test for QMimeData::hasText() first due to the + // lazy evaluation of the '||' operator. + const bool shouldEnable = md && (md->hasText() || kpSelectionDrag::canDecode(md)); + + d->actionPasteInNewWindow->setEnabled(shouldEnable); + d->actionPaste->setEnabled(shouldEnable); +} + +//--------------------------------------------------------------------- + +// private +QRect kpMainWindow::calcUsefulPasteRect (int imageWidth, int imageHeight) +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "kpMainWindow::calcUsefulPasteRect(" + << imageWidth << "," << imageHeight + << ")"; +#endif + Q_ASSERT (d->document); + + // TODO: 1st choice is to paste sel near but not overlapping last deselect point + + if (d->mainView && d->scrollView) + { + const QPoint viewTopLeft (d->scrollView->horizontalScrollBar()->value (), + d->scrollView->verticalScrollBar()->value ()); + + const QPoint docTopLeft = d->mainView->transformViewToDoc (viewTopLeft); + + if ((docTopLeft.x () + imageWidth <= d->document->width () && + docTopLeft.y () + imageHeight <= d->document->height ()) || + imageWidth <= docTopLeft.x () || + imageHeight <= docTopLeft.y ()) + { + return {docTopLeft.x (), docTopLeft.y (), imageWidth, imageHeight}; + } + } + + return {0, 0, imageWidth, imageHeight}; +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::paste(const kpAbstractSelection &sel, bool forceTopLeft) +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "kpMainWindow::paste(forceTopLeft=" << forceTopLeft << ")"; +#endif + + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + toolEndShape (); + + // + // Make sure we've got a document (esp. with File/Close) + // + + if (!d->document) + { + auto *newDoc = new kpDocument ( + sel.width (), sel.height (), documentEnvironment ()); + + // will also create viewManager + setDocument (newDoc); + } + + // + // Paste as new selection + // + + const auto *imageSel = dynamic_cast (&sel); + + if (imageSel && imageSel->hasContent () && imageSel->transparency ().isTransparent ()) + { + d->colorToolBar->flashColorSimilarityToolBarItem (); + } + + kpAbstractSelection *selInUsefulPos = sel.clone (); + if (!forceTopLeft) { + selInUsefulPos->moveTo (calcUsefulPasteRect (sel.width (), sel.height ()).topLeft ()); + } + // TODO: Should use kpCommandHistory::addCreateSelectionCommand(), + // as well, to really support pasting selection borders. + addDeselectFirstCommand (new kpToolSelectionCreateCommand ( + dynamic_cast (selInUsefulPos) ? + i18n ("Text: Create Box") : + i18n ("Selection: Create"), + *selInUsefulPos, + commandEnvironment ())); + delete selInUsefulPos; + + +#if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "sel.size=" << QSize (sel.width (), sel.height ()) + << " document.size=" + << QSize (d->document->width (), d->document->height ()); +#endif + + // If the selection is bigger than the document, automatically + // resize the document (with the option of Undo'ing) to fit + // the selection. + // + // No annoying dialog necessary. + // + if (sel.width () > d->document->width () || + sel.height () > d->document->height ()) + { + d->commandHistory->addCommand ( + new kpTransformResizeScaleCommand ( + false/*act on doc, not sel*/, + qMax (sel.width (), d->document->width ()), + qMax (sel.height (), d->document->height ()), + kpTransformResizeScaleCommand::Resize, + commandEnvironment ())); + } +} + +//--------------------------------------------------------------------- + +// public +void kpMainWindow::pasteText (const QString &text, + bool forceNewTextSelection, + const QPoint &newTextSelectionTopLeft) +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "kpMainWindow::pasteText(" << text + << ",forceNewTextSelection=" << forceNewTextSelection + << ",newTextSelectionTopLeft=" << newTextSelectionTopLeft + << ")"; +#endif + + if ( text.isEmpty() ) { + return; + } + + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + toolEndShape (); + + QStringList textLines = text.split('\n'); + + if (!forceNewTextSelection && + d->document && d->document->textSelection () && + d->commandHistory && d->viewManager) + { + #if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "\treusing existing Text Selection"; + #endif + + d->viewManager->setQueueUpdates(); + + kpTextSelection *textSel = d->document->textSelection (); + if (!textSel->hasContent ()) + { + #if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "\t\tneeds content"; + #endif + commandHistory ()->addCreateSelectionCommand ( + new kpToolSelectionCreateCommand ( + i18n ("Text: Create Box"), + *textSel, + commandEnvironment ()), + false/*no exec*/); + } + + kpMacroCommand *macroCmd = new kpMacroCommand (i18n ("Text: Paste"), + commandEnvironment ()); + // (yes, this is the same check as the previous "if") + if (!textSel->hasContent ()) + { + kpCommand *giveContentCmd = new kpToolTextGiveContentCommand ( + *textSel, + QString ()/*uninteresting child of macro cmd*/, + commandEnvironment ()); + giveContentCmd->execute (); + + macroCmd->addCommand (giveContentCmd); + } + + for (int i = 0; i < textLines.size(); i++) + { + if (i > 0) + { + macroCmd->addCommand ( + new kpToolTextEnterCommand ( + QString()/*uninteresting child of macroCmd*/, + d->viewManager->textCursorRow (), + d->viewManager->textCursorCol (), + kpToolTextEnterCommand::AddEnterNow, + commandEnvironment ())); + } + + macroCmd->addCommand ( + new kpToolTextInsertCommand ( + QString()/*uninteresting child of macroCmd*/, + d->viewManager->textCursorRow (), + d->viewManager->textCursorCol (), + textLines [i], + commandEnvironment ())); + } + + d->commandHistory->addCommand (macroCmd, false/*no exec*/); + + d->viewManager->restoreQueueUpdates(); + } + else + { + #if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "\tcreating Text Selection"; + #endif + + const kpTextStyle ts = textStyle (); + const QFontMetrics fontMetrics = ts.fontMetrics (); + + int height = textLines.size () * fontMetrics.height (); + if (textLines.size () >= 1) { + height += (textLines.size () - 1) * fontMetrics.leading (); + } + + int width = 0; + foreach (const QString &str, textLines) + width = std::max(width, fontMetrics.horizontalAdvance(str)); + + // limit the size to avoid memory overflow + width = qMin(qMax(QApplication::desktop()->width(), d->document ? d->document->width() : 0), width); + height = qMin(qMax(QApplication::desktop()->height(), d->document ? d->document->height() : 0), height); + + const int selWidth = qMax (kpTextSelection::MinimumWidthForTextStyle (ts), + width + kpTextSelection::TextBorderSize () * 2); + const int selHeight = qMax (kpTextSelection::MinimumHeightForTextStyle (ts), + height + kpTextSelection::TextBorderSize () * 2); + kpTextSelection newTextSel (QRect (0, 0, selWidth, selHeight), + textLines, + ts); + + if (newTextSelectionTopLeft != KP_INVALID_POINT) + { + newTextSel.moveTo (newTextSelectionTopLeft); + paste (newTextSel, true/*force topLeft*/); + } + else + { + paste (newTextSel); + } + } +} + +//--------------------------------------------------------------------- + +// public +void kpMainWindow::pasteTextAt (const QString &text, const QPoint &point, + bool allowNewTextSelectionPointShift) +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "kpMainWindow::pasteTextAt(" << text + << ",point=" << point + << ",allowNewTextSelectionPointShift=" + << allowNewTextSelectionPointShift + << ")"; +#endif + + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + toolEndShape (); + + + if (d->document && + d->document->textSelection () && + d->document->textSelection ()->pointIsInTextArea (point)) + { + kpTextSelection *textSel = d->document->textSelection (); + + int row, col; + + if (textSel->hasContent ()) + { + row = textSel->closestTextRowForPoint (point); + col = textSel->closestTextColForPoint (point); + } + else + { + row = col = 0; + } + + d->viewManager->setTextCursorPosition (row, col); + + pasteText (text); + } + else + { + QPoint pointToUse = point; + + if (allowNewTextSelectionPointShift) + { + // TODO: In terms of doc pixels, would be inconsistent behaviour + // based on zoomLevel of view. + // pointToUse -= QPoint (-view->selectionResizeHandleAtomicSize (), + // -view->selectionResizeHandleAtomicSize ()); + } + + pasteText (text, true/*force new text selection*/, pointToUse); + } +} + +//--------------------------------------------------------------------- +// public slot + +void kpMainWindow::slotPaste() +{ + kpSetOverrideCursorSaver cursorSaver(Qt::WaitCursor); + + toolEndShape(); + + const QMimeData *mimeData = QApplication::clipboard()->mimeData(QClipboard::Clipboard); + + kpAbstractImageSelection *sel = kpSelectionDrag::decode(mimeData); + if ( sel ) + { + sel->setTransparency(imageSelectionTransparency()); + paste(*sel); + delete sel; + } + else if ( mimeData->hasText() ) + { + pasteText(mimeData->text()); + } + else + { + kpSetOverrideCursorSaver cursorSaver(Qt::ArrowCursor); + + KMessageBox::sorry(this, + i18n("KolourPaint cannot paste the contents of" + " the clipboard as it has an unknown format."), + i18n("Cannot Paste")); + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotPasteInNewWindow () +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "kpMainWindow::slotPasteInNewWindow() CALLED"; +#endif + + kpSetOverrideCursorSaver cursorSaver (Qt::WaitCursor); + + toolEndShape (); + + // + // Pasting must ensure that: + // + // Requirement 1. the document is the same size as the image to be pasted. + // Requirement 2. transparent pixels in the image must remain as transparent. + // + + auto *win = new kpMainWindow (nullptr/*no document*/); + win->show (); + + // Make "Edit / Paste in New Window" always paste white pixels as white. + // Don't let selection transparency get in the way and paste them as + // transparent. + kpImageSelectionTransparency transparency = win->imageSelectionTransparency (); + if (transparency.isTransparent ()) + { + #if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "\tchanging image selection transparency to opaque"; + #endif + transparency.setOpaque (); + // Since we are setting selection transparency programmatically + // -- as opposed to in response to user input -- this will not + // affect the selection transparency tool option widget's "last used" + // config setting. + win->setImageSelectionTransparency (transparency); + } + + // (this handles Requirement 1. above) + win->slotPaste (); + + // if slotPaste could not decode clipboard data, no document was created + if ( win->document() ) + { + // (this handles Requirement 2. above; + // slotDeselect() is not enough unless the document is filled with the + // transparent color in advance) + win->slotCrop(); + } +} + +//--------------------------------------------------------------------- + +// public slot +void kpMainWindow::slotDelete () +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "kpMainWindow::slotDelete() CALLED"; +#endif + if (!d->actionDelete->isEnabled ()) + { + #if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "\taction not enabled - was probably called from kpTool::keyPressEvent()"; + #endif + return; + } + + Q_ASSERT (d->document && d->document->selection ()); + + toolEndShape (); + + addImageOrSelectionCommand (new kpToolSelectionDestroyCommand ( + d->document->textSelection () ? + i18n ("Text: Delete Box") : // not to be confused with i18n ("Text: Delete") + i18n ("Selection: Delete"), + false/*no push onto doc*/, + commandEnvironment ())); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotSelectAll () +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "kpMainWindow::slotSelectAll() CALLED"; +#endif + Q_ASSERT (d->document); + + toolEndShape (); + + if (d->document->selection ()) { + slotDeselect (); + } + + // just the border - don't actually pull image from doc yet + d->document->setSelection ( + kpRectangularImageSelection (d->document->rect (), + imageSelectionTransparency ())); + + if (tool ()) { + tool ()->somethingBelowTheCursorChanged (); + } +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::addDeselectFirstCommand (kpCommand *cmd) +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "kpMainWindow::addDeselectFirstCommand(" + << cmd + << ")"; +#endif + + + kpAbstractSelection *sel = d->document->selection (); + +#if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "\tsel=" << sel; +#endif + + if (sel) + { + // if you just dragged out something with no action then + // forget the drag + if (!sel->hasContent ()) + { + #if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "\tjust a fresh border - was nop - delete"; + #endif + d->document->selectionDelete (); + if (tool ()) { + tool ()->somethingBelowTheCursorChanged (); + } + + if (cmd) { + d->commandHistory->addCommand (cmd); + } + } + else + { + #if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "\treal selection with image - push onto doc cmd"; + #endif + kpCommand *deselectCommand = new kpToolSelectionDestroyCommand ( + dynamic_cast (sel) ? + i18n ("Text: Finish") : + i18n ("Selection: Deselect"), + true/*push onto document*/, + commandEnvironment ()); + + if (cmd) + { + kpMacroCommand *macroCmd = new kpMacroCommand (cmd->name (), + commandEnvironment ()); + macroCmd->addCommand (deselectCommand); + macroCmd->addCommand (cmd); + d->commandHistory->addCommand (macroCmd); + } + else { + d->commandHistory->addCommand (deselectCommand); + } + } + } + else + { + if (cmd) { + d->commandHistory->addCommand (cmd); + } + } +} + +//--------------------------------------------------------------------- + +// public slot +void kpMainWindow::slotDeselect () +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "kpMainWindow::slotDeselect() CALLED"; +#endif + Q_ASSERT (d->document && d->document->selection ()); + + toolEndShape (); + + addDeselectFirstCommand (nullptr); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotCopyToFile () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotCopyToFile()"; +#endif + + toolEndShape (); + + + if (!d->document->selection ()) { + return; + } + + kpImage imageToSave; + + if (d->document->imageSelection ()) + { + kpAbstractImageSelection *imageSel = d->document->imageSelection (); + if (!imageSel->hasContent ()) + { + // Not a floating selection - user has just selected a region; + // haven't pulled it off yet so probably don't expect and can't + // visualize selection transparency so give opaque, not transparent + // image. + imageToSave = d->document->getSelectedBaseImage (); + } + else { + imageToSave = imageSel->transparentImage (); + } + } + else if (d->document->textSelection ()) + { + imageToSave = d->document->textSelection ()->approximateImage (); + } + else { + Q_ASSERT (!"Unknown selection type"); + } + + + kpDocumentSaveOptions chosenSaveOptions; + bool allowLossyPrompt; + QUrl chosenURL = askForSaveURL (i18nc ("@title:window", "Copy to File"), + d->lastCopyToURL.url (), + imageToSave, + d->lastCopyToSaveOptions, + kpDocumentMetaInfo (), + kpSettingsGroupEditCopyTo, + false/*allow remote files*/, + &chosenSaveOptions, + d->copyToFirstTime, + &allowLossyPrompt); + + if (chosenURL.isEmpty ()) { + return; + } + + + if (!kpDocument::savePixmapToFile (imageToSave, + chosenURL, + chosenSaveOptions, kpDocumentMetaInfo (), + allowLossyPrompt, + this)) + { + return; + } + + + addRecentURL (chosenURL); + + + d->lastCopyToURL = chosenURL; + d->lastCopyToSaveOptions = chosenSaveOptions; + + d->copyToFirstTime = false; +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotPasteFromFile () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotPasteFromFile()"; +#endif + + toolEndShape (); + + + QList urls = askForOpenURLs(i18nc ("@title:window", "Paste From File"), + false/*only 1 URL*/); + + if (urls.count () != 1) { + return; + } + + QUrl url = urls.first (); + + kpImage image = kpDocument::getPixmapFromFile (url, + false/*show error message if doesn't exist*/, + this); + + if (image.isNull ()) { + return; + } + + addRecentURL (url); + + paste (kpRectangularImageSelection ( + QRect (0, 0, image.width (), image.height ()), + image, + imageSelectionTransparency ())); +} + +//--------------------------------------------------------------------- diff --git a/mainWindow/kpMainWindow_File.cpp b/mainWindow/kpMainWindow_File.cpp new file mode 100644 index 0000000..55622d1 --- /dev/null +++ b/mainWindow/kpMainWindow_File.cpp @@ -0,0 +1,1510 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2007 John Layt + Copyright (c) 2007,2011,2015 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "kpMainWindow.h" +#include "kpMainWindowPrivate.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 "kpLogCategories.h" +#include "commands/kpCommandHistory.h" +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "commands/imagelib/kpDocumentMetaInfoCommand.h" +#include "dialogs/imagelib/kpDocumentMetaInfoDialog.h" +#include "widgets/kpDocumentSaveOptionsWidget.h" +#include "pixmapfx/kpPixmapFX.h" +#include "widgets/kpPrintDialogPage.h" +#include "views/kpView.h" +#include "views/manager/kpViewManager.h" + +#if HAVE_KSANE +#include "../scan/sanedialog.h" +#endif // HAVE_KSANE + +// private +void kpMainWindow::setupFileMenuActions () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::setupFileMenuActions()"; +#endif + KActionCollection *ac = actionCollection (); + + d->actionNew = KStandardAction::openNew (this, SLOT (slotNew()), ac); + d->actionOpen = KStandardAction::open (this, SLOT (slotOpen()), ac); + + d->actionOpenRecent = KStandardAction::openRecent(this, &kpMainWindow::slotOpenRecent, ac); + connect(d->actionOpenRecent, &KRecentFilesAction::recentListCleared, this, &kpMainWindow::slotRecentListCleared); + d->actionOpenRecent->loadEntries (KSharedConfig::openConfig ()->group (kpSettingsGroupRecentFiles)); +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\trecent URLs=" << d->actionOpenRecent->items (); +#endif + + d->actionSave = KStandardAction::save (this, SLOT (slotSave()), ac); + d->actionSaveAs = KStandardAction::saveAs (this, SLOT (slotSaveAs()), ac); + + d->actionExport = ac->addAction(QStringLiteral("file_export")); + d->actionExport->setText (i18n ("E&xport...")); + d->actionExport->setIcon(QIcon::fromTheme(QStringLiteral("document-export"))); + connect (d->actionExport, &QAction::triggered, this, &kpMainWindow::slotExport); + + d->actionScan = ac->addAction(QStringLiteral("file_scan")); + d->actionScan->setText(i18n ("Scan...")); + d->actionScan->setIcon(QIcon::fromTheme("scanner")); +#if HAVE_KSANE + connect (d->actionScan, &QAction::triggered, this, &kpMainWindow::slotScan); +#else + d->actionScan->setEnabled(false); +#endif // HAVE_KSANE + + d->actionScreenshot = ac->addAction(QStringLiteral("file_screenshot")); + d->actionScreenshot->setText(i18n("Acquire Screenshot")); + connect (d->actionScreenshot, &QAction::triggered, this, &kpMainWindow::slotScreenshot); + + d->actionProperties = ac->addAction (QStringLiteral("file_properties")); + d->actionProperties->setText (i18n ("Properties")); + d->actionProperties->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); + connect (d->actionProperties, &QAction::triggered, this, &kpMainWindow::slotProperties); + + //d->actionRevert = KStandardAction::revert (this, SLOT (slotRevert()), ac); + d->actionReload = ac->addAction (QStringLiteral("file_revert")); + d->actionReload->setText (i18n ("Reloa&d")); + d->actionReload->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); + connect (d->actionReload, &QAction::triggered, this, &kpMainWindow::slotReload); + ac->setDefaultShortcuts (d->actionReload, KStandardShortcut::reload ()); + slotEnableReload (); + + d->actionPrint = KStandardAction::print (this, SLOT (slotPrint()), ac); + d->actionPrintPreview = KStandardAction::printPreview (this, SLOT (slotPrintPreview()), ac); + + d->actionMail = KStandardAction::mail (this, SLOT (slotMail()), ac); + + d->actionClose = KStandardAction::close (this, SLOT (slotClose()), ac); + d->actionQuit = KStandardAction::quit (this, SLOT (slotQuit()), ac); + + d->scanDialog = nullptr; + + enableFileMenuDocumentActions (false); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::enableFileMenuDocumentActions (bool enable) +{ + // d->actionNew + // d->actionOpen + + // d->actionOpenRecent + + d->actionSave->setEnabled (enable); + d->actionSaveAs->setEnabled (enable); + + d->actionExport->setEnabled (enable); + + // d->actionScan + + d->actionProperties->setEnabled (enable); + + // d->actionReload + + d->actionPrint->setEnabled (enable); + d->actionPrintPreview->setEnabled (enable); + + d->actionMail->setEnabled (enable); + + d->actionClose->setEnabled (enable); + // d->actionQuit->setEnabled (enable); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::addRecentURL (const QUrl &url_) +{ + // HACK: KRecentFilesAction::loadEntries() clears the KRecentFilesAction::d->urls + // map. + // + // So afterwards, the URL ref, our method is given, points to an + // element in this now-cleared map (see KRecentFilesAction::urlSelected(QAction*)). + // Accessing it would result in a crash. + // + // To avoid the crash, make a copy of it before calling + // loadEntries() and use this copy, instead of the to-be-dangling + // ref. + const QUrl url = url_; // DO NOT MAKE IT A REFERENCE, THE CALL BELOW TO loadEntries DESTROYS url_ + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::addRecentURL(" << url << ")"; +#endif + if (url.isEmpty ()) + return; + + + KSharedConfig::Ptr cfg = KSharedConfig::openConfig(); + + // KConfig::readEntry() does not actually reread from disk, hence doesn't + // realize what other processes have done e.g. Settings / Show Path + cfg->reparseConfiguration (); + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\trecent URLs=" << d->actionOpenRecent->items (); +#endif + // HACK: Something might have changed interprocess. + // If we could PROPAGATE: interprocess, then this wouldn't be required. + d->actionOpenRecent->loadEntries (cfg->group (kpSettingsGroupRecentFiles)); +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tafter loading config=" << d->actionOpenRecent->items (); +#endif + + d->actionOpenRecent->addUrl (url); + + d->actionOpenRecent->saveEntries (cfg->group (kpSettingsGroupRecentFiles)); + cfg->sync (); + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tnew recent URLs=" << d->actionOpenRecent->items (); +#endif + + + // TODO: PROPAGATE: interprocess + // TODO: Is this loop safe since a KMainWindow later along in the list, + // could be closed as the code in the body almost certainly re-enters + // the event loop? Problem for KDE 3 as well, I think. + for (auto *kmw : KMainWindow::memberList ()) + { + Q_ASSERT (dynamic_cast (kmw)); + auto *mw = dynamic_cast (kmw); + + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\tmw=" << mw; + #endif + + if (mw != this) + { + // WARNING: Do not use KRecentFilesAction::setItems() + // - it does not work since only its superclass, + // KSelectAction, implements setItems() and can't + // update KRecentFilesAction's URL list. + + // Avoid URL memory leak in KRecentFilesAction::loadEntries(). + mw->d->actionOpenRecent->clear (); + + mw->d->actionOpenRecent->loadEntries (cfg->group (kpSettingsGroupRecentFiles)); + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\t\tcheck recent URLs=" + << mw->d->actionOpenRecent->items (); + #endif + } + } +} + +//--------------------------------------------------------------------- + + +// private slot +// TODO: Disable action if +// (d->configOpenImagesInSameWindow && d->document && d->document->isEmpty()) +// as it does nothing if this is true. +void kpMainWindow::slotNew () +{ + toolEndShape (); + + if (d->document && !d->configOpenImagesInSameWindow) + { + // A document -- empty or otherwise -- is open. + // Force open a new window. In contrast, open() might not open + // a new window in this case. + auto *win = new kpMainWindow (); + win->show (); + } + else + { + open (QUrl (), true/*create an empty doc*/); + } +} + +//--------------------------------------------------------------------- + + +// private +QSize kpMainWindow::defaultDocSize () const +{ + // KConfig::readEntry() does not actually reread from disk, hence doesn't + // realize what other processes have done e.g. Settings / Show Path + KSharedConfig::openConfig ()->reparseConfiguration (); + + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral); + + QSize docSize = cfg.readEntry (kpSettingLastDocSize, QSize ()); + + if (docSize.isEmpty ()) + { + docSize = QSize (400, 300); + } + else + { + // Don't get too big or you'll thrash (or even lock up) the computer + // just by opening a window + docSize = QSize (qMin (2048, docSize.width ()), + qMin (2048, docSize.height ())); + } + + return docSize; +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::saveDefaultDocSize (const QSize &size) +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tCONFIG: saving Last Doc Size = " << size; +#endif + + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral); + + cfg.writeEntry (kpSettingLastDocSize, size); + cfg.sync (); +} + +//--------------------------------------------------------------------- + +// private +bool kpMainWindow::shouldOpen () +{ + if (d->configOpenImagesInSameWindow) + { + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\topenImagesInSameWindow"; + #endif + // (this brings up a dialog and might save the current doc) + if (!queryCloseDocument ()) + { + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\tqueryCloseDocument() aborts open"; + #endif + return false; + } + } + + return true; +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::setDocumentChoosingWindow (kpDocument *doc) +{ + // Want new window? + if (d->document && !d->document->isEmpty () && + !d->configOpenImagesInSameWindow) + { + // Send doc to new window. + auto *win = new kpMainWindow (doc); + win->show (); + } + else + { + // (sets up views, doc signals) + setDocument (doc); + } +} + +//--------------------------------------------------------------------- + +// private +kpDocument *kpMainWindow::openInternal (const QUrl &url, + const QSize &fallbackDocSize, + bool newDocSameNameIfNotExist) +{ + // If using OpenImagesInSameWindow mode, ask whether to close the + // current document. + if (!shouldOpen ()) + return nullptr; + + // Create/open doc. + auto *newDoc = new kpDocument (fallbackDocSize.width (), + fallbackDocSize.height (), documentEnvironment ()); + + if (!newDoc->open (url, newDocSameNameIfNotExist)) + { + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\topen failed"; + #endif + delete newDoc; + return nullptr; + } + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\topen OK"; +#endif + // Send document to current or new window. + setDocumentChoosingWindow (newDoc); + + return newDoc; +} + +//--------------------------------------------------------------------- + +// private +bool kpMainWindow::open (const QUrl &url, bool newDocSameNameIfNotExist) +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::open(" << url + << ",newDocSameNameIfNotExist=" << newDocSameNameIfNotExist + << ")"; +#endif + + kpDocument *newDoc = openInternal (url, + defaultDocSize (), + newDocSameNameIfNotExist); + if (newDoc) + { + if (newDoc->isFromExistingURL ()) + addRecentURL (url); + return true; + } + + return false; +} + +//--------------------------------------------------------------------- + +// private +QList kpMainWindow::askForOpenURLs(const QString &caption, bool allowMultipleURLs) +{ + QMimeDatabase db; + QStringList filterList; + QString filter; + for (const auto &type : QImageReader::supportedMimeTypes()) + { + if ( !filter.isEmpty() ) { + filter += QLatin1Char(' '); + } + + QMimeType mime(db.mimeTypeForName(QString::fromLatin1(type))); + if ( mime.isValid() ) + { + QString glob = mime.globPatterns().join(QLatin1Char(' ')); + + filter += glob; + + // I want to show the mime comment AND the file glob pattern, + // but to avoid that the "All Supported Files" entry shows ALL glob patterns, + // I must add the pattern here a second time so that QFileDialog::HideNameFilterDetails + // can hide the first pattern and I still see the second one + filterList << mime.comment() + QStringLiteral(" (%1)(%2)").arg(glob).arg(glob); + } + } + + filterList.prepend(i18n("All Supported Files (%1)", filter)); + + QFileDialog fd(this); + fd.setNameFilters(filterList); + fd.setOption(QFileDialog::HideNameFilterDetails); + fd.setWindowTitle(caption); + + if ( allowMultipleURLs ) { + fd.setFileMode(QFileDialog::ExistingFiles); + } + + if ( fd.exec() ) { + return fd.selectedUrls(); + } + + return {}; +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotOpen () +{ + toolEndShape (); + + const QList urls = askForOpenURLs(i18nc("@title:window", "Open Image")); + + for (const auto & url : urls) + { + open (url); + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotOpenRecent (const QUrl &url) +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotOpenRecent(" << url << ")"; + qCDebug(kpLogMainWindow) << "\titems=" << d->actionOpenRecent->items (); +#endif + + toolEndShape (); + + open (url); + + // If the open is successful, addRecentURL() would have bubbled up the + // URL in the File / Open Recent action. As a side effect, the URL is + // deselected. + // + // If the open fails, we should deselect the URL: + // + // 1. for consistency + // + // 2. because it has not been opened. + // + d->actionOpenRecent->setCurrentItem (-1); +} + +//--------------------------------------------------------------------- + +void kpMainWindow::slotRecentListCleared() +{ + d->actionOpenRecent->saveEntries(KSharedConfig::openConfig()->group(kpSettingsGroupRecentFiles)); +} + +//--------------------------------------------------------------------- + +#if HAVE_KSANE +// private slot +void kpMainWindow::slotScan () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotScan() scanDialog=" << d->scanDialog; +#endif + + toolEndShape (); + + if (!d->scanDialog) + { + // Create scan dialog + d->scanDialog = new SaneDialog(this); + + // No scanning support (kdegraphics/libkscan) installed? + if (!d->scanDialog) + { + KMessageBox::sorry (this, + i18n("Failed to open scanning dialog."), + i18nc("@title:window", "Scanning Failed")); + return; + } + + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tcreated scanDialog=" << d->scanDialog; + #endif + connect (d->scanDialog, &SaneDialog::finalImage, this, &kpMainWindow::slotScanned); + } + + + // If using OpenImagesInSameWindow mode, ask whether to close the + // current document. + // + // Do this after scan support is detected. Because if it's not, what + // would be the point of closing the document? + // + // Ideally, we would do this after the user presses "Final Scan" in + // the scan dialog and before the scan begins (if the user wants to + // cancel the scan operation, it would be annoying to offer this choice + // only after the slow scan is completed) but the KScanDialog API does + // not allow this. So we settle for doing this before any + // scan dialogs are shown. We don't do this between KScanDialog::setup() + // and KScanDialog::exec() as it could be confusing alternating between + // scanning and KolourPaint dialogs. + if (!shouldOpen ()) { + return; + } + + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tcalling setup"; +#endif + // Bring up dialog to select scan device. + // If there is no scanner, we find that this does not bring up a dialog + // but still returns true. + if (d->scanDialog->setup ()) + { + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\tOK - showing dialog"; + #endif + // Called only if scanner configured/available. + // + // In reality, this seems to be called even if you press "Cancel" in + // the KScanDialog::setup() dialog! + // + // We use exec() to make sure it's modal. show() seems to work too + // but better safe than sorry. + d->scanDialog->exec (); + } + else + { + // Have never seen this code path execute even if "Cancel" is pressed. + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\tFAIL"; + #endif + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotScanned (const QImage &image, int) +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotScanned() image.rect=" << image.rect (); +#endif + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\thiding dialog"; +#endif + // (KScanDialog does not close itself after a scan is made) + // + // Close the dialog, first thing: + // + // 1. This means that any dialogs we bring up won't be nested on top. + // + // 2. We don't want to return from this method but forget to close + // the dialog. So do it before anything else. + d->scanDialog->hide (); + + // (just in case there's some drawing between slotScan() exiting and + // us being called) + toolEndShape (); + + + // TODO: Maybe this code should be moved into kpdocument.cpp - + // since it resembles the responsibilities of kpDocument::open(). + + kpDocumentSaveOptions saveOptions; + kpDocumentMetaInfo metaInfo; + + kpDocument::getDataFromImage(image, saveOptions, metaInfo); + + // Create document from image and meta info. + auto *doc = new kpDocument (image.width (), image.height (), documentEnvironment ()); + doc->setImage (image); + doc->setSaveOptions (saveOptions); + doc->setMetaInfo (metaInfo); + + // Send document to current or new window. + setDocumentChoosingWindow (doc); +} +#endif // HAVE_KSANE + +//--------------------------------------------------------------------- + +void kpMainWindow::slotScreenshot() +{ + toolEndShape(); + + auto *dialog = new QDialog(this); + auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | + QDialogButtonBox::Cancel, dialog); + connect (buttons, &QDialogButtonBox::accepted, dialog, &QDialog::accept); + connect (buttons, &QDialogButtonBox::rejected, dialog, &QDialog::reject); + + auto *label = new QLabel(i18n("Snapshot Delay")); + auto *seconds = new KPluralHandlingSpinBox; + seconds->setRange(0, 99); + seconds->setSuffix(ki18np(" second", " seconds")); + seconds->setSpecialValueText(i18n("No delay")); + + auto *hideWindow = new QCheckBox(i18n("Hide Main Window")); + hideWindow->setChecked(true); + + auto *vbox = new QVBoxLayout(dialog); + vbox->addWidget(label); + vbox->addWidget(seconds); + vbox->addWidget(hideWindow); + vbox->addWidget(buttons); + + if ( dialog->exec() == QDialog::Rejected ) + { + delete dialog; + return; + } + + if ( hideWindow->isChecked() ) { + hide(); + } + + // at least 1 seconds to make sure the window is hidden and the hide effect already stopped + QTimer::singleShot((seconds->value() + 1) * 1000, this, &kpMainWindow::slotMakeScreenshot); + + delete dialog; +} + +//--------------------------------------------------------------------- + +void kpMainWindow::slotMakeScreenshot() +{ + QCoreApplication::processEvents(); + QPixmap pixmap = QGuiApplication::primaryScreen()->grabWindow(QApplication::desktop()->winId()); + + auto *doc = new kpDocument(pixmap.width(), pixmap.height(), documentEnvironment()); + doc->setImage(pixmap.toImage()); + + // Send document to current or new window. + setDocumentChoosingWindow(doc); + + show(); // in case we hid the mainwindow, show it again +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotProperties () +{ + toolEndShape (); + + kpDocumentMetaInfoDialog dialog (document ()->metaInfo (), this); + + if (dialog.exec () && !dialog.isNoOp ()) + { + commandHistory ()->addCommand ( + new kpDocumentMetaInfoCommand ( + i18n ("Document Properties"), + dialog.metaInfo ()/*new*/, *document ()->metaInfo ()/*old*/, + commandEnvironment ())); + } +} + +//--------------------------------------------------------------------- + +// private slot +bool kpMainWindow::save (bool localOnly) +{ + if (d->document->url ().isEmpty () || + !QImageWriter::supportedMimeTypes() + .contains(d->document->saveOptions ()->mimeType().toLatin1()) || + // SYNC: kpDocument::getPixmapFromFile() can't determine quality + // from file so it has been set initially to an invalid value. + (d->document->saveOptions ()->mimeTypeHasConfigurableQuality () && + d->document->saveOptions ()->qualityIsInvalid ()) || + (localOnly && !d->document->url ().isLocalFile ())) + { + return saveAs (localOnly); + } + + if (d->document->save (!d->document->savedAtLeastOnceBefore ()/*lossy prompt*/)) + { + addRecentURL (d->document->url ()); + return true; + } + + return false; +} + +//--------------------------------------------------------------------- + +// private slot +bool kpMainWindow::slotSave () +{ + toolEndShape (); + + return save (); +} + +//--------------------------------------------------------------------- + +// private +QUrl kpMainWindow::askForSaveURL (const QString &caption, + const QString &startURL, + const kpImage &imageToBeSaved, + const kpDocumentSaveOptions &startSaveOptions, + const kpDocumentMetaInfo &docMetaInfo, + const QString &forcedSaveOptionsGroup, + bool localOnly, + kpDocumentSaveOptions *chosenSaveOptions, + bool isSavingForFirstTime, + bool *allowLossyPrompt) +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::askForURL() startURL=" << startURL; + startSaveOptions.printDebug ("\tstartSaveOptions"); +#endif + + bool reparsedConfiguration = false; + + // KConfig::readEntry() does not actually reread from disk, hence doesn't + // realize what other processes have done e.g. Settings / Show Path + // so reparseConfiguration() must be called +#define SETUP_READ_CFG() \ + if (!reparsedConfiguration) \ + { \ + KSharedConfig::openConfig ()->reparseConfiguration (); \ + reparsedConfiguration = true; \ + } \ + \ + KConfigGroup cfg (KSharedConfig::openConfig (), forcedSaveOptionsGroup); + + + if (chosenSaveOptions) { + *chosenSaveOptions = kpDocumentSaveOptions (); + } + + if (allowLossyPrompt) { + *allowLossyPrompt = true; // play it safe for now + } + + + kpDocumentSaveOptions fdSaveOptions = startSaveOptions; + + QStringList mimeTypes; + for (const auto &type : QImageWriter::supportedMimeTypes()) { + mimeTypes << QString::fromLatin1(type); + } +#if DEBUG_KP_MAIN_WINDOW + QStringList sortedMimeTypes = mimeTypes; + sortedMimeTypes.sort (); + qCDebug(kpLogMainWindow) << "\tmimeTypes=" << mimeTypes + << "\tsortedMimeTypes=" << sortedMimeTypes; +#endif + if (mimeTypes.isEmpty ()) + { + qCCritical(kpLogMainWindow) << "No output mimetypes!"; + return {}; + } + +#define MIME_TYPE_IS_VALID() (!fdSaveOptions.mimeTypeIsInvalid () && \ + mimeTypes.contains (fdSaveOptions.mimeType ())) + if (!MIME_TYPE_IS_VALID ()) + { + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tmimeType=" << fdSaveOptions.mimeType () + << " not valid, get default"; + #endif + + SETUP_READ_CFG (); + + fdSaveOptions.setMimeType (kpDocumentSaveOptions::defaultMimeType (cfg)); + + + if (!MIME_TYPE_IS_VALID ()) + { + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tmimeType=" << fdSaveOptions.mimeType () + << " not valid, get hardcoded"; + #endif + if (mimeTypes.contains(QLatin1String("image/png"))) { + fdSaveOptions.setMimeType (QStringLiteral("image/png")); + } + else if (mimeTypes.contains(QLatin1String("image/bmp"))) { + fdSaveOptions.setMimeType (QStringLiteral("image/bmp")); + } + else { + fdSaveOptions.setMimeType (mimeTypes.first ()); + } + } + } +#undef MIME_TYPE_IS_VALID + + if (fdSaveOptions.colorDepthIsInvalid ()) + { + SETUP_READ_CFG (); + + fdSaveOptions.setColorDepth (kpDocumentSaveOptions::defaultColorDepth (cfg)); + fdSaveOptions.setDither (kpDocumentSaveOptions::defaultDither (cfg)); + } + + if (fdSaveOptions.qualityIsInvalid ()) + { + SETUP_READ_CFG (); + + fdSaveOptions.setQuality (kpDocumentSaveOptions::defaultQuality (cfg)); + } +#if DEBUG_KP_MAIN_WINDOW + fdSaveOptions.printDebug ("\tcorrected saveOptions passed to fileDialog"); +#endif + + auto *saveOptionsWidget = + new kpDocumentSaveOptionsWidget (imageToBeSaved, + fdSaveOptions, + docMetaInfo, + this); + + KFileCustomDialog fd (QUrl (startURL), this); + fd.setOperationMode (KFileWidget::Saving); + fd.setWindowTitle (caption); + fd.setCustomWidget (saveOptionsWidget); + KFileWidget *fw = fd.fileWidget(); + fw->setConfirmOverwrite (true); + fw->setMimeFilter (mimeTypes, fdSaveOptions.mimeType ()); + if (localOnly) { + fw->setMode (KFile::File | KFile::LocalOnly); + } + + saveOptionsWidget->setVisualParent (&fd); + + connect (fw, &KFileWidget::filterChanged, + saveOptionsWidget, &kpDocumentSaveOptionsWidget::setMimeType); + + if ( fd.exec() == QDialog::Accepted ) + { + kpDocumentSaveOptions newSaveOptions = saveOptionsWidget->documentSaveOptions (); + #if DEBUG_KP_MAIN_WINDOW + newSaveOptions.printDebug ("\tnewSaveOptions"); + #endif + + KConfigGroup cfg (KSharedConfig::openConfig (), forcedSaveOptionsGroup); + + // Save options user forced - probably want to use them in future + kpDocumentSaveOptions::saveDefaultDifferences (cfg, + fdSaveOptions, newSaveOptions); + cfg.sync (); + + + if (chosenSaveOptions) { + *chosenSaveOptions = newSaveOptions; + } + + const QList selectedUrls = fw->selectedUrls (); + if (selectedUrls.isEmpty()) { // shouldn't happen + return {}; + } + const QUrl selectedUrl = selectedUrls.at(0); + + if (allowLossyPrompt) + { + // SYNC: kpDocumentSaveOptions elements - everything except quality + // (one quality setting is "just as lossy" as another so no + // need to continually warn due to quality change) + *allowLossyPrompt = + (isSavingForFirstTime || + selectedUrl != QUrl (startURL) || + newSaveOptions.mimeType () != startSaveOptions.mimeType () || + newSaveOptions.colorDepth () != startSaveOptions.colorDepth () || + newSaveOptions.dither () != startSaveOptions.dither ()); + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tallowLossyPrompt=" << *allowLossyPrompt; + #endif + } + + + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tselectedUrl=" << selectedUrl; + #endif + return selectedUrl; + } + + return {}; +#undef SETUP_READ_CFG +} + +//--------------------------------------------------------------------- + +// private slot +bool kpMainWindow::saveAs (bool localOnly) +{ + kpDocumentSaveOptions chosenSaveOptions; + bool allowLossyPrompt; + QUrl chosenURL = askForSaveURL (i18nc ("@title:window", "Save Image As"), + d->document->url ().url (), + d->document->imageWithSelection (), + *d->document->saveOptions (), + *d->document->metaInfo (), + kpSettingsGroupFileSaveAs, + localOnly, + &chosenSaveOptions, + !d->document->savedAtLeastOnceBefore (), + &allowLossyPrompt); + + + if (chosenURL.isEmpty ()) { + return false; + } + + + if (!d->document->saveAs (chosenURL, chosenSaveOptions, + allowLossyPrompt)) + { + return false; + } + + + addRecentURL (chosenURL); + + return true; +} + +//--------------------------------------------------------------------- + +// private slot +bool kpMainWindow::slotSaveAs () +{ + toolEndShape (); + + return saveAs (); +} + +//--------------------------------------------------------------------- + +// private slot +bool kpMainWindow::slotExport () +{ + toolEndShape (); + + kpDocumentSaveOptions chosenSaveOptions; + bool allowLossyPrompt; + QUrl chosenURL = askForSaveURL (i18nc ("@title:window", "Export"), + d->lastExportURL.url (), + d->document->imageWithSelection (), + d->lastExportSaveOptions, + *d->document->metaInfo (), + kpSettingsGroupFileExport, + false/*allow remote files*/, + &chosenSaveOptions, + d->exportFirstTime, + &allowLossyPrompt); + + + if (chosenURL.isEmpty ()) { + return false; + } + + if (!kpDocument::savePixmapToFile (d->document->imageWithSelection (), + chosenURL, + chosenSaveOptions, *d->document->metaInfo (), + allowLossyPrompt, + this)) + { + return false; + } + + + addRecentURL (chosenURL); + + d->lastExportURL = chosenURL; + d->lastExportSaveOptions = chosenSaveOptions; + + d->exportFirstTime = false; + + return true; +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotEnableReload () +{ + d->actionReload->setEnabled (d->document); +} + +//--------------------------------------------------------------------- + +// private slot +bool kpMainWindow::slotReload () +{ + toolEndShape (); + + Q_ASSERT (d->document); + + + QUrl oldURL = d->document->url (); + + + if (d->document->isModified ()) + { + int result = KMessageBox::Cancel; + + if (d->document->isFromExistingURL () && !oldURL.isEmpty ()) + { + result = KMessageBox::warningContinueCancel (this, + i18n ("The document \"%1\" has been modified.\n" + "Reloading will lose all changes since you last saved it.\n" + "Are you sure?", + d->document->prettyFilename ()), + QString()/*caption*/, + KGuiItem(i18n ("&Reload"))); + } + else + { + result = KMessageBox::warningContinueCancel (this, + i18n ("The document \"%1\" has been modified.\n" + "Reloading will lose all changes.\n" + "Are you sure?", + d->document->prettyFilename ()), + QString()/*caption*/, + KGuiItem(i18n ("&Reload"))); + } + + if (result != KMessageBox::Continue) { + return false; + } + } + + + kpDocument *doc = nullptr; + + // If it's _supposed to_ come from an existing URL or it actually exists + if (d->document->isFromExistingURL () || + d->document->urlExists (oldURL)) + { + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotReload() reloading from disk!"; + #endif + + doc = new kpDocument (1, 1, documentEnvironment ()); + if (!doc->open (oldURL)) + { + delete doc; doc = nullptr; + return false; + } + + addRecentURL (oldURL); + } + else + { + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotReload() create doc"; + #endif + + doc = new kpDocument (d->document->constructorWidth (), + d->document->constructorHeight (), + documentEnvironment ()); + doc->setURL (oldURL, false/*not from URL*/); + } + + + setDocument (doc); + + return true; +} + + +// private +void kpMainWindow::sendDocumentNameToPrinter (QPrinter *printer) +{ + QUrl url = d->document->url (); + if (!url.isEmpty ()) + { + int dot; + + QString fileName = url.fileName (); + dot = fileName.lastIndexOf ('.'); + + // file.ext but not .hidden-file? + if (dot > 0) { + fileName.truncate (dot); + } + + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::sendDocumentNameToPrinter() fileName=" + << fileName + << " dir=" + << url.path(); + #endif + printer->setDocName (fileName); + } +} + +//-------------------------------------------------------------------------------- + +void kpMainWindow::setPrinterPageOrientation(QPrinter *printer) +{ + const bool isLandscape = d->document->width() > d->document->height(); + printer->setPageOrientation(isLandscape ? QPageLayout::Landscape : QPageLayout::Portrait); +} + +//-------------------------------------------------------------------------------- + +void kpMainWindow::sendPreviewToPrinter(QPrinter *printer) +{ + sendImageToPrinter(printer, false); +} + +//-------------------------------------------------------------------------------- +// private +void kpMainWindow::sendImageToPrinter (QPrinter *printer, + bool showPrinterSetupDialog) +{ + // Get image to be printed. + kpImage image = d->document->imageWithSelection (); + + + // Get image DPI. + auto imageDotsPerMeterX = double (d->document->metaInfo ()->dotsPerMeterX ()); + auto imageDotsPerMeterY = double (d->document->metaInfo ()->dotsPerMeterY ()); +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::sendImageToPrinter() image:" + << " width=" << image.width () + << " height=" << image.height () + << " dotsPerMeterX=" << imageDotsPerMeterX + << " dotsPerMeterY=" << imageDotsPerMeterY; +#endif + + // Image DPI invalid (e.g. new image, could not read from file + // or Qt3 doesn't implement DPI for JPEG)? + if (imageDotsPerMeterX <= 0 || imageDotsPerMeterY <= 0) + { + // Even if just one DPI dimension is invalid, mutate both DPI + // dimensions as we have no information about the intended + // aspect ratio anyway (and other dimension likely to be invalid). + + // When rendering text onto a document, the fonts are rasterised + // according to the screen's DPI. + // TODO: I think we should use the image's DPI. Technically + // possible? + // + // So no matter what computer you draw text on, you get + // the same pixels. + // + // So we must print at the screen's DPI to get the right text size. + // + // Unfortunately, this means that moving to a different screen DPI + // affects printing. If you edited the image at a different screen + // DPI than when you print, you get incorrect results. Furthermore, + // this is bogus if you don't have text in your image. Worse still, + // what if you have multiple screens connected to the same computer + // with different DPIs? + // TODO: mysteriously, someone else is setting this to 96dpi always. + QPixmap arbitraryScreenElement(1, 1); + const QPaintDevice *screenDevice = &arbitraryScreenElement; + const auto dpiX = screenDevice->logicalDpiX (); + const auto dpiY = screenDevice->logicalDpiY (); + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tusing screen dpi: x=" << dpiX << " y=" << dpiY; + #endif + + imageDotsPerMeterX = dpiX * KP_INCHES_PER_METER; + imageDotsPerMeterY = dpiY * KP_INCHES_PER_METER; + } + + + // Get page size (excluding margins). + // Coordinate (0,0) is the X here: + // mmmmm + // mX m + // m m m = margin + // m m + // mmmmm + const auto printerWidthMM = printer->widthMM (); + const auto printerHeightMM = printer->heightMM (); + + auto dpiX = imageDotsPerMeterX / KP_INCHES_PER_METER; + auto dpiY = imageDotsPerMeterY / KP_INCHES_PER_METER; + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tprinter: widthMM=" << printerWidthMM + << " heightMM=" << printerHeightMM; + + qCDebug(kpLogMainWindow) << "\timage: dpiX=" << dpiX << " dpiY=" << dpiY; +#endif + + + // + // If image doesn't fit on page at intended DPI, change the DPI. + // + + const auto scaleDpiX = + (image.width () / (printerWidthMM / KP_MILLIMETERS_PER_INCH)) / dpiX; + const auto scaleDpiY = + (image.height () / (printerHeightMM / KP_MILLIMETERS_PER_INCH)) / dpiY; + const auto scaleDpi = qMax (scaleDpiX, scaleDpiY); + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\tscaleDpi: x=" << scaleDpiX << " y=" << scaleDpiY + << " --> scale at " << scaleDpi << " to fit?"; +#endif + + // Need to increase resolution to fit page? + if (scaleDpi > 1.0) + { + dpiX *= scaleDpi; + dpiY *= scaleDpi; + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\t\tto fit page, scaled to:" + << " dpiX=" << dpiX << " dpiY=" << dpiY; + #endif + } + + + // Make sure DPIs are equal as that's all QPrinter::setResolution() + // supports. We do this in such a way that we only ever stretch an + // image, to avoid losing information. Don't antialias as the printer + // will do that to translate our DPI to its physical resolution and + // double-antialiasing looks bad. + if (dpiX > dpiY) + { + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tdpiX > dpiY; stretching image height to equalise DPIs to dpiX=" + << dpiX; + #endif + kpPixmapFX::scale (&image, + image.width (), + qMax (1, qRound (image.height () * dpiX / dpiY)), + false/*don't antialias*/); + + dpiY = dpiX; + } + else if (dpiY > dpiX) + { + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tdpiY > dpiX; stretching image width to equalise DPIs to dpiY=" + << dpiY; + #endif + kpPixmapFX::scale (&image, + qMax (1, qRound (image.width () * dpiY / dpiX)), + image.height (), + false/*don't antialias*/); + + dpiX = dpiY; + } + + Q_ASSERT (dpiX == dpiY); + + + // QPrinter::setResolution() has to be called before QPrinter::setup(). + printer->setResolution (qMax (1, qRound (dpiX))); + + + sendDocumentNameToPrinter (printer); + + + if (showPrinterSetupDialog) + { + auto *optionsPage = new kpPrintDialogPage (this); + optionsPage->setPrintImageCenteredOnPage (d->configPrintImageCenteredOnPage); + + QPrintDialog printDialog (printer, this); + printDialog.setOptionTabs ({optionsPage}); + printDialog.setWindowTitle (i18nc ("@title:window", "Print Image")); + + // Display dialog. + const bool wantToPrint = printDialog.exec (); + + if (optionsPage->printImageCenteredOnPage () != + d->configPrintImageCenteredOnPage) + { + // Save config option even if the dialog was cancelled. + d->configPrintImageCenteredOnPage = optionsPage->printImageCenteredOnPage (); + + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral); + cfg.writeEntry (kpSettingPrintImageCenteredOnPage, + d->configPrintImageCenteredOnPage); + cfg.sync (); + } + + if (!wantToPrint) { + return; + } + } + + + // Send image to printer. + QPainter painter; + painter.begin(printer); + + double originX = 0, originY = 0; + + // Center image on page? + if (d->configPrintImageCenteredOnPage) + { + originX = (printer->width() - image.width ()) / 2; + originY = (printer->height() - image.height ()) / 2; + } + + painter.drawImage(qRound(originX), qRound(originY), image); + painter.end(); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotPrint () +{ + toolEndShape (); + + QPrinter printer; + setPrinterPageOrientation(&printer); + + sendImageToPrinter (&printer, true/*showPrinterSetupDialog*/); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotPrintPreview () +{ + toolEndShape (); + + QPrinter printer; + setPrinterPageOrientation(&printer); + QPrintPreviewDialog printPreview(&printer, this); + connect(&printPreview, &QPrintPreviewDialog::paintRequested, this, &kpMainWindow::sendPreviewToPrinter); + + printPreview.exec (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotMail () +{ + toolEndShape (); + + if (d->document->url ().isEmpty ()/*no name*/ || + !(d->document->isFromExistingURL () && d->document->urlExists (d->document->url ())) || + d->document->isModified ()/*needs to be saved*/) + { + int result = KMessageBox::questionYesNo (this, + i18n ("You must save this image before sending it.\n" + "Do you want to save it?"), + QString(), + KStandardGuiItem::save (), KStandardGuiItem::cancel ()); + + if (result == KMessageBox::Yes) + { + if (!save ()) + { + // save failed or aborted - don't email + return; + } + } + else + { + // don't want to save - don't email + return; + } + } + + auto *job = new KEMailClientLauncherJob; + job->setSubject(d->document->prettyFilename()); + job->setAttachments({d->document->url()}); + job->start(); +} + +//--------------------------------------------------------------------- + +// private +bool kpMainWindow::queryCloseDocument () +{ + toolEndShape (); + + if (!d->document || !d->document->isModified ()) { + return true; // ok to close current doc + } + + int result = KMessageBox::warningYesNoCancel (this, + i18n ("The document \"%1\" has been modified.\n" + "Do you want to save it?", + d->document->prettyFilename ()), + QString()/*caption*/, + KStandardGuiItem::save (), KStandardGuiItem::discard ()); + + switch (result) + { + case KMessageBox::Yes: + return slotSave (); // close only if save succeeds + case KMessageBox::No: + return true; // close without saving + default: + return false; // don't close current doc + } +} + +//--------------------------------------------------------------------- + +// private virtual [base KMainWindow] +bool kpMainWindow::queryClose () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::queryClose()"; +#endif + toolEndShape (); + + if (!queryCloseDocument ()) { + return false; + } + + if (!queryCloseColors ()) { + return false; + } + + return true; +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotClose () +{ + toolEndShape (); + + if (!queryCloseDocument ()) { + return; + } + + setDocument (nullptr); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotQuit () +{ + toolEndShape (); + + close (); // will call queryClose() +} + +//--------------------------------------------------------------------- diff --git a/mainWindow/kpMainWindow_Image.cpp b/mainWindow/kpMainWindow_Image.cpp new file mode 100644 index 0000000..8d0b734 --- /dev/null +++ b/mainWindow/kpMainWindow_Image.cpp @@ -0,0 +1,624 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "mainWindow/kpMainWindow.h" +#include "kpMainWindowPrivate.h" + +#include "layers/selections/image/kpAbstractImageSelection.h" +#include "imagelib/kpColor.h" +#include "kpDefs.h" +#include "widgets/toolbars/kpColorToolBar.h" +#include "commands/kpCommandHistory.h" +#include "document/kpDocument.h" +#include "commands/imagelib/effects/kpEffectInvertCommand.h" +#include "commands/imagelib/effects/kpEffectReduceColorsCommand.h" +#include "dialogs/imagelib/effects/kpEffectsDialog.h" +#include "commands/imagelib/effects/kpEffectClearCommand.h" +#include "commands/imagelib/effects/kpEffectGrayscaleCommand.h" +#include "commands/kpMacroCommand.h" +#include "layers/selections/text/kpTextSelection.h" +#include "commands/tools/selection/kpToolSelectionCreateCommand.h" +#include "commands/tools/selection/kpToolSelectionPullFromDocumentCommand.h" +#include "commands/tools/selection/text/kpToolTextGiveContentCommand.h" +#include "imagelib/transforms/kpTransformAutoCrop.h" +#include "imagelib/transforms/kpTransformCrop.h" +#include "environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.h" +#include "commands/imagelib/transforms/kpTransformFlipCommand.h" +#include "commands/imagelib/transforms/kpTransformResizeScaleCommand.h" +#include "dialogs/imagelib/transforms/kpTransformResizeScaleDialog.h" +#include "commands/imagelib/transforms/kpTransformRotateCommand.h" +#include "dialogs/imagelib/transforms/kpTransformRotateDialog.h" +#include "commands/imagelib/transforms/kpTransformSkewCommand.h" +#include "dialogs/imagelib/transforms/kpTransformSkewDialog.h" +#include "views/manager/kpViewManager.h" +#include "commands/imagelib/effects/kpEffectBlurSharpenCommand.h" +#include "imagelib/effects/kpEffectBlurSharpen.h" +#include "kpLogCategories.h" + +#include +#include +#include +#include + +#include +#include +#include + + +//--------------------------------------------------------------------- + +// private +kpTransformDialogEnvironment *kpMainWindow::transformDialogEnvironment () +{ + if (!d->transformDialogEnvironment) + d->transformDialogEnvironment = new kpTransformDialogEnvironment (this); + + return d->transformDialogEnvironment; +} + +//--------------------------------------------------------------------- + +// private +bool kpMainWindow::isSelectionActive () const +{ + return (d->document ? bool (d->document->selection ()) : false); +} + +//--------------------------------------------------------------------- + +// private +bool kpMainWindow::isTextSelection () const +{ + return (d->document && d->document->textSelection ()); +} + +//--------------------------------------------------------------------- + +// private +QString kpMainWindow::autoCropText () const +{ + return kpTransformAutoCropCommand::text(isSelectionActive(), + kpTransformAutoCropCommand::ShowAccel); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::setupImageMenuActions () +{ + KActionCollection *ac = actionCollection (); + + d->actionResizeScale = ac->addAction (QStringLiteral("image_resize_scale")); + d->actionResizeScale->setText (i18n ("R&esize / Scale...")); + connect (d->actionResizeScale, &QAction::triggered, this, &kpMainWindow::slotResizeScale); + + ac->setDefaultShortcut (d->actionResizeScale, Qt::CTRL | Qt::Key_E); + + d->actionCrop = ac->addAction (QStringLiteral("image_crop")); + d->actionCrop->setText (i18n ("Se&t as Image (Crop)")); + connect (d->actionCrop, &QAction::triggered, this, &kpMainWindow::slotCrop); + ac->setDefaultShortcut (d->actionCrop, Qt::CTRL | Qt::Key_T); + + d->actionAutoCrop = ac->addAction (QStringLiteral("image_auto_crop")); + d->actionAutoCrop->setText (autoCropText ()); + connect (d->actionAutoCrop, &QAction::triggered, this, &kpMainWindow::slotAutoCrop); + ac->setDefaultShortcut (d->actionAutoCrop, Qt::CTRL | Qt::Key_U); + + d->actionFlip = ac->addAction (QStringLiteral("image_flip")); + d->actionFlip->setText (i18n ("&Flip (upside down)")); + connect (d->actionFlip, &QAction::triggered, this, &kpMainWindow::slotFlip); + ac->setDefaultShortcut (d->actionFlip, Qt::CTRL | Qt::Key_F); + + d->actionMirror = ac->addAction (QStringLiteral("image_mirror")); + d->actionMirror->setText (i18n ("Mirror (horizontally)")); + connect (d->actionMirror, &QAction::triggered, this, &kpMainWindow::slotMirror); + //ac->setDefaultShortcut (d->actionMirror, Qt::CTRL | Qt::Key_M); + + d->actionRotate = ac->addAction (QStringLiteral("image_rotate")); + d->actionRotate->setText (i18n ("&Rotate...")); + d->actionRotate->setIcon(QIcon::fromTheme(QStringLiteral("transform-rotate"))); + connect (d->actionRotate, &QAction::triggered, this, &kpMainWindow::slotRotate); + ac->setDefaultShortcut (d->actionRotate, Qt::CTRL | Qt::Key_R); + + d->actionRotateLeft = ac->addAction (QStringLiteral("image_rotate_270deg")); + d->actionRotateLeft->setText (i18n ("Rotate &Left")); + d->actionRotateLeft->setIcon(QIcon::fromTheme(QStringLiteral("object-rotate-left"))); + connect (d->actionRotateLeft, &QAction::triggered, this, &kpMainWindow::slotRotate270); + ac->setDefaultShortcut (d->actionRotateLeft, Qt::CTRL | Qt::SHIFT | Qt::Key_Left); + + d->actionRotateRight = ac->addAction (QStringLiteral("image_rotate_90deg")); + d->actionRotateRight->setText (i18n ("Rotate Righ&t")); + d->actionRotateRight->setIcon(QIcon::fromTheme(QStringLiteral("object-rotate-right"))); + connect (d->actionRotateRight, &QAction::triggered, this, &kpMainWindow::slotRotate90); + ac->setDefaultShortcut (d->actionRotateRight, Qt::CTRL | Qt::SHIFT | Qt::Key_Right); + + d->actionSkew = ac->addAction (QStringLiteral("image_skew")); + d->actionSkew->setText (i18n ("S&kew...")); + connect (d->actionSkew, &QAction::triggered, this, &kpMainWindow::slotSkew); + ac->setDefaultShortcut (d->actionSkew, Qt::CTRL | Qt::Key_K); + + d->actionConvertToBlackAndWhite = ac->addAction (QStringLiteral("image_convert_to_black_and_white")); + d->actionConvertToBlackAndWhite->setText (i18n ("Reduce to Mo&nochrome (Dithered)")); + connect (d->actionConvertToBlackAndWhite, &QAction::triggered, + this, &kpMainWindow::slotConvertToBlackAndWhite); + + d->actionConvertToGrayscale = ac->addAction (QStringLiteral("image_convert_to_grayscale")); + d->actionConvertToGrayscale->setText (i18n ("Reduce to &Grayscale")); + connect (d->actionConvertToGrayscale, &QAction::triggered, this, &kpMainWindow::slotConvertToGrayscale); + + d->actionInvertColors = ac->addAction (QStringLiteral("image_invert_colors")); + d->actionInvertColors->setText (i18n ("&Invert Colors")); + connect (d->actionInvertColors, &QAction::triggered, this, &kpMainWindow::slotInvertColors); + ac->setDefaultShortcut (d->actionInvertColors, Qt::CTRL | Qt::Key_I); + + d->actionClear = ac->addAction (QStringLiteral("image_clear")); + d->actionClear->setText (i18n ("C&lear")); + connect (d->actionClear, &QAction::triggered, this, &kpMainWindow::slotClear); + ac->setDefaultShortcut (d->actionClear, Qt::CTRL | Qt::SHIFT | Qt::Key_N); + + d->actionBlur = ac->addAction(QStringLiteral("image_make_confidential")); + d->actionBlur->setText(i18n("Make Confidential")); + connect(d->actionBlur, &QAction::triggered, this, &kpMainWindow::slotMakeConfidential); + + d->actionMoreEffects = ac->addAction (QStringLiteral("image_more_effects")); + d->actionMoreEffects->setText (i18n ("&More Effects...")); + connect (d->actionMoreEffects, &QAction::triggered, this, &kpMainWindow::slotMoreEffects); + ac->setDefaultShortcut (d->actionMoreEffects, Qt::CTRL | Qt::Key_M); + + + enableImageMenuDocumentActions (false); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::enableImageMenuDocumentActions (bool enable) +{ + d->actionResizeScale->setEnabled (enable); + d->actionCrop->setEnabled (enable); + d->actionAutoCrop->setEnabled (enable); + d->actionFlip->setEnabled (enable); + d->actionMirror->setEnabled (enable); + d->actionRotate->setEnabled (enable); + d->actionRotateLeft->setEnabled (enable); + d->actionRotateRight->setEnabled (enable); + d->actionSkew->setEnabled (enable); + d->actionConvertToBlackAndWhite->setEnabled (enable); + d->actionConvertToGrayscale->setEnabled (enable); + d->actionInvertColors->setEnabled (enable); + d->actionClear->setEnabled (enable); + d->actionBlur->setEnabled (enable); + d->actionMoreEffects->setEnabled (enable); + + d->imageMenuDocumentActionsEnabled = enable; +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotImageMenuUpdateDueToSelection () +{ + // SYNC: kolourpaintui.rc + const QString MenuBarItemTextImage = i18nc ( + "Image/Selection Menu caption - make sure the translation has" + " the same accel as the Select&ion translation", + "&Image"); + const QString MenuBarItemTextSelection = i18nc ( + "Image/Selection Menu caption - make sure that translation has" + " the same accel as the &Image translation", + "Select&ion"); + + Q_ASSERT (menuBar ()); + for (auto *action : menuBar ()->actions ()) + { + if (action->text () == MenuBarItemTextImage || + action->text () == MenuBarItemTextSelection) + { + if (isSelectionActive ()) { + action->setText (MenuBarItemTextSelection); + } + else { + action->setText (MenuBarItemTextImage); + } + + break; + } + } + + + d->actionResizeScale->setEnabled (d->imageMenuDocumentActionsEnabled); + d->actionCrop->setEnabled (d->imageMenuDocumentActionsEnabled && + isSelectionActive ()); + + const bool enable = (d->imageMenuDocumentActionsEnabled && !isTextSelection ()); + d->actionAutoCrop->setText (autoCropText ()); + d->actionAutoCrop->setEnabled (enable); + d->actionFlip->setEnabled (enable); + d->actionMirror->setEnabled (enable); + d->actionRotate->setEnabled (enable); + d->actionRotateLeft->setEnabled (enable); + d->actionRotateRight->setEnabled (enable); + d->actionSkew->setEnabled (enable); + d->actionConvertToBlackAndWhite->setEnabled (enable); + d->actionConvertToGrayscale->setEnabled (enable); + d->actionInvertColors->setEnabled (enable); + d->actionClear->setEnabled (enable); + d->actionBlur->setEnabled (enable); + d->actionMoreEffects->setEnabled (enable); +} + +//--------------------------------------------------------------------- + +// public +kpColor kpMainWindow::backgroundColor (bool ofSelection) const +{ + if (ofSelection) { + return kpColor::Transparent; + } + + Q_ASSERT (d->colorToolBar); + return d->colorToolBar->backgroundColor (); +} + +//--------------------------------------------------------------------- + +// public +// REFACTOR: sync: Code dup with kpAbstractSelectionTool::addNeedingContentCommand(). +void kpMainWindow::addImageOrSelectionCommand (kpCommand *cmd, + bool addSelCreateCmdIfSelAvail, + bool addSelContentCmdIfSelAvail) +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "kpMainWindow::addImageOrSelectionCommand()" + << " addSelCreateCmdIfSelAvail=" << addSelCreateCmdIfSelAvail + << " addSelContentCmdIfSelAvail=" << addSelContentCmdIfSelAvail; +#endif + + Q_ASSERT (d->document); + + + if (d->viewManager) { + d->viewManager->setQueueUpdates (); + } + + + kpAbstractSelection *sel = d->document->selection (); +#if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "\timage sel=" << sel + << " sel->hasContent=" << (sel ? sel->hasContent () : 0); +#endif + if (addSelCreateCmdIfSelAvail && sel && !sel->hasContent ()) + { + QString createCmdName; + + if (dynamic_cast (sel)) { + createCmdName = i18n ("Selection: Create"); + } + else if (dynamic_cast (sel)) { + createCmdName = i18n ("Text: Create Box"); + } + else { + Q_ASSERT (!"Unknown selection type"); + } + + // create selection region + commandHistory ()->addCreateSelectionCommand ( + new kpToolSelectionCreateCommand ( + createCmdName, + *sel, + commandEnvironment ()), + false/*no exec - user already dragged out sel*/); + } + + + if (addSelContentCmdIfSelAvail && sel && !sel->hasContent ()) + { + auto *imageSel = dynamic_cast (sel); + auto *textSel = dynamic_cast (sel); + + if (imageSel && imageSel->transparency ().isTransparent ()) { + d->colorToolBar->flashColorSimilarityToolBarItem (); + } + + kpMacroCommand *macroCmd = new kpMacroCommand (cmd->name (), + commandEnvironment ()); + + if (imageSel) + { + macroCmd->addCommand ( + new kpToolSelectionPullFromDocumentCommand ( + *imageSel, + backgroundColor (), + QString()/*uninteresting child of macro cmd*/, + commandEnvironment ())); + } + else if (textSel) + { + macroCmd->addCommand ( + new kpToolTextGiveContentCommand ( + *textSel, + QString()/*uninteresting child of macro cmd*/, + commandEnvironment ())); + } + else { + Q_ASSERT (!"Unknown selection type"); + } + + macroCmd->addCommand (cmd); + + d->commandHistory->addCommand (macroCmd); + } + else + { + d->commandHistory->addCommand (cmd); + } + + + if (d->viewManager) { + d->viewManager->restoreQueueUpdates (); + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotResizeScale () +{ + toolEndShape (); + + kpTransformResizeScaleDialog dialog(transformDialogEnvironment(), this); + + if (dialog.exec () && !dialog.isNoOp ()) + { + auto *cmd = new kpTransformResizeScaleCommand ( + dialog.actOnSelection (), + dialog.imageWidth (), dialog.imageHeight (), + dialog.type (), + commandEnvironment ()); + + bool addSelCreateCommand = (dialog.actOnSelection () || + cmd->scaleSelectionWithImage ()); + bool addSelContentCommand = dialog.actOnSelection (); + + addImageOrSelectionCommand ( + cmd, + addSelCreateCommand, + addSelContentCommand); + + // Resized document? + if (!dialog.actOnSelection () && + dialog.type () == kpTransformResizeScaleCommand::Resize) + { + // TODO: this should be the responsibility of kpDocument + saveDefaultDocSize (QSize (dialog.imageWidth (), dialog.imageHeight ())); + } + } +} + +//--------------------------------------------------------------------- + +// public slot +void kpMainWindow::slotCrop () +{ + toolEndShape (); + + Q_ASSERT (d->document && d->document->selection ()); + + + ::kpTransformCrop (this); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotAutoCrop () +{ + toolEndShape (); + + ::kpTransformAutoCrop (this); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotFlip() +{ + toolEndShape(); + + addImageOrSelectionCommand( + new kpTransformFlipCommand(d->document->selection(), + false, true, commandEnvironment())); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotMirror() +{ + toolEndShape(); + + addImageOrSelectionCommand( + new kpTransformFlipCommand(d->document->selection(), + true, false, commandEnvironment())); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotRotate () +{ + toolEndShape (); + + kpTransformRotateDialog dialog (static_cast (d->document->selection ()), + transformDialogEnvironment (), this); + + if (dialog.exec () && !dialog.isNoOp ()) + { + addImageOrSelectionCommand ( + new kpTransformRotateCommand (d->document->selection (), + dialog.angle (), + commandEnvironment ())); + } +} + +// private slot +void kpMainWindow::slotRotate270 () +{ + toolEndShape (); + + // TODO: Special command name instead of just "Rotate"? + addImageOrSelectionCommand ( + new kpTransformRotateCommand ( + d->document->selection (), + 270, + commandEnvironment ())); +} + +// private slot +void kpMainWindow::slotRotate90 () +{ + toolEndShape (); + + // TODO: Special command name instead of just "Rotate"? + addImageOrSelectionCommand ( + new kpTransformRotateCommand ( + d->document->selection (), + 90, + commandEnvironment ())); +} + + +// private slot +void kpMainWindow::slotSkew () +{ + toolEndShape (); + + kpTransformSkewDialog dialog (static_cast (d->document->selection ()), + transformDialogEnvironment (), this); + + if (dialog.exec () && !dialog.isNoOp ()) + { + addImageOrSelectionCommand ( + new kpTransformSkewCommand (d->document->selection (), + dialog.horizontalAngle (), dialog.verticalAngle (), + commandEnvironment ())); + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotConvertToBlackAndWhite () +{ + toolEndShape (); + + addImageOrSelectionCommand ( + new kpEffectReduceColorsCommand (1/*depth*/, true/*dither*/, + d->document->selection (), + commandEnvironment ())); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotConvertToGrayscale () +{ + toolEndShape (); + + addImageOrSelectionCommand ( + new kpEffectGrayscaleCommand (d->document->selection (), + commandEnvironment ())); +} + +//-------------------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotInvertColors () +{ + toolEndShape (); + + addImageOrSelectionCommand ( + new kpEffectInvertCommand (d->document->selection (), + commandEnvironment ())); +} + +//-------------------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotClear () +{ + toolEndShape (); + + addImageOrSelectionCommand ( + new kpEffectClearCommand ( + d->document->selection (), + backgroundColor (), + commandEnvironment ())); +} + +//-------------------------------------------------------------------------------- + +void kpMainWindow::slotMakeConfidential() +{ + toolEndShape(); + + addImageOrSelectionCommand( + new kpEffectBlurSharpenCommand(kpEffectBlurSharpen::MakeConfidential, kpEffectBlurSharpen::MaxStrength, + d->document->selection(), commandEnvironment())); +} + +//-------------------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotMoreEffects () +{ + toolEndShape (); + + kpEffectsDialog dialog (static_cast (d->document->selection ()), + transformDialogEnvironment (), this, + d->moreEffectsDialogLastEffect); + + if (dialog.exec () && !dialog.isNoOp ()) + { + addImageOrSelectionCommand (dialog.createCommand ()); + } + + + if (d->moreEffectsDialogLastEffect != dialog.selectedEffect ()) + { + d->moreEffectsDialogLastEffect = dialog.selectedEffect (); + + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral); + + cfg.writeEntry (kpSettingMoreEffectsLastEffect, + d->moreEffectsDialogLastEffect); + cfg.sync (); + } +} + +//-------------------------------------------------------------------------------- diff --git a/mainWindow/kpMainWindow_Settings.cpp b/mainWindow/kpMainWindow_Settings.cpp new file mode 100644 index 0000000..1fa44ce --- /dev/null +++ b/mainWindow/kpMainWindow_Settings.cpp @@ -0,0 +1,161 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "mainWindow/kpMainWindow.h" +#include "kpMainWindowPrivate.h" +#include "kpLogCategories.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "tools/kpToolAction.h" +#include "widgets/toolbars/kpToolToolBar.h" +#include "environments/tools/kpToolEnvironment.h" + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::setupSettingsMenuActions () +{ + KActionCollection *ac = actionCollection (); + + + // Settings/Toolbars |> %s + setStandardToolBarMenuEnabled (true); + + // Settings/Show Statusbar + createStandardStatusBarAction (); + + + d->actionFullScreen = KStandardAction::fullScreen (this, SLOT (slotFullScreen()), + this/*window*/, ac); + + + d->actionShowPath = ac->add (QStringLiteral("settings_show_path")); + d->actionShowPath->setText (i18n ("Show &Path")); + connect (d->actionShowPath, &QAction::triggered, this, &kpMainWindow::slotShowPathToggled); + slotEnableSettingsShowPath (); + + auto *action = ac->add(QStringLiteral("settings_draw_antialiased")); + action->setText(i18n("Draw Anti-Aliased")); + action->setChecked(kpToolEnvironment::drawAntiAliased); + connect (action, &KToggleAction::triggered, this, &kpMainWindow::slotDrawAntiAliasedToggled); + + d->actionKeyBindings = KStandardAction::keyBindings (this, SLOT (slotKeyBindings()), ac); + + KStandardAction::configureToolbars(this, SLOT(configureToolbars()), actionCollection()); + + enableSettingsMenuDocumentActions (false); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::enableSettingsMenuDocumentActions (bool /*enable*/) +{ +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotFullScreen () +{ + KToggleFullScreenAction::setFullScreen( this, d->actionFullScreen->isChecked ()); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotEnableSettingsShowPath () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotEnableSettingsShowPath()"; +#endif + + const bool enable = (d->document && !d->document->url ().isEmpty ()); + + d->actionShowPath->setEnabled (enable); + d->actionShowPath->setChecked (enable && d->configShowPath); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotShowPathToggled () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotShowPathToggled()"; +#endif + + d->configShowPath = d->actionShowPath->isChecked (); + + slotUpdateCaption (); + + + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral); + + cfg.writeEntry (kpSettingShowPath, d->configShowPath); + cfg.sync (); +} + +//--------------------------------------------------------------------- + +void kpMainWindow::slotDrawAntiAliasedToggled(bool on) +{ + kpToolEnvironment::drawAntiAliased = on; + + KConfigGroup cfg(KSharedConfig::openConfig(), kpSettingsGroupGeneral); + + cfg.writeEntry(kpSettingDrawAntiAliased, kpToolEnvironment::drawAntiAliased); + cfg.sync(); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotKeyBindings () +{ + toolEndShape (); + + auto *dlg = new KShortcutsDialog(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this); + dlg->setAttribute(Qt::WA_DeleteOnClose); + dlg->addCollection(actionCollection()); + + // TODO: PROPAGATE: through mainWindow's and interprocess, by connecting to + // KShortcutsDialog::saved() signal + dlg->configure(true /* save settings */); +} + +//--------------------------------------------------------------------- diff --git a/mainWindow/kpMainWindow_StatusBar.cpp b/mainWindow/kpMainWindow_StatusBar.cpp new file mode 100644 index 0000000..22d986e --- /dev/null +++ b/mainWindow/kpMainWindow_StatusBar.cpp @@ -0,0 +1,440 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2011 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define DEBUG_STATUS_BAR (DEBUG_KP_MAIN_WINDOW && 0) + + +#include "mainWindow/kpMainWindow.h" +#include "kpMainWindowPrivate.h" + +#include +#include +#include + +#include "kpLogCategories.h" +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "tools/kpTool.h" +#include "views/manager/kpViewManager.h" +#include "kpViewScrollableContainer.h" +#include "views/kpZoomedView.h" + +#include +#include + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::addPermanentStatusBarItem (int id, int maxTextLen) +{ + QStatusBar *sb = statusBar (); + + QLabel *label = new QLabel (sb); + label->setAlignment (Qt::AlignCenter); + label->setFixedHeight (label->fontMetrics ().height () + 2); + int maxWidth = label->fontMetrics().horizontalAdvance(QLatin1Char ('8')) * maxTextLen; + // add some margins + maxWidth += label->fontMetrics ().height (); + label->setFixedWidth (maxWidth); + + // Permanent --> place on the right + sb->addPermanentWidget (label); + + d->statusBarLabels.append (label); + Q_ASSERT (d->statusBarLabels.at(id) == label); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::createStatusBar () +{ + QStatusBar *sb = statusBar(); + + // 9999 pixels "ought to be enough for anybody" + const int maxDimenLength = 4; + + d->statusBarMessageLabel = new KSqueezedTextLabel(sb); + // this is done to have the same height as the other labels in status bar; done like in kstatusbar.cpp + d->statusBarMessageLabel->setFixedHeight(d->statusBarMessageLabel->fontMetrics().height() + 2); + d->statusBarMessageLabel->setTextElideMode(Qt::ElideRight); // this is the reason why we explicitly set a widget + sb->addWidget(d->statusBarMessageLabel, 1/*stretch*/); + + addPermanentStatusBarItem (StatusBarItemShapePoints, + (maxDimenLength + 1/*,*/ + maxDimenLength) * 2 + 3/* - */); + addPermanentStatusBarItem (StatusBarItemShapeSize, + (1/*+/-*/ + maxDimenLength) * 2 + 1/*x*/); + + QString numSample = i18n("%1 x %2", 5000, 5000); // localized string; can e.g. be "5 000" + addPermanentStatusBarItem(StatusBarItemDocSize, numSample.length()); + + addPermanentStatusBarItem(StatusBarItemDocDepth, 5/*XXbpp*/); + + addPermanentStatusBarItem (StatusBarItemZoom, + 5/*1600%*/); + + d->statusBarShapeLastPointsInitialised = false; + d->statusBarShapeLastSizeInitialised = false; + d->statusBarCreated = true; +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::setStatusBarMessage (const QString &message) +{ +#if DEBUG_STATUS_BAR && 1 + qCDebug(kpLogMainWindow) << "kpMainWindow::setStatusBarMessage(" + << message + << ") ok=" << d->statusBarCreated; +#endif + + if (!d->statusBarCreated) { + return; + } + + d->statusBarMessageLabel->setText (message); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::setStatusBarShapePoints (const QPoint &startPoint, + const QPoint &endPoint) +{ +#if DEBUG_STATUS_BAR && 0 + qCDebug(kpLogMainWindow) << "kpMainWindow::setStatusBarShapePoints(" + << startPoint << "," << endPoint + << ") ok=" << d->statusBarCreated; +#endif + + if (!d->statusBarCreated) { + return; + } + + if (d->statusBarShapeLastPointsInitialised && + startPoint == d->statusBarShapeLastStartPoint && + endPoint == d->statusBarShapeLastEndPoint) + { + #if DEBUG_STATUS_BAR && 0 + qCDebug(kpLogMainWindow) << "\tNOP"; + #endif + return; + } + + QLabel *statusBarLabel = d->statusBarLabels.at (StatusBarItemShapePoints); + if (startPoint == KP_INVALID_POINT) + { + statusBarLabel->setText (QString()); + } + else if (endPoint == KP_INVALID_POINT) + { + statusBarLabel->setText (i18n ("%1,%2", + startPoint.x (), + startPoint.y ())); + } + else + { + statusBarLabel->setText (i18n ("%1,%2 - %3,%4", + startPoint.x (), + startPoint.y (), + endPoint.x (), + endPoint.y ())); + } + + d->statusBarShapeLastStartPoint = startPoint; + d->statusBarShapeLastEndPoint = endPoint; + d->statusBarShapeLastPointsInitialised = true; +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::setStatusBarShapeSize (const QSize &size) +{ +#if DEBUG_STATUS_BAR && 0 + qCDebug(kpLogMainWindow) << "kpMainWindow::setStatusBarShapeSize(" + << size + << ") ok=" << d->statusBarCreated; +#endif + + if (!d->statusBarCreated) { + return; + } + + if (d->statusBarShapeLastSizeInitialised && + size == d->statusBarShapeLastSize) + { + #if DEBUG_STATUS_BAR && 0 + qCDebug(kpLogMainWindow) << "\tNOP"; + #endif + return; + } + + QLabel *statusBarLabel = d->statusBarLabels.at (StatusBarItemShapeSize); + if (size == KP_INVALID_SIZE) + { + statusBarLabel->setText (QString()); + } + else + { + statusBarLabel->setText (i18n ("%1x%2", + size.width (), + size.height ())); + } + + d->statusBarShapeLastSize = size; + d->statusBarShapeLastSizeInitialised = true; +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::setStatusBarDocSize (const QSize &size) +{ +#if DEBUG_STATUS_BAR && 0 + qCDebug(kpLogMainWindow) << "kpMainWindow::setStatusBarDocSize(" + << size + << ") ok=" << d->statusBarCreated; +#endif + + if (!d->statusBarCreated) { + return; + } + + QLabel *statusBarLabel = d->statusBarLabels.at (StatusBarItemDocSize); + if (size == KP_INVALID_SIZE) + { + statusBarLabel->setText (QString()); + } + else + { + statusBarLabel->setText (i18n ("%1 x %2", + size.width (), + size.height ())); + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::setStatusBarDocDepth (int depth) +{ +#if DEBUG_STATUS_BAR && 0 + qCDebug(kpLogMainWindow) << "kpMainWindow::setStatusBarDocDepth(" + << depth + << ") ok=" << d->statusBarCreated; +#endif + + if (!d->statusBarCreated) { + return; + } + + QLabel *statusBarLabel = d->statusBarLabels.at (StatusBarItemDocDepth); + if (depth <= 0) + { + statusBarLabel->setText (QString()); + } + else + { + statusBarLabel->setText (i18n ("%1bpp", depth)); + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::setStatusBarZoom (int zoom) +{ +#if DEBUG_STATUS_BAR && 0 + qCDebug(kpLogMainWindow) << "kpMainWindow::setStatusBarZoom(" + << zoom + << ") ok=" << d->statusBarCreated; +#endif + + if (!d->statusBarCreated) { + return; + } + + QLabel *statusBarLabel = d->statusBarLabels.at (StatusBarItemZoom); + if (zoom <= 0) + { + statusBarLabel->setText (QString()); + } + else + { + statusBarLabel->setText (i18n ("%1%", zoom)); + } +} + +//--------------------------------------------------------------------- + +void kpMainWindow::recalculateStatusBarMessage () +{ +#if DEBUG_STATUS_BAR && 1 + qCDebug(kpLogMainWindow) << "kpMainWindow::recalculateStatusBarMessage()"; +#endif + QString scrollViewMessage = d->scrollView->statusMessage (); +#if DEBUG_STATUS_BAR && 1 + qCDebug(kpLogMainWindow) << "\tscrollViewMessage=" << scrollViewMessage; + qCDebug(kpLogMainWindow) << "\tresizing doc? " << !d->scrollView->newDocSize ().isEmpty (); + qCDebug(kpLogMainWindow) << "\tviewUnderCursor? " + << (d->viewManager && d->viewManager->viewUnderCursor ()); +#endif + + // HACK: To work around kpViewScrollableContainer's unreliable + // status messages (which in turn is due to Qt not updating + // QWidget::underMouse() on drags and we needing to hack around it) + if (!scrollViewMessage.isEmpty () && + d->scrollView->newDocSize ().isEmpty () && + d->viewManager && d->viewManager->viewUnderCursor ()) + { + #if DEBUG_STATUS_BAR && 1 + qCDebug(kpLogMainWindow) << "\t\tnot resizing & viewUnderCursor - message is wrong - clearing"; + #endif + d->scrollView->blockSignals (true); + d->scrollView->clearStatusMessage (); + d->scrollView->blockSignals (false); + + scrollViewMessage.clear (); + #if DEBUG_STATUS_BAR && 1 + qCDebug(kpLogMainWindow) << "\t\t\tdone"; + #endif + } + + if (!scrollViewMessage.isEmpty ()) + { + setStatusBarMessage (scrollViewMessage); + } + else + { + const kpTool *t = tool (); + if (t) + { + setStatusBarMessage (t->userMessage ()); + } + else + { + setStatusBarMessage (); + } + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::recalculateStatusBarShape () +{ +#if DEBUG_STATUS_BAR && 0 + qCDebug(kpLogMainWindow) << "kpMainWindow::recalculateStatusBarShape()"; +#endif + + QSize docResizeTo = d->scrollView->newDocSize (); +#if DEBUG_STATUS_BAR && 0 + qCDebug(kpLogMainWindow) << "\tdocResizeTo=" << docResizeTo; +#endif + if (docResizeTo.isValid ()) + { + const QPoint startPoint (d->document->width (), d->document->height ()); + #if DEBUG_STATUS_BAR && 0 + qCDebug(kpLogMainWindow) << "\thavedMovedFromOrgSize=" + << d->scrollView->haveMovedFromOriginalDocSize (); + #endif + if (!d->scrollView->haveMovedFromOriginalDocSize ()) + { + setStatusBarShapePoints (startPoint); + setStatusBarShapeSize (); + } + else + { + const int newWidth = docResizeTo.width (); + const int newHeight = docResizeTo.height (); + + setStatusBarShapePoints (startPoint, QPoint (newWidth, newHeight)); + const QPoint sizeAsPoint (QPoint (newWidth, newHeight) - startPoint); + setStatusBarShapeSize (QSize (sizeAsPoint.x (), sizeAsPoint.y ())); + } + } + else + { + const kpTool *t = tool (); + #if DEBUG_STATUS_BAR && 0 + qCDebug(kpLogMainWindow) << "\ttool=" << t; + #endif + if (t) + { + setStatusBarShapePoints (t->userShapeStartPoint (), + t->userShapeEndPoint ()); + setStatusBarShapeSize (t->userShapeSize ()); + } + else + { + setStatusBarShapePoints (); + setStatusBarShapeSize (); + } + } +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::recalculateStatusBar () +{ +#if DEBUG_STATUS_BAR && 1 + qCDebug(kpLogMainWindow) << "kpMainWindow::recalculateStatusBar() ok=" + << d->statusBarCreated; +#endif + + if (!d->statusBarCreated) { + return; + } + + recalculateStatusBarMessage (); + recalculateStatusBarShape (); + + if (d->document) + { + setStatusBarDocSize (QSize (d->document->width (), d->document->height ())); + setStatusBarDocDepth (d->document->image ().depth ()); + } + else + { + setStatusBarDocSize (); + setStatusBarDocDepth (); + } + + if (d->mainView) + { + setStatusBarZoom (d->mainView->zoomLevelX ()); + } + else + { + setStatusBarZoom (); + } +} + +//--------------------------------------------------------------------- diff --git a/mainWindow/kpMainWindow_Text.cpp b/mainWindow/kpMainWindow_Text.cpp new file mode 100644 index 0000000..bcb98f3 --- /dev/null +++ b/mainWindow/kpMainWindow_Text.cpp @@ -0,0 +1,435 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "mainWindow/kpMainWindow.h" +#include "kpMainWindowPrivate.h" + +#include +#include +#include +#include "kpLogCategories.h" +#include +#include +#include +#include +#include + +#include "widgets/toolbars/kpColorToolBar.h" +#include "kpDefs.h" +#include "layers/selections/text/kpTextStyle.h" +#include "tools/selection/text/kpToolText.h" +#include "widgets/toolbars/kpToolToolBar.h" +#include "widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.h" +#include "views/kpZoomedView.h" + + +// private +void kpMainWindow::setupTextToolBarActions () +{ + KActionCollection *ac = actionCollection (); + + d->actionTextFontFamily = ac->add (QStringLiteral("text_font_family")); + d->actionTextFontFamily->setText (i18n ("Font Family")); + connect (d->actionTextFontFamily, + &KSelectAction::textTriggered, + this, &kpMainWindow::slotTextFontFamilyChanged); + + d->actionTextFontSize = ac->add (QStringLiteral("text_font_size")); + d->actionTextFontSize->setText (i18n ("Font Size")); + connect (d->actionTextFontSize, + &KSelectAction::indexTriggered, + this, &kpMainWindow::slotTextFontSizeChanged); + + d->actionTextBold = ac->add (QStringLiteral("text_bold")); + d->actionTextBold->setIcon(QIcon::fromTheme(QStringLiteral("format-text-bold"))); + d->actionTextBold->setText (i18n ("Bold")); + connect (d->actionTextBold, &KToggleAction::triggered, + this, &kpMainWindow::slotTextBoldChanged); + + d->actionTextItalic = ac->add (QStringLiteral("text_italic")); + d->actionTextItalic->setIcon (QIcon::fromTheme(QStringLiteral("format-text-italic"))); + d->actionTextItalic->setText (i18n ("Italic")); + connect (d->actionTextItalic, &KToggleAction::triggered, + this, &kpMainWindow::slotTextItalicChanged); + + d->actionTextUnderline = ac->add (QStringLiteral("text_underline")); + d->actionTextUnderline->setIcon (QIcon::fromTheme(QStringLiteral("format-text-underline"))); + d->actionTextUnderline->setText (i18n ("Underline")); + connect (d->actionTextUnderline, &KToggleAction::triggered, + this, &kpMainWindow::slotTextUnderlineChanged); + + d->actionTextStrikeThru = ac->add (QStringLiteral("text_strike_thru")); + d->actionTextStrikeThru->setIcon(QIcon::fromTheme(QStringLiteral("format-text-strikethrough"))); + d->actionTextStrikeThru->setText (i18n ("Strike Through")); + connect (d->actionTextStrikeThru, &KToggleAction::triggered, + this, &kpMainWindow::slotTextStrikeThruChanged); + + + readAndApplyTextSettings (); + + + enableTextToolBarActions (false); +} + +// private +void kpMainWindow::readAndApplyTextSettings () +{ + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupText); + + const QString font (cfg.readEntry (kpSettingFontFamily, QStringLiteral ("Times"))); + d->actionTextFontFamily->setFont (font); +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "asked setFont to set to=" << font + << "- got back=" << d->actionTextFontFamily->font (); +#endif + d->actionTextFontSize->setFontSize (cfg.readEntry (kpSettingFontSize, 14)); + d->actionTextBold->setChecked (cfg.readEntry (kpSettingBold, false)); + d->actionTextItalic->setChecked (cfg.readEntry (kpSettingItalic, false)); + d->actionTextUnderline->setChecked (cfg.readEntry (kpSettingUnderline, false)); + d->actionTextStrikeThru->setChecked (cfg.readEntry (kpSettingStrikeThru, false)); + + d->textOldFontFamily = d->actionTextFontFamily->font (); + d->textOldFontSize = d->actionTextFontSize->fontSize (); +} + + +// public +void kpMainWindow::enableTextToolBarActions (bool enable) +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::enableTextToolBarActions(" << enable << ")"; +#endif + + d->actionTextFontFamily->setEnabled (enable); + d->actionTextFontSize->setEnabled (enable); + d->actionTextBold->setEnabled (enable); + d->actionTextItalic->setEnabled (enable); + d->actionTextUnderline->setEnabled (enable); + d->actionTextStrikeThru->setEnabled (enable); + + if (textToolBar ()) + { + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\thave toolbar - setShown"; + #endif + // COMPAT: KDE4 does not place the Text Tool Bar in a new row, underneath + // the Main Tool Bar, if there isn't enough room. This makes + // accessing the Text Tool Bar's buttons difficult. + textToolBar ()->setVisible (enable); + } +} + + +// private slot +void kpMainWindow::slotTextFontFamilyChanged () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotTextFontFamilyChanged() alive=" + << d->isFullyConstructed + << "fontFamily=" + << d->actionTextFontFamily->font () + << "action.currentItem=" + << d->actionTextFontFamily->currentItem (); +#endif + + if (!d->isFullyConstructed) { + return; + } + + if (d->toolText && d->toolText->hasBegun ()) + { + toolEndShape (); + d->toolText->slotFontFamilyChanged (d->actionTextFontFamily->font (), + d->textOldFontFamily); + } + + // Since editable KSelectAction's steal focus from view, switch back to mainView + // TODO: back to the last view + if (d->mainView) { + d->mainView->setFocus (); + } + + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupText); + cfg.writeEntry (kpSettingFontFamily, d->actionTextFontFamily->font ()); + cfg.sync (); + + d->textOldFontFamily = d->actionTextFontFamily->font (); +} + +// private slot +void kpMainWindow::slotTextFontSizeChanged () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotTextFontSizeChanged() alive=" + << d->isFullyConstructed + << " fontSize=" + << d->actionTextFontSize->fontSize (); +#endif + + if (!d->isFullyConstructed) { + return; + } + + if (d->toolText && d->toolText->hasBegun ()) + { + toolEndShape (); + d->toolText->slotFontSizeChanged (d->actionTextFontSize->fontSize (), + d->textOldFontSize); + } + + // Since editable KSelectAction's steal focus from view, switch back to mainView + // TODO: back to the last view + if (d->mainView) { + d->mainView->setFocus (); + } + + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupText); + cfg.writeEntry (kpSettingFontSize, d->actionTextFontSize->fontSize ()); + cfg.sync (); + + d->textOldFontSize = d->actionTextFontSize->fontSize (); +} + +// private slot +void kpMainWindow::slotTextBoldChanged () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotTextFontBoldChanged() alive=" + << d->isFullyConstructed + << " bold=" + << d->actionTextBold->isChecked (); +#endif + + if (!d->isFullyConstructed) { + return; + } + + if (d->toolText && d->toolText->hasBegun ()) + { + toolEndShape (); + d->toolText->slotBoldChanged (d->actionTextBold->isChecked ()); + } + + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupText); + cfg.writeEntry (kpSettingBold, d->actionTextBold->isChecked ()); + cfg.sync (); +} + +// private slot +void kpMainWindow::slotTextItalicChanged () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotTextFontItalicChanged() alive=" + << d->isFullyConstructed + << " bold=" + << d->actionTextItalic->isChecked (); +#endif + + if (!d->isFullyConstructed) { + return; + } + + if (d->toolText && d->toolText->hasBegun ()) + { + toolEndShape (); + d->toolText->slotItalicChanged (d->actionTextItalic->isChecked ()); + } + + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupText); + cfg.writeEntry (kpSettingItalic, d->actionTextItalic->isChecked ()); + cfg.sync (); +} + +// private slot +void kpMainWindow::slotTextUnderlineChanged () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotTextFontUnderlineChanged() alive=" + << d->isFullyConstructed + << " underline=" + << d->actionTextUnderline->isChecked (); +#endif + + if (!d->isFullyConstructed) { + return; + } + + if (d->toolText && d->toolText->hasBegun ()) + { + toolEndShape (); + d->toolText->slotUnderlineChanged (d->actionTextUnderline->isChecked ()); + } + + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupText); + cfg.writeEntry (kpSettingUnderline, d->actionTextUnderline->isChecked ()); + cfg.sync (); +} + +// private slot +void kpMainWindow::slotTextStrikeThruChanged () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotTextStrikeThruChanged() alive=" + << d->isFullyConstructed + << " strikeThru=" + << d->actionTextStrikeThru->isChecked (); +#endif + + if (!d->isFullyConstructed) { + return; + } + + if (d->toolText && d->toolText->hasBegun ()) + { + toolEndShape (); + d->toolText->slotStrikeThruChanged (d->actionTextStrikeThru->isChecked ()); + } + + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupText); + cfg.writeEntry (kpSettingStrikeThru, d->actionTextStrikeThru->isChecked ()); + cfg.sync (); +} + + +// public +KToolBar *kpMainWindow::textToolBar () +{ + return toolBar (QStringLiteral("textToolBar")); +} + +bool kpMainWindow::isTextStyleBackgroundOpaque () const +{ + if (d->toolToolBar) + { + kpToolWidgetOpaqueOrTransparent *oot = + d->toolToolBar->toolWidgetOpaqueOrTransparent (); + + if (oot) + { + return oot->isOpaque (); + } + } + + return true; +} + +// public +kpTextStyle kpMainWindow::textStyle () const +{ + return kpTextStyle (d->actionTextFontFamily->font (), + d->actionTextFontSize->fontSize (), + d->actionTextBold->isChecked (), + d->actionTextItalic->isChecked (), + d->actionTextUnderline->isChecked (), + d->actionTextStrikeThru->isChecked (), + d->colorToolBar ? d->colorToolBar->foregroundColor () : kpColor::Invalid, + d->colorToolBar ? d->colorToolBar->backgroundColor () : kpColor::Invalid, + isTextStyleBackgroundOpaque ()); +} + +// public +void kpMainWindow::setTextStyle (const kpTextStyle &textStyle_) +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::setTextStyle()"; +#endif + + d->settingTextStyle++; + + + if (textStyle_.fontFamily () != d->actionTextFontFamily->font ()) + { + d->actionTextFontFamily->setFont (textStyle_.fontFamily ()); + slotTextFontFamilyChanged (); + } + + if (textStyle_.fontSize () != d->actionTextFontSize->fontSize ()) + { + d->actionTextFontSize->setFontSize (textStyle_.fontSize ()); + slotTextFontSizeChanged (); + } + + if (textStyle_.isBold () != d->actionTextBold->isChecked ()) + { + d->actionTextBold->setChecked (textStyle_.isBold ()); + slotTextBoldChanged (); + } + + if (textStyle_.isItalic () != d->actionTextItalic->isChecked ()) + { + d->actionTextItalic->setChecked (textStyle_.isItalic ()); + slotTextItalicChanged (); + } + + if (textStyle_.isUnderline () != d->actionTextUnderline->isChecked ()) + { + d->actionTextUnderline->setChecked (textStyle_.isUnderline ()); + slotTextUnderlineChanged (); + } + + if (textStyle_.isStrikeThru () != d->actionTextStrikeThru->isChecked ()) + { + d->actionTextStrikeThru->setChecked (textStyle_.isStrikeThru ()); + slotTextStrikeThruChanged (); + } + + + if (textStyle_.foregroundColor () != d->colorToolBar->foregroundColor ()) + { + d->colorToolBar->setForegroundColor (textStyle_.foregroundColor ()); + } + + if (textStyle_.backgroundColor () != d->colorToolBar->backgroundColor ()) + { + d->colorToolBar->setBackgroundColor (textStyle_.backgroundColor ()); + } + + + if (textStyle_.isBackgroundOpaque () != isTextStyleBackgroundOpaque ()) + { + if (d->toolToolBar) + { + kpToolWidgetOpaqueOrTransparent *oot = + d->toolToolBar->toolWidgetOpaqueOrTransparent (); + + if (oot) + { + oot->setOpaque (textStyle_.isBackgroundOpaque ()); + } + } + } + + + d->settingTextStyle--; +} + +// public +int kpMainWindow::settingTextStyle () const +{ + return d->settingTextStyle; +} + diff --git a/mainWindow/kpMainWindow_Tools.cpp b/mainWindow/kpMainWindow_Tools.cpp new file mode 100644 index 0000000..c877d22 --- /dev/null +++ b/mainWindow/kpMainWindow_Tools.cpp @@ -0,0 +1,824 @@ +/* + Copyright (c) 2003-2007 Clarence Dang + Copyright (c) 2011 Martin Koller + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "mainWindow/kpMainWindow.h" +#include "kpMainWindowPrivate.h" + +#include +#include + +#include +#include +#include +#include "kpLogCategories.h" +#include + +#include "widgets/toolbars/kpColorToolBar.h" +#include "commands/kpCommandHistory.h" +#include "document/kpDocument.h" +#include "layers/selections/image/kpImageSelectionTransparency.h" +#include "tools/kpTool.h" +#include "tools/kpToolAction.h" +#include "tools/flow/kpToolBrush.h" +#include "tools/flow/kpToolColorEraser.h" +#include "tools/kpToolColorPicker.h" +#include "tools/polygonal/kpToolCurve.h" +#include "tools/selection/image/kpToolEllipticalSelection.h" +#include "tools/rectangular/kpToolEllipse.h" +#include "tools/flow/kpToolEraser.h" +#include "tools/kpToolFloodFill.h" +#include "tools/selection/image/kpToolFreeFormSelection.h" +#include "tools/polygonal/kpToolLine.h" +#include "tools/flow/kpToolPen.h" +#include "tools/polygonal/kpToolPolygon.h" +#include "tools/polygonal/kpToolPolyline.h" +#include "tools/rectangular/kpToolRectangle.h" +#include "tools/selection/image/kpToolRectSelection.h" +#include "tools/rectangular/kpToolRoundedRectangle.h" +#include "environments/tools/selection/kpToolSelectionEnvironment.h" +#include "tools/flow/kpToolSpraycan.h" +#include "tools/selection/text/kpToolText.h" +#include "widgets/toolbars/kpToolToolBar.h" +#include "widgets/toolbars/options/kpToolWidgetOpaqueOrTransparent.h" +#include "tools/kpToolZoom.h" +#include "commands/imagelib/transforms/kpTransformResizeScaleCommand.h" +#include "kpViewScrollableContainer.h" +#include "views/kpZoomedView.h" + +//--------------------------------------------------------------------- + +// private +kpToolSelectionEnvironment *kpMainWindow::toolSelectionEnvironment () +{ + if (!d->toolSelectionEnvironment) { + d->toolSelectionEnvironment = new kpToolSelectionEnvironment (this); + } + + return d->toolSelectionEnvironment; +} + +//--------------------------------------------------------------------- + +// private +kpToolEnvironment *kpMainWindow::toolEnvironment () +{ + // It's fine to return a more complex environment than required. + return toolSelectionEnvironment (); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::setupToolActions () +{ + kpToolSelectionEnvironment *toolSelEnv = toolSelectionEnvironment (); + kpToolEnvironment *toolEnv = toolEnvironment (); + + d->tools.append (d->toolFreeFormSelection = new kpToolFreeFormSelection (toolSelEnv, this)); + d->tools.append (d->toolRectSelection = new kpToolRectSelection (toolSelEnv, this)); + + d->tools.append (d->toolEllipticalSelection = new kpToolEllipticalSelection (toolSelEnv, this)); + d->tools.append (d->toolText = new kpToolText (toolSelEnv, this)); + + d->tools.append (d->toolLine = new kpToolLine (toolEnv, this)); + d->tools.append (d->toolPen = new kpToolPen (toolEnv, this)); + + d->tools.append (d->toolEraser = new kpToolEraser (toolEnv, this)); + d->tools.append (d->toolBrush = new kpToolBrush (toolEnv, this)); + + d->tools.append (d->toolFloodFill = new kpToolFloodFill (toolEnv, this)); + d->tools.append (d->toolColorPicker = new kpToolColorPicker (toolEnv, this)); + + d->tools.append (d->toolColorEraser = new kpToolColorEraser (toolEnv, this)); + d->tools.append (d->toolSpraycan = new kpToolSpraycan (toolEnv, this)); + + d->tools.append (d->toolRoundedRectangle = new kpToolRoundedRectangle (toolEnv, this)); + d->tools.append (d->toolRectangle = new kpToolRectangle (toolEnv, this)); + + d->tools.append (d->toolPolygon = new kpToolPolygon (toolEnv, this)); + d->tools.append (d->toolEllipse = new kpToolEllipse (toolEnv, this)); + + d->tools.append (d->toolPolyline = new kpToolPolyline (toolEnv, this)); + d->tools.append (d->toolCurve = new kpToolCurve (toolEnv, this)); + + d->tools.append (d->toolZoom = new kpToolZoom (toolEnv, this)); + + + KActionCollection *ac = actionCollection (); + + d->actionPrevToolOptionGroup1 = ac->addAction (QStringLiteral("prev_tool_option_group_1")); + d->actionPrevToolOptionGroup1->setText (i18n ("Previous Tool Option (Group #1)")); + ac->setDefaultShortcuts (d->actionPrevToolOptionGroup1, kpTool::shortcutForKey (Qt::Key_1)); + connect (d->actionPrevToolOptionGroup1, &QAction::triggered, + this, &kpMainWindow::slotActionPrevToolOptionGroup1); + + d->actionNextToolOptionGroup1 = ac->addAction (QStringLiteral("next_tool_option_group_1")); + d->actionNextToolOptionGroup1->setText (i18n ("Next Tool Option (Group #1)")); + ac->setDefaultShortcuts (d->actionNextToolOptionGroup1, kpTool::shortcutForKey (Qt::Key_2)); + connect (d->actionNextToolOptionGroup1, &QAction::triggered, + this, &kpMainWindow::slotActionNextToolOptionGroup1); + + d->actionPrevToolOptionGroup2 = ac->addAction (QStringLiteral("prev_tool_option_group_2")); + d->actionPrevToolOptionGroup2->setText (i18n ("Previous Tool Option (Group #2)")); + ac->setDefaultShortcuts (d->actionPrevToolOptionGroup2, kpTool::shortcutForKey (Qt::Key_3)); + connect (d->actionPrevToolOptionGroup2, &QAction::triggered, + this, &kpMainWindow::slotActionPrevToolOptionGroup2); + + d->actionNextToolOptionGroup2 = ac->addAction (QStringLiteral("next_tool_option_group_2")); + d->actionNextToolOptionGroup2->setText (i18n ("Next Tool Option (Group #2)")); + ac->setDefaultShortcuts (d->actionNextToolOptionGroup2, kpTool::shortcutForKey (Qt::Key_4)); + connect (d->actionNextToolOptionGroup2, &QAction::triggered, + this, &kpMainWindow::slotActionNextToolOptionGroup2); + + + // + // Implemented in this file (kpMainWindow_Tools.cpp), not + // kpImageWindow_Image.cpp since they're really setting tool options. + // + + d->actionDrawOpaque = ac->add (QStringLiteral("image_draw_opaque")); + d->actionDrawOpaque->setText (i18n ("&Draw Opaque")); + connect (d->actionDrawOpaque, &QAction::triggered, + this, &kpMainWindow::slotActionDrawOpaqueToggled); + + d->actionDrawColorSimilarity = ac->addAction (QStringLiteral("image_draw_color_similarity")); + d->actionDrawColorSimilarity->setText (i18n ("Draw With Color Similarity...")); + connect (d->actionDrawColorSimilarity, &QAction::triggered, + this, &kpMainWindow::slotActionDrawColorSimilarity); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::createToolBox () +{ + d->toolToolBar = new kpToolToolBar(QStringLiteral("Tool Box"), 2/*columns/rows*/, this); + d->toolToolBar->setWindowTitle(i18n("Tool Box")); + + connect (d->toolToolBar, &kpToolToolBar::sigToolSelected, + this, &kpMainWindow::slotToolSelected); + + connect (d->toolToolBar, &kpToolToolBar::toolWidgetOptionSelected, + this, &kpMainWindow::updateToolOptionPrevNextActionsEnabled); + + connect (d->toolToolBar->toolWidgetOpaqueOrTransparent(), + &kpToolWidgetOpaqueOrTransparent::isOpaqueChanged, + this, &kpMainWindow::updateActionDrawOpaqueChecked); + + updateActionDrawOpaqueChecked (); + + for (auto *tool : d->tools) { + d->toolToolBar->registerTool(tool); + } + + // (from config file) + readLastTool (); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::enableToolsDocumentActions (bool enable) +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::enableToolsDocumentsAction(" << enable << ")"; +#endif + + d->toolActionsEnabled = enable; + + if (enable && !d->toolToolBar->isEnabled ()) + { + kpTool *previousTool = d->toolToolBar->previousTool (); + + // select tool for enabled Tool Box + + if (previousTool) { + d->toolToolBar->selectPreviousTool (); + } + else + { + if (d->lastToolNumber >= 0 && d->lastToolNumber < d->tools.count ()) { + d->toolToolBar->selectTool (d->tools.at (d->lastToolNumber)); + } + else { + d->toolToolBar->selectTool (d->toolPen); + } + } + } + else if (!enable && d->toolToolBar->isEnabled ()) + { + // don't have a disabled Tool Box with a checked Tool + d->toolToolBar->selectTool (nullptr); + } + + + d->toolToolBar->setEnabled (enable); + + + for (auto *tool : d->tools) + { + kpToolAction *action = tool->action(); + if (!enable && action->isChecked()) { + action->setChecked(false); + } + + action->setEnabled(enable); + } + + + updateToolOptionPrevNextActionsEnabled (); + updateActionDrawOpaqueEnabled (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::updateToolOptionPrevNextActionsEnabled () +{ + const bool enable = d->toolActionsEnabled; + + + d->actionPrevToolOptionGroup1->setEnabled (enable && + d->toolToolBar->shownToolWidget (0) && + d->toolToolBar->shownToolWidget (0)->hasPreviousOption ()); + d->actionNextToolOptionGroup1->setEnabled (enable && + d->toolToolBar->shownToolWidget (0) && + d->toolToolBar->shownToolWidget (0)->hasNextOption ()); + + d->actionPrevToolOptionGroup2->setEnabled (enable && + d->toolToolBar->shownToolWidget (1) && + d->toolToolBar->shownToolWidget (1)->hasPreviousOption ()); + d->actionNextToolOptionGroup2->setEnabled (enable && + d->toolToolBar->shownToolWidget (1) && + d->toolToolBar->shownToolWidget (1)->hasNextOption ()); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::updateActionDrawOpaqueChecked () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::updateActionDrawOpaqueChecked()"; +#endif + + const bool drawOpaque = + (d->toolToolBar->toolWidgetOpaqueOrTransparent ()->selectedRow () == 0); +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tdrawOpaque=" << drawOpaque; +#endif + + d->actionDrawOpaque->setChecked (drawOpaque); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::updateActionDrawOpaqueEnabled () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::updateActionDrawOpaqueEnabled()"; +#endif + + const bool enable = d->toolActionsEnabled; + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tenable=" << enable + << " tool=" << (tool () ? tool ()->objectName () : nullptr) + << " (is selection=" << toolIsASelectionTool () << ")"; +#endif + + d->actionDrawOpaque->setEnabled (enable && toolIsASelectionTool ()); +} + +//--------------------------------------------------------------------- + +// public +QActionGroup *kpMainWindow::toolsActionGroup () +{ + if (!d->toolsActionGroup) { + d->toolsActionGroup = new QActionGroup (this); + } + + return d->toolsActionGroup; +} + +//--------------------------------------------------------------------- + +// public +kpTool *kpMainWindow::tool () const +{ + return d->toolToolBar ? d->toolToolBar->tool () : nullptr; +} + +//--------------------------------------------------------------------- + +// public +bool kpMainWindow::toolHasBegunShape () const +{ + kpTool *currentTool = tool (); + return (currentTool && currentTool->hasBegunShape ()); +} + +//--------------------------------------------------------------------- + +// public +bool kpMainWindow::toolIsASelectionTool (bool includingTextTool) const +{ + kpTool *currentTool = tool (); + + return ((currentTool == d->toolFreeFormSelection) || + (currentTool == d->toolRectSelection) || + (currentTool == d->toolEllipticalSelection) || + (currentTool == d->toolText && includingTextTool)); +} + +//--------------------------------------------------------------------- + +// public +bool kpMainWindow::toolIsTextTool () const +{ + return (tool () == d->toolText); +} + +//--------------------------------------------------------------------- + + +// private +void kpMainWindow::toolEndShape () +{ + if (toolHasBegunShape ()) { + tool ()->endShapeInternal (); + } +} + +//--------------------------------------------------------------------- + +// public +kpImageSelectionTransparency kpMainWindow::imageSelectionTransparency () const +{ + kpToolWidgetOpaqueOrTransparent *oot = d->toolToolBar->toolWidgetOpaqueOrTransparent (); + Q_ASSERT (oot); + + return kpImageSelectionTransparency (oot->isOpaque (), backgroundColor (), d->colorToolBar->colorSimilarity ()); +} + +//--------------------------------------------------------------------- + +// public +void kpMainWindow::setImageSelectionTransparency (const kpImageSelectionTransparency &transparency, bool forceColorChange) +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "kpMainWindow::setImageSelectionTransparency() isOpaque=" << transparency.isOpaque () + << " color=" << (transparency.transparentColor ().isValid () ? (int *) transparency.transparentColor ().toQRgb () : nullptr) + << " forceColorChange=" << forceColorChange; +#endif + + kpToolWidgetOpaqueOrTransparent *oot = d->toolToolBar->toolWidgetOpaqueOrTransparent (); + Q_ASSERT (oot); + + d->settingImageSelectionTransparency++; + + oot->setOpaque (transparency.isOpaque ()); + if (transparency.isTransparent () || forceColorChange) + { + d->colorToolBar->setColor (1, transparency.transparentColor ()); + d->colorToolBar->setColorSimilarity (transparency.colorSimilarity ()); + } + + d->settingImageSelectionTransparency--; +} + +//--------------------------------------------------------------------- + +// public +int kpMainWindow::settingImageSelectionTransparency () const +{ + return d->settingImageSelectionTransparency; +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotToolSelected (kpTool *tool) +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotToolSelected (" << tool << ")"; +#endif + + kpTool *previousTool = d->toolToolBar ? d->toolToolBar->previousTool () : nullptr; + + if (previousTool) + { + disconnect (previousTool, &kpTool::movedAndAboutToDraw, + this, &kpMainWindow::slotDragScroll); + + disconnect (previousTool, &kpTool::endedDraw, + this, &kpMainWindow::slotEndDragScroll); + + disconnect (previousTool, &kpTool::cancelledShape, + this, &kpMainWindow::slotEndDragScroll); + + disconnect (previousTool, &kpTool::userMessageChanged, + this, &kpMainWindow::recalculateStatusBarMessage); + + disconnect (previousTool, &kpTool::userShapePointsChanged, + this, &kpMainWindow::recalculateStatusBarShape); + + disconnect (previousTool, &kpTool::userShapeSizeChanged, + this, &kpMainWindow::recalculateStatusBarShape); + + + disconnect (d->colorToolBar, &kpColorToolBar::colorsSwapped, + previousTool, &kpTool::slotColorsSwappedInternal); + + disconnect (d->colorToolBar, &kpColorToolBar::foregroundColorChanged, + previousTool, &kpTool::slotForegroundColorChangedInternal); + + disconnect (d->colorToolBar, &kpColorToolBar::backgroundColorChanged, + previousTool, &kpTool::slotBackgroundColorChangedInternal); + + + disconnect (d->colorToolBar, &kpColorToolBar::colorSimilarityChanged, + previousTool, &kpTool::slotColorSimilarityChangedInternal); + } + + if (tool) + { + connect (tool, &kpTool::movedAndAboutToDraw, + this, &kpMainWindow::slotDragScroll); + + connect (tool, &kpTool::endedDraw, + this, &kpMainWindow::slotEndDragScroll); + + connect (tool, &kpTool::cancelledShape, + this, &kpMainWindow::slotEndDragScroll); + + connect (tool, &kpTool::userMessageChanged, + this, &kpMainWindow::recalculateStatusBarMessage); + + connect (tool, &kpTool::userShapePointsChanged, + this, &kpMainWindow::recalculateStatusBarShape); + + connect (tool, &kpTool::userShapeSizeChanged, + this, &kpMainWindow::recalculateStatusBarShape); + + recalculateStatusBar (); + + + connect (d->colorToolBar, &kpColorToolBar::colorsSwapped, + tool, &kpTool::slotColorsSwappedInternal); + + connect (d->colorToolBar, &kpColorToolBar::foregroundColorChanged, + tool, &kpTool::slotForegroundColorChangedInternal); + + connect (d->colorToolBar, &kpColorToolBar::backgroundColorChanged, + tool, &kpTool::slotBackgroundColorChangedInternal); + + connect (d->colorToolBar, &kpColorToolBar::colorSimilarityChanged, + tool, &kpTool::slotColorSimilarityChangedInternal); + + + saveLastTool (); + } + + updateToolOptionPrevNextActionsEnabled (); + updateActionDrawOpaqueEnabled (); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::readLastTool () +{ + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupTools); + + d->lastToolNumber = cfg.readEntry (kpSettingLastTool, -1); +} + +//--------------------------------------------------------------------- + +// private +int kpMainWindow::toolNumber() const +{ + for (int i = 0; i < d->tools.count(); i++) + if ( d->tools[i] == tool() ) + return i; + + return -1; +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::saveLastTool () +{ + int number = toolNumber (); + if ( (number < 0) || (number >= d->tools.count()) ) + return; + + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupTools); + + cfg.writeEntry (kpSettingLastTool, number); + cfg.sync (); +} + +//--------------------------------------------------------------------- + +// private +bool kpMainWindow::maybeDragScrollingMainView () const +{ + return (tool () && d->mainView && + tool ()->viewUnderStartPoint () == d->mainView); +} + +//--------------------------------------------------------------------- + +// private slot +bool kpMainWindow::slotDragScroll (const QPoint &docPoint, + const QPoint &docLastPoint, + int zoomLevel, + bool *scrolled) +{ + Q_UNUSED(docPoint) + Q_UNUSED(docLastPoint) + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotDragScroll() maybeDragScrolling=" + << maybeDragScrollingMainView (); +#endif + + if (maybeDragScrollingMainView ()) + { + return d->scrollView->beginDragScroll(zoomLevel, scrolled); + } + + return false; +} + +//--------------------------------------------------------------------- + +// private slot +bool kpMainWindow::slotEndDragScroll () +{ + // (harmless if haven't started drag scroll) + return d->scrollView->endDragScroll (); +} + +//--------------------------------------------------------------------- + + +// private slot +void kpMainWindow::slotBeganDocResize () +{ + toolEndShape (); + + recalculateStatusBarShape (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotContinuedDocResize (const QSize &) +{ + recalculateStatusBarShape (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotCancelledDocResize () +{ + recalculateStatusBar (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotEndedDocResize (const QSize &size) +{ +#define DOC_RESIZE_COMPLETED() \ +{ \ + d->docResizeToBeCompleted = false; \ + recalculateStatusBar (); \ +} + + // Prevent statusbar updates + d->docResizeToBeCompleted = true; + + d->docResizeWidth = (size.width () > 0 ? size.width () : 1); + d->docResizeHeight = (size.height () > 0 ? size.height () : 1); + + if (d->docResizeWidth == d->document->width () && + d->docResizeHeight == d->document->height ()) + { + DOC_RESIZE_COMPLETED (); + return; + } + + + // Blank status to avoid confusion if dialog comes up + setStatusBarMessage (); + setStatusBarShapePoints (); + setStatusBarShapeSize (); + + + if (kpTool::warnIfBigImageSize (d->document->width (), + d->document->height (), + d->docResizeWidth, d->docResizeHeight, + i18n ("

Resizing the image to" + " %1x%2 may take a substantial amount of memory." + " This can reduce system" + " responsiveness and cause other application resource" + " problems.

" + + "

Are you sure you want to resize the" + " image?

", + d->docResizeWidth, + d->docResizeHeight), + i18nc ("@title:window", "Resize Image?"), + i18n ("R&esize Image"), + this)) + { + d->commandHistory->addCommand ( + new kpTransformResizeScaleCommand ( + false/*doc, not sel*/, + d->docResizeWidth, d->docResizeHeight, + kpTransformResizeScaleCommand::Resize, + commandEnvironment ())); + + saveDefaultDocSize (QSize (d->docResizeWidth, d->docResizeHeight)); + } + + + DOC_RESIZE_COMPLETED (); + +#undef DOC_RESIZE_COMPLETED +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotDocResizeMessageChanged (const QString &string) +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotDocResizeMessageChanged(" << string + << ") docResizeToBeCompleted=" << d->docResizeToBeCompleted; +#else + (void) string; +#endif + + if (d->docResizeToBeCompleted) { + return; + } + + recalculateStatusBarMessage (); +} + +//--------------------------------------------------------------------- + + +// private slot +void kpMainWindow::slotActionPrevToolOptionGroup1 () +{ + if (!d->toolToolBar->shownToolWidget (0)) { + return; + } + + // We don't call toolEndShape() here because we want #23 in the file BUGS + // to later work. + + d->toolToolBar->shownToolWidget (0)->selectPreviousOption (); + updateToolOptionPrevNextActionsEnabled (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotActionNextToolOptionGroup1 () +{ + if (!d->toolToolBar->shownToolWidget (0)) { + return; + } + + // We don't call toolEndShape() here because we want #23 in the file BUGS + // to later work. + + d->toolToolBar->shownToolWidget (0)->selectNextOption (); + updateToolOptionPrevNextActionsEnabled (); +} + +//--------------------------------------------------------------------- + + +// private slot +void kpMainWindow::slotActionPrevToolOptionGroup2 () +{ + if (!d->toolToolBar->shownToolWidget (1)) { + return; + } + + // We don't call toolEndShape() here because we want #23 in the file BUGS + // to later work. + + d->toolToolBar->shownToolWidget (1)->selectPreviousOption (); + updateToolOptionPrevNextActionsEnabled (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotActionNextToolOptionGroup2 () +{ + if (!d->toolToolBar->shownToolWidget (1)) { + return; + } + + // We don't call toolEndShape() here because we want #23 in the file BUGS + // to later work. + + d->toolToolBar->shownToolWidget (1)->selectNextOption (); + updateToolOptionPrevNextActionsEnabled (); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotActionDrawOpaqueToggled () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotActionDrawOpaqueToggled()"; +#endif + toolEndShape (); + + // TODO: How does this differ to setImageSelectionTransparency()? + + // ("kpToolWidgetBase::" is to access one overload shadowed by the override + // of the other overload) + d->toolToolBar->toolWidgetOpaqueOrTransparent ()->kpToolWidgetBase::setSelected ( + (d->actionDrawOpaque->isChecked () ? + 0/*row 0 = opaque*/ : + 1/*row 1 = transparent*/), + 0/*column*/); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotActionDrawColorSimilarity () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotActionDrawColorSimilarity()"; +#endif + toolEndShape (); + + d->colorToolBar->openColorSimilarityDialog (); +} + +//--------------------------------------------------------------------- + + +// public slots + +#define SLOT_TOOL(toolName) \ +void kpMainWindow::slotTool##toolName () \ +{ \ + if (!d->toolToolBar) \ + return; \ + \ + if (tool () == d->tool##toolName) \ + return; \ + \ + d->toolToolBar->selectTool (d->tool##toolName); \ +} + + +SLOT_TOOL (RectSelection) +SLOT_TOOL (EllipticalSelection) +SLOT_TOOL (FreeFormSelection) +SLOT_TOOL (Text) diff --git a/mainWindow/kpMainWindow_View.cpp b/mainWindow/kpMainWindow_View.cpp new file mode 100644 index 0000000..9341e8e --- /dev/null +++ b/mainWindow/kpMainWindow_View.cpp @@ -0,0 +1,163 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "mainWindow/kpMainWindow.h" +#include "kpMainWindowPrivate.h" +#include "kpLogCategories.h" + +#include +#include +#include +#include +#include + +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "kpThumbnail.h" +#include "tools/kpTool.h" +#include "widgets/toolbars/kpToolToolBar.h" +#include "views/kpUnzoomedThumbnailView.h" +#include "views/manager/kpViewManager.h" +#include "kpViewScrollableContainer.h" +#include "generic/kpWidgetMapper.h" +#include "views/kpZoomedView.h" +#include "views/kpZoomedThumbnailView.h" + + +// private +void kpMainWindow::setupViewMenuActions () +{ + KActionCollection *ac = actionCollection (); + + /*d->actionFullScreen = KStandardAction::fullScreen (0, 0, ac); + d->actionFullScreen->setEnabled (false);*/ + + + setupViewMenuZoomActions (); + + + d->actionShowGrid = ac->add (QStringLiteral("view_show_grid")); + d->actionShowGrid->setText (i18n ("Show &Grid")); + ac->setDefaultShortcut (d->actionShowGrid, Qt::CTRL | Qt::Key_G); + //d->actionShowGrid->setCheckedState (KGuiItem(i18n ("Hide &Grid"))); + connect (d->actionShowGrid, &KToggleAction::triggered, + this, &kpMainWindow::slotShowGridToggled); + + + setupViewMenuThumbnailActions (); + + + enableViewMenuDocumentActions (false); +} + +//--------------------------------------------------------------------- + +// private +bool kpMainWindow::viewMenuDocumentActionsEnabled () const +{ + return d->viewMenuDocumentActionsEnabled; +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::enableViewMenuDocumentActions (bool enable) +{ + d->viewMenuDocumentActionsEnabled = enable; + + + enableViewMenuZoomDocumentActions (enable); + + actionShowGridUpdate (); + + enableViewMenuThumbnailDocumentActions (enable); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::actionShowGridUpdate () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::actionShowGridUpdate()"; +#endif + const bool enable = (viewMenuDocumentActionsEnabled () && + d->mainView && d->mainView->canShowGrid ()); + + d->actionShowGrid->setEnabled (enable); + d->actionShowGrid->setChecked (enable && d->configShowGrid); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotShowGridToggled () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotActionShowGridToggled()"; +#endif + + updateMainViewGrid (); + + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral); + + cfg.writeEntry (kpSettingShowGrid, d->configShowGrid = d->actionShowGrid->isChecked ()); + cfg.sync (); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::updateMainViewGrid () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::updateMainViewGrid ()"; +#endif + + if (d->mainView) { + d->mainView->showGrid (d->actionShowGrid->isChecked ()); + } +} + +//--------------------------------------------------------------------- + +// private +QRect kpMainWindow::mapToGlobal (const QRect &rect) const +{ + return kpWidgetMapper::toGlobal (this, rect); +} + +//--------------------------------------------------------------------- + +// private +QRect kpMainWindow::mapFromGlobal (const QRect &rect) const +{ + return kpWidgetMapper::fromGlobal (this, rect); +} + +//--------------------------------------------------------------------- diff --git a/mainWindow/kpMainWindow_View_Thumbnail.cpp b/mainWindow/kpMainWindow_View_Thumbnail.cpp new file mode 100644 index 0000000..5c4e19f --- /dev/null +++ b/mainWindow/kpMainWindow_View_Thumbnail.cpp @@ -0,0 +1,479 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "mainWindow/kpMainWindow.h" +#include "kpMainWindowPrivate.h" +#include "kpLogCategories.h" + +#include + +#include +#include +#include +#include +#include + +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "kpThumbnail.h" +#include "tools/kpTool.h" +#include "widgets/toolbars/kpToolToolBar.h" +#include "views/kpUnzoomedThumbnailView.h" +#include "views/manager/kpViewManager.h" +#include "kpViewScrollableContainer.h" +#include "generic/kpWidgetMapper.h" +#include "views/kpZoomedView.h" +#include "views/kpZoomedThumbnailView.h" + + +// private +void kpMainWindow::setupViewMenuThumbnailActions () +{ + d->thumbnailSaveConfigTimer = nullptr; + + KActionCollection *ac = actionCollection (); + + + d->actionShowThumbnail = ac->add (QStringLiteral("view_show_thumbnail")); + d->actionShowThumbnail->setText (i18n ("Show T&humbnail")); + // TODO: This doesn't work when the thumbnail has focus. + // Testcase: Press CTRL+H twice on a fresh KolourPaint. + // The second CTRL+H doesn't close the thumbnail. + ac->setDefaultShortcut (d->actionShowThumbnail, Qt::CTRL | Qt::Key_H); + //d->actionShowThumbnail->setCheckedState (KGuiItem(i18n ("Hide T&humbnail"))); + connect (d->actionShowThumbnail, &KToggleAction::triggered, + this, &kpMainWindow::slotShowThumbnailToggled); + + // Please do not use setCheckedState() here - it wouldn't make sense + d->actionZoomedThumbnail = ac->add (QStringLiteral("view_zoomed_thumbnail")); + d->actionZoomedThumbnail->setText (i18n ("Zoo&med Thumbnail Mode")); + connect (d->actionZoomedThumbnail, &KToggleAction::triggered, + this, &kpMainWindow::slotZoomedThumbnailToggled); + + // For consistency with the above action, don't use setCheckedState() + // + // Also, don't use "Show Thumbnail Rectangle" because if entire doc + // can be seen in scrollView, checking option won't "Show" anything + // since rect _surrounds_ entire doc (hence, won't be rendered). + d->actionShowThumbnailRectangle = ac->add (QStringLiteral("view_show_thumbnail_rectangle")); + d->actionShowThumbnailRectangle->setText (i18n ("Enable Thumbnail &Rectangle")); + connect (d->actionShowThumbnailRectangle, &KToggleAction::triggered, + this, &kpMainWindow::slotThumbnailShowRectangleToggled); +} + +// private +void kpMainWindow::enableViewMenuThumbnailDocumentActions (bool enable) +{ + d->actionShowThumbnail->setEnabled (enable); + enableThumbnailOptionActions (enable); +} + +// private slot +void kpMainWindow::slotDestroyThumbnail () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotDestroyThumbnail()"; +#endif + + d->actionShowThumbnail->setChecked (false); + enableThumbnailOptionActions (false); + updateThumbnail (); +} + +// private slot +void kpMainWindow::slotDestroyThumbnailInitatedByUser () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotDestroyThumbnailInitiatedByUser()"; +#endif + + d->actionShowThumbnail->setChecked (false); + slotShowThumbnailToggled (); +} + +// private slot +void kpMainWindow::slotCreateThumbnail () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotCreateThumbnail()"; +#endif + + d->actionShowThumbnail->setChecked (true); + enableThumbnailOptionActions (true); + updateThumbnail (); +} + +// public +void kpMainWindow::notifyThumbnailGeometryChanged () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::notifyThumbnailGeometryChanged()"; +#endif + + if (!d->thumbnailSaveConfigTimer) + { + d->thumbnailSaveConfigTimer = new QTimer (this); + d->thumbnailSaveConfigTimer->setSingleShot (true); + connect (d->thumbnailSaveConfigTimer, &QTimer::timeout, + this, &kpMainWindow::slotSaveThumbnailGeometry); + } + + // (single shot) + d->thumbnailSaveConfigTimer->start (500/*msec*/); +} + +// private slot +void kpMainWindow::slotSaveThumbnailGeometry () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::saveThumbnailGeometry()"; +#endif + + if (!d->thumbnail) { + return; + } + + QRect rect (d->thumbnail->x (), d->thumbnail->y (), + d->thumbnail->width (), d->thumbnail->height ()); +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tthumbnail relative geometry=" << rect; +#endif + + d->configThumbnailGeometry = mapFromGlobal (rect); + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tCONFIG: saving thumbnail geometry " + << d->configThumbnailGeometry; +#endif + + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupThumbnail); + + cfg.writeEntry (kpSettingThumbnailGeometry, d->configThumbnailGeometry); + cfg.sync (); +} + +// private slot +void kpMainWindow::slotShowThumbnailToggled () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotShowThumbnailToggled()"; +#endif + + d->configThumbnailShown = d->actionShowThumbnail->isChecked (); + + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupThumbnail); + + cfg.writeEntry (kpSettingThumbnailShown, d->configThumbnailShown); + cfg.sync (); + + + enableThumbnailOptionActions (d->actionShowThumbnail->isChecked ()); + updateThumbnail (); +} + +// private slot +void kpMainWindow::updateThumbnailZoomed () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::updateThumbnailZoomed() zoomed=" + << d->actionZoomedThumbnail->isChecked (); +#endif + + if (!d->thumbnailView) { + return; + } + + destroyThumbnailView (); + createThumbnailView (); +} + +// private slot +void kpMainWindow::slotZoomedThumbnailToggled () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotZoomedThumbnailToggled()"; +#endif + + d->configZoomedThumbnail = d->actionZoomedThumbnail->isChecked (); + + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupThumbnail); + + cfg.writeEntry (kpSettingThumbnailZoomed, d->configZoomedThumbnail); + cfg.sync (); + + + updateThumbnailZoomed (); +} + +// private slot +void kpMainWindow::slotThumbnailShowRectangleToggled () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotThumbnailShowRectangleToggled()"; +#endif + + d->configThumbnailShowRectangle = d->actionShowThumbnailRectangle->isChecked (); + + KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupThumbnail); + + cfg.writeEntry (kpSettingThumbnailShowRectangle, d->configThumbnailShowRectangle); + cfg.sync (); + + + if (d->thumbnailView) + { + d->thumbnailView->showBuddyViewScrollableContainerRectangle ( + d->actionShowThumbnailRectangle->isChecked ()); + } +} + +// private +void kpMainWindow::enableViewZoomedThumbnail (bool enable) +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::enableSettingsViewZoomedThumbnail()"; +#endif + + d->actionZoomedThumbnail->setEnabled (enable && + d->actionShowThumbnail->isChecked ()); + + // Note: Don't uncheck if disabled - being able to see the zoomed state + // before turning on the thumbnail can be useful. + d->actionZoomedThumbnail->setChecked (d->configZoomedThumbnail); +} + +// private +void kpMainWindow::enableViewShowThumbnailRectangle (bool enable) +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::enableViewShowThumbnailRectangle()"; +#endif + + d->actionShowThumbnailRectangle->setEnabled (enable && + d->actionShowThumbnail->isChecked ()); + + // Note: Don't uncheck if disabled for consistency with + // enableViewZoomedThumbnail() + d->actionShowThumbnailRectangle->setChecked ( + d->configThumbnailShowRectangle); +} + +// private +void kpMainWindow::enableThumbnailOptionActions (bool enable) +{ + enableViewZoomedThumbnail (enable); + enableViewShowThumbnailRectangle (enable); +} + + +// private +void kpMainWindow::createThumbnailView () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\tcreating new kpView:"; +#endif + + if (d->thumbnailView) + { + qCDebug(kpLogMainWindow) << "kpMainWindow::createThumbnailView() had to destroy view"; + destroyThumbnailView (); + } + + if (d->actionZoomedThumbnail->isChecked ()) + { + d->thumbnailView = new kpZoomedThumbnailView ( + d->document, d->toolToolBar, d->viewManager, + d->mainView, + nullptr/*scrollableContainer*/, + d->thumbnail); + d->thumbnailView->setObjectName ( QStringLiteral("thumbnailView" )); + } + else + { + d->thumbnailView = new kpUnzoomedThumbnailView ( + d->document, d->toolToolBar, d->viewManager, + d->mainView, + nullptr/*scrollableContainer*/, + d->thumbnail); + d->thumbnailView->setObjectName ( QStringLiteral("thumbnailView" )); + } + + d->thumbnailView->showBuddyViewScrollableContainerRectangle ( + d->actionShowThumbnailRectangle->isChecked ()); + + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\tgive kpThumbnail the kpView:"; +#endif + + if (d->thumbnail) { + d->thumbnail->setView (d->thumbnailView); + } + else { + qCCritical(kpLogMainWindow) << "kpMainWindow::createThumbnailView() no thumbnail"; + } + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\tregistering the kpView:"; +#endif + if (d->viewManager) { + d->viewManager->registerView (d->thumbnailView); + } +} + +// private +void kpMainWindow::destroyThumbnailView () +{ + if (!d->thumbnailView) { + return; + } + + if (d->viewManager) { + d->viewManager->unregisterView (d->thumbnailView); + } + + if (d->thumbnail) { + d->thumbnail->setView (nullptr); + } + + d->thumbnailView->deleteLater (); d->thumbnailView = nullptr; +} + + +// private +void kpMainWindow::updateThumbnail () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::updateThumbnail()"; +#endif + bool enable = d->actionShowThumbnail->isChecked (); + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tthumbnail=" + << bool (d->thumbnail) + << " action_isChecked=" + << enable; +#endif + + if (bool (d->thumbnail) == enable) { + return; + } + + if (!d->thumbnail) + { + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tcreating thumbnail"; + #endif + + // Read last saved geometry before creating thumbnail & friends + // in case they call notifyThumbnailGeometryChanged() + QRect thumbnailGeometry = d->configThumbnailGeometry; + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\tlast used geometry=" << thumbnailGeometry; + #endif + + d->thumbnail = new kpThumbnail (this); + + createThumbnailView (); + + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\tmoving thumbnail to right place"; + #endif + if (!thumbnailGeometry.isEmpty () && + QRect (0, 0, width (), height ()).intersects (thumbnailGeometry)) + { + const QRect geometry = mapToGlobal (thumbnailGeometry); + d->thumbnail->resize (geometry.size ()); + d->thumbnail->move (geometry.topLeft ()); + } + else + { + if (d->scrollView) + { + const int margin = 20; + const int initialWidth = 160, initialHeight = 120; + + QRect geometryRect (width () - initialWidth - margin * 2, + d->scrollView->y () + margin, + initialWidth, + initialHeight); + + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\tcreating geometry=" << geometryRect; + #endif + + geometryRect = mapToGlobal (geometryRect); + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\tmap to global=" << geometryRect; + #endif + d->thumbnail->resize (geometryRect.size ()); + d->thumbnail->move (geometryRect.topLeft ()); + } + } + + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\tshowing thumbnail"; + #endif + d->thumbnail->show (); + + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\tconnecting signal thumbnail::windowClosed to destroy slot"; + #endif + connect (d->thumbnail, &kpThumbnail::windowClosed, + this, &kpMainWindow::slotDestroyThumbnailInitatedByUser); + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\tDONE"; + #endif + } + else + { + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tdestroying thumbnail d->thumbnail=" + << d->thumbnail; + #endif + + if (d->thumbnailSaveConfigTimer && d->thumbnailSaveConfigTimer->isActive ()) + { + d->thumbnailSaveConfigTimer->stop (); + slotSaveThumbnailGeometry (); + } + + // Must be done before hiding the thumbnail to avoid triggering + // this signal - re-entering this code. + disconnect (d->thumbnail, &kpThumbnail::windowClosed, + this, &kpMainWindow::slotDestroyThumbnailInitatedByUser); + + // Avoid change/flicker of caption due to view delete + // (destroyThumbnailView()) + d->thumbnail->hide (); + + destroyThumbnailView (); + + d->thumbnail->deleteLater (); d->thumbnail = nullptr; + } +} diff --git a/mainWindow/kpMainWindow_View_Zoom.cpp b/mainWindow/kpMainWindow_View_Zoom.cpp new file mode 100644 index 0000000..371310a --- /dev/null +++ b/mainWindow/kpMainWindow_View_Zoom.cpp @@ -0,0 +1,692 @@ +// REFACTOR: Clean up bits of this file + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "mainWindow/kpMainWindow.h" +#include "kpMainWindowPrivate.h" +#include "kpLogCategories.h" + +#include + +#include +#include +#include +#include +#include + +#include "kpDefs.h" +#include "document/kpDocument.h" +#include "kpThumbnail.h" +#include "tools/kpTool.h" +#include "widgets/toolbars/kpToolToolBar.h" +#include "views/kpUnzoomedThumbnailView.h" +#include "views/manager/kpViewManager.h" +#include "kpViewScrollableContainer.h" +#include "generic/kpWidgetMapper.h" +#include "views/kpZoomedView.h" +#include "views/kpZoomedThumbnailView.h" + +static int ZoomLevelFromString (const QString &stringIn) +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow_View.cpp:ZoomLevelFromString(" << stringIn << ")"; +#endif + + // Remove any non-digits kdelibs sometimes adds behind our back :( e.g.: + // + // 1. kdelibs adds accelerators to actions' text directly + // 2. ',' is automatically added to change "1000%" to "1,000%" + QString string = stringIn; + string.remove (QRegExp ("[^0-9]")); +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\twithout non-digits='" << string << "'"; +#endif + + // Convert zoom level to number. + bool ok = false; + int zoomLevel = string.toInt (&ok); +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tzoomLevel=" << zoomLevel; +#endif + + if (!ok || zoomLevel < kpView::MinZoomLevel || zoomLevel > kpView::MaxZoomLevel) { + return 0; // error + } + + return zoomLevel; +} + +//--------------------------------------------------------------------- + +static QString ZoomLevelToString (int zoomLevel) +{ + return i18n ("%1%", zoomLevel); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::setupViewMenuZoomActions () +{ + KActionCollection *ac = actionCollection (); + + + d->actionActualSize = KStandardAction::actualSize (this, SLOT (slotActualSize()), ac); + d->actionFitToPage = KStandardAction::fitToPage (this, SLOT (slotFitToPage()), ac); + d->actionFitToWidth = KStandardAction::fitToWidth (this, SLOT (slotFitToWidth()), ac); + d->actionFitToHeight = KStandardAction::fitToHeight (this, SLOT (slotFitToHeight()), ac); + + + d->actionZoomIn = KStandardAction::zoomIn (this, SLOT (slotZoomIn()), ac); + d->actionZoomOut = KStandardAction::zoomOut (this, SLOT (slotZoomOut()), ac); + + + d->actionZoom = ac->add (QStringLiteral("view_zoom_to")); + d->actionZoom->setText (i18n ("&Zoom")); + connect (d->actionZoom, + static_cast(&KSelectAction::triggered), + this, &kpMainWindow::slotZoom); + d->actionZoom->setEditable (true); + + // create the zoom list for the 1st call to zoomTo() below + d->zoomList.append (10); d->zoomList.append (25); d->zoomList.append (33); + d->zoomList.append (50); d->zoomList.append (67); d->zoomList.append (75); + d->zoomList.append (100); + d->zoomList.append (200); d->zoomList.append (300); + d->zoomList.append (400); d->zoomList.append (600); d->zoomList.append (800); + d->zoomList.append (1000); d->zoomList.append (1200); d->zoomList.append (1600); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::enableViewMenuZoomDocumentActions (bool enable) +{ + d->actionActualSize->setEnabled (enable); + d->actionFitToPage->setEnabled (enable); + d->actionFitToWidth->setEnabled (enable); + d->actionFitToHeight->setEnabled (enable); + + d->actionZoomIn->setEnabled (enable); + d->actionZoomOut->setEnabled (enable); + d->actionZoom->setEnabled (enable); + + + // TODO: for the time being, assume that we start at zoom 100% + // with no grid + + // This function is only called when a new document is created + // or an existing document is closed. So the following will + // always be correct: + + zoomTo (100); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::sendZoomListToActionZoom () +{ + QStringList items; + + const QList ::ConstIterator zoomListEnd (d->zoomList.end ()); + for (QList ::ConstIterator it = d->zoomList.constBegin (); + it != zoomListEnd; + ++it) + { + items << ::ZoomLevelToString (*it); + } + + // Work around a KDE bug - KSelectAction::setItems() enables the action. + // David Faure said it won't be fixed because it's a feature used by + // KRecentFilesAction. + bool e = d->actionZoom->isEnabled (); + d->actionZoom->setItems (items); + if (e != d->actionZoom->isEnabled ()) { + d->actionZoom->setEnabled (e); + } +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::zoomToPre (int zoomLevel) +{ + // We're called quite early in the init process and/or when there might + // not be a document or a view so we have a lot of "if (ptr)" guards. + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::zoomToPre(" << zoomLevel << ")"; +#endif + + zoomLevel = qBound (kpView::MinZoomLevel, zoomLevel, kpView::MaxZoomLevel); + + int index = 0; + QList ::Iterator it = d->zoomList.begin (); + + while (index < d->zoomList.count () && zoomLevel > *it) + { + it++; + index++; + } + + if (zoomLevel != *it) { + d->zoomList.insert (it, zoomLevel); + } + + // OPT: We get called twice on startup. sendZoomListToActionZoom() is very slow. + sendZoomListToActionZoom (); + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tsetCurrentItem(" << index << ")"; +#endif + d->actionZoom->setCurrentItem (index); +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tcurrentItem=" + << d->actionZoom->currentItem () + << " action=" + << d->actionZoom->action (d->actionZoom->currentItem ()) + << " checkedAction" + << d->actionZoom->selectableActionGroup ()->checkedAction (); +#endif + + + if (viewMenuDocumentActionsEnabled ()) + { + d->actionActualSize->setEnabled (zoomLevel != 100); + + d->actionZoomIn->setEnabled (d->actionZoom->currentItem () < d->zoomList.count () - 1); + d->actionZoomOut->setEnabled (d->actionZoom->currentItem () > 0); + } + + + // TODO: Is this actually needed? + if (d->viewManager) { + d->viewManager->setQueueUpdates (); + } + + if (d->scrollView) + { + d->scrollView->setUpdatesEnabled (false); + } +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::zoomToPost () +{ +#if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "kpMainWindow::zoomToPost()"; +#endif + + if (d->mainView) + { + actionShowGridUpdate (); + updateMainViewGrid (); + + // Since Zoom Level KSelectAction on ToolBar grabs focus after changing + // Zoom, switch back to the Main View. + // TODO: back to the last view + d->mainView->setFocus (); + + } + + + // The view magnified and moved beneath the cursor + if (tool ()) { + tool ()->somethingBelowTheCursorChanged (); + } + + + if (d->scrollView) + { + // TODO: setUpdatesEnabled() should really return to old value + // - not necessarily "true" + d->scrollView->setUpdatesEnabled (true); + } + + if (d->viewManager && d->viewManager->queueUpdates ()/*just in case*/) { + d->viewManager->restoreQueueUpdates (); + } + + setStatusBarZoom (d->mainView ? d->mainView->zoomLevelX () : 0); + +#if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "kpMainWindow::zoomToPost() done"; +#endif +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::zoomTo (int zoomLevel, bool centerUnderCursor) +{ + zoomToPre (zoomLevel); + + + if (d->scrollView && d->mainView) + { + #if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "\tscrollView contentsX=" << d->scrollView->horizontalScrollBar()->value () + << " contentsY=" << d->scrollView->verticalScrollBar()->value () + << " contentsWidth=" << d->scrollView->widget()->width () + << " contentsHeight=" << d->scrollView->widget()->height () + << " visibleWidth=" << d->scrollView->viewport()->width () + << " visibleHeight=" << d->scrollView->viewport()->height () + << " oldZoomX=" << d->mainView->zoomLevelX () + << " oldZoomY=" << d->mainView->zoomLevelY () + << " newZoom=" << zoomLevel; + #endif + + // TODO: when changing from no scrollbars to scrollbars, Qt lies about + // visibleWidth() & visibleHeight() (doesn't take into account the + // space taken by the would-be scrollbars) until it updates the + // scrollview; hence the centering is off by about 5-10 pixels. + + // TODO: Use visibleRect() for greater accuracy? + // Or use kpAbstractScrollAreaUtils::EstimateUsableArea() + // instead of ScrollView::visible{Width,Height}(), as + // per zoomToRect()? + + int viewX, viewY; + + bool targetDocAvail = false; + double targetDocX = -1, targetDocY = -1; + + if (centerUnderCursor && + d->viewManager && d->viewManager->viewUnderCursor ()) + { + kpView *const vuc = d->viewManager->viewUnderCursor (); + QPoint viewPoint = vuc->mouseViewPoint (); + + // vuc->transformViewToDoc() returns QPoint which only has int + // accuracy so we do X and Y manually. + targetDocX = vuc->transformViewToDocX (viewPoint.x ()); + targetDocY = vuc->transformViewToDocY (viewPoint.y ()); + targetDocAvail = true; + + if (vuc != d->mainView) { + viewPoint = vuc->transformViewToOtherView (viewPoint, d->mainView); + } + + viewX = viewPoint.x (); + viewY = viewPoint.y (); + } + else + { + viewX = d->scrollView->horizontalScrollBar()->value () + + qMin (d->mainView->width (), + d->scrollView->viewport()->width ()) / 2; + viewY = d->scrollView->verticalScrollBar()->value () + + qMin (d->mainView->height (), + d->scrollView->viewport()->height ()) / 2; + } + + + int newCenterX = viewX * zoomLevel / d->mainView->zoomLevelX (); + int newCenterY = viewY * zoomLevel / d->mainView->zoomLevelY (); + + // Do the zoom. + d->mainView->setZoomLevel (zoomLevel, zoomLevel); + + #if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "\tvisibleWidth=" << d->scrollView->viewport()->width () + << " visibleHeight=" << d->scrollView->viewport()->height (); + qCDebug(kpLogMainWindow) << "\tnewCenterX=" << newCenterX + << " newCenterY=" << newCenterY; + #endif + + d->scrollView->horizontalScrollBar()->setValue(newCenterX - (d->scrollView->viewport()->width() / 2)); + d->scrollView->verticalScrollBar()->setValue(newCenterY - (d->scrollView->viewport()->height() / 2)); + + if (centerUnderCursor && + targetDocAvail && + d->viewManager && d->viewManager->viewUnderCursor ()) + { + // Move the mouse cursor so that it is still above the same + // document pixel as before the zoom. + + kpView *const vuc = d->viewManager->viewUnderCursor (); + + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tcenterUnderCursor: reposition cursor; viewUnderCursor=" + << vuc->objectName (); + #endif + + const auto viewX = vuc->transformDocToViewX (targetDocX); + const auto viewY = vuc->transformDocToViewY (targetDocY); + // Rounding error from zooming in and out :( + // TODO: do everything in terms of tool doc points in type "double". + const QPoint viewPoint (static_cast (viewX), static_cast (viewY)); + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\tdoc: (" << targetDocX << "," << targetDocY << ")" + << " viewUnderCursor: (" << viewX << "," << viewY << ")"; + #endif + + if (vuc->visibleRegion ().contains (viewPoint)) + { + const QPoint globalPoint = + kpWidgetMapper::toGlobal (vuc, viewPoint); + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\tglobalPoint=" << globalPoint; + #endif + + // TODO: Determine some sane cursor flashing indication - + // cursor movement is convenient but not conventional. + // + // Major problem: if using QApplication::setOverrideCursor() + // and in some stage of flash and window quits. + // + // Or if using kpView::setCursor() and change tool. + QCursor::setPos (globalPoint); + } + // e.g. Zoom to 200%, scroll mainView to bottom-right. + // Unzoomed Thumbnail shows top-left portion of bottom-right of + // mainView. + // + // Aim cursor at bottom-right of thumbnail and zoom out with + // CTRL+Wheel. + // + // If mainView is now small enough to largely not need scrollbars, + // Unzoomed Thumbnail scrolls to show _top-left_ portion + // _of top-left_ of mainView. + // + // Unzoomed Thumbnail no longer contains the point we zoomed out + // on top of. + else + { + #if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\t\twon't move cursor - would get outside view"; + #endif + + // TODO: Sane cursor flashing indication that indicates + // that the normal cursor movement didn't happen. + } + } + + #if DEBUG_KP_MAIN_WINDOW && 1 + qCDebug(kpLogMainWindow) << "\t\tcheck (contentsX=" << d->scrollView->horizontalScrollBar()->value () + << ",contentsY=" << d->scrollView->verticalScrollBar()->value () + << ")"; + #endif + } + + + zoomToPost (); +} + +//--------------------------------------------------------------------- + +// private +void kpMainWindow::zoomToRect (const QRect &normalizedDocRect, + bool accountForGrips, + bool careAboutWidth, bool careAboutHeight) +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::zoomToRect(normalizedDocRect=" + << normalizedDocRect + << ",accountForGrips=" << accountForGrips + << ",careAboutWidth=" << careAboutWidth + << ",careAboutHeight=" << careAboutHeight + << ")"; +#endif + // You can't care about nothing. + Q_ASSERT (careAboutWidth || careAboutHeight); + + // The size of the scroll view minus the current or future scrollbars. + const QSize usableScrollArea + (d->scrollView->maximumViewportSize().width() - d->scrollView->verticalScrollBar()->sizeHint().width(), + d->scrollView->maximumViewportSize().height() - d->scrollView->horizontalScrollBar()->sizeHint().height()); + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "size=" << d->scrollView->maximumViewportSize() + << "scrollbar w=" << d->scrollView->verticalScrollBar()->sizeHint().width() + << "usableSize=" << usableScrollArea; +#endif + // Handle rounding error, mis-estimating the scroll view size and + // cosmic rays. We do this because we really don't want unnecessary + // scrollbars. This seems to need to be at least 2 for slotFitToWidth() + // and friends. + // least 2. + // TODO: I might have fixed this but check later. + const int slack = 0; + + // The grip and slack are in view coordinates but are never zoomed. + const int viewWidth = + usableScrollArea.width () - + (accountForGrips ? kpGrip::Size : 0) - + slack; + const int viewHeight = + usableScrollArea.height () - + (accountForGrips ? kpGrip::Size : 0) - + slack; + + // We want the selected document rectangle to fill the scroll view. + // + // The integer arithmetic rounds down, rather than to the nearest zoom + // level, as rounding down guarantees that the view, at the zoom level, + // will fit inside x . + const int zoomX = + careAboutWidth ? + qMax (1, viewWidth * 100 / normalizedDocRect.width ()) : + INT_MAX; + const int zoomY = + careAboutHeight ? + qMax (1, viewHeight * 100 / normalizedDocRect.height ()) : + INT_MAX; + + // Since kpView only supports identical horizontal and vertical zooms, + // choose the one that will show the greatest amount of document + // content. + const int zoomLevel = qMin (zoomX, zoomY); + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tzoomX=" << zoomX + << " zoomY=" << zoomY + << " -> zoomLevel=" << zoomLevel; +#endif + + zoomToPre (zoomLevel); + { + d->mainView->setZoomLevel (zoomLevel, zoomLevel); + + const QPoint viewPoint = + d->mainView->transformDocToView (normalizedDocRect.topLeft ()); + + d->scrollView->horizontalScrollBar()->setValue(viewPoint.x()); + d->scrollView->verticalScrollBar()->setValue(viewPoint.y()); + } + zoomToPost (); +} + +//--------------------------------------------------------------------- + +// public slot +void kpMainWindow::slotActualSize () +{ + zoomTo (100); +} + +//--------------------------------------------------------------------- + +// public slot +void kpMainWindow::slotFitToPage () +{ + if ( d->document ) + { + zoomToRect ( + d->document->rect (), + true/*account for grips*/, + true/*care about width*/, true/*care about height*/); + } +} + +//--------------------------------------------------------------------- + +// public slot +void kpMainWindow::slotFitToWidth () +{ + if ( d->document ) + { + const QRect docRect ( + 0/*x*/, + static_cast (d->mainView->transformViewToDocY (d->scrollView->verticalScrollBar()->value ()))/*maintain y*/, + d->document->width (), + 1/*don't care about height*/); + zoomToRect ( + docRect, + true/*account for grips*/, + true/*care about width*/, false/*don't care about height*/); + } +} + +//--------------------------------------------------------------------- + +// public slot +void kpMainWindow::slotFitToHeight () +{ + if ( d->document ) + { + const QRect docRect ( + static_cast (d->mainView->transformViewToDocX (d->scrollView->horizontalScrollBar()->value ()))/*maintain x*/, + 0/*y*/, + 1/*don't care about width*/, + d->document->height ()); + zoomToRect ( + docRect, + true/*account for grips*/, + false/*don't care about width*/, true/*care about height*/); + } +} + +//--------------------------------------------------------------------- + +// public +void kpMainWindow::zoomIn (bool centerUnderCursor) +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::zoomIn(centerUnderCursor=" + << centerUnderCursor << ") currentItem=" + << d->actionZoom->currentItem (); +#endif + const int targetItem = d->actionZoom->currentItem () + 1; + + if (targetItem >= static_cast (d->zoomList.count ())) { + return; + } + + d->actionZoom->setCurrentItem (targetItem); + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tnew currentItem=" << d->actionZoom->currentItem (); +#endif + + zoomAccordingToZoomAction (centerUnderCursor); +} + +//--------------------------------------------------------------------- + +// public +void kpMainWindow::zoomOut (bool centerUnderCursor) +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::zoomOut(centerUnderCursor=" + << centerUnderCursor << ") currentItem=" + << d->actionZoom->currentItem (); +#endif + const int targetItem = d->actionZoom->currentItem () - 1; + + if (targetItem < 0) { + return; + } + + d->actionZoom->setCurrentItem (targetItem); + +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "\tnew currentItem=" << d->actionZoom->currentItem (); +#endif + + zoomAccordingToZoomAction (centerUnderCursor); +} + +//--------------------------------------------------------------------- + +// public slot +void kpMainWindow::slotZoomIn () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotZoomIn ()"; +#endif + + zoomIn (false/*don't center under cursor*/); +} + +//--------------------------------------------------------------------- + +// public slot +void kpMainWindow::slotZoomOut () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotZoomOut ()"; +#endif + + zoomOut (false/*don't center under cursor*/); +} + +//--------------------------------------------------------------------- + +// public +void kpMainWindow::zoomAccordingToZoomAction (bool centerUnderCursor) +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::zoomAccordingToZoomAction(centerUnderCursor=" + << centerUnderCursor + << ") currentItem=" << d->actionZoom->currentItem () + << " currentText=" << d->actionZoom->currentText (); +#endif + + // This might be a new zoom level the user has typed in. + zoomTo (::ZoomLevelFromString (d->actionZoom->currentText ()), + centerUnderCursor); +} + +//--------------------------------------------------------------------- + +// private slot +void kpMainWindow::slotZoom () +{ +#if DEBUG_KP_MAIN_WINDOW + qCDebug(kpLogMainWindow) << "kpMainWindow::slotZoom () index=" << d->actionZoom->currentItem () + << " text='" << d->actionZoom->currentText () << "'"; +#endif + + zoomAccordingToZoomAction (false/*don't center under cursor*/); +} + +//--------------------------------------------------------------------- diff --git a/org.kde.kolourpaint.appdata.xml b/org.kde.kolourpaint.appdata.xml new file mode 100644 index 0000000..3d13ad3 --- /dev/null +++ b/org.kde.kolourpaint.appdata.xml @@ -0,0 +1,429 @@ + + + org.kde.kolourpaint.desktop + MIT + BSD-2-Clause and LGPL-2.0+ and GFDL-1.2 + KolourPaint + الرسام + KolourPaint + KolurPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + KolourPaint + ilo KolourPaint + KolourPaint + KolourPaint + xxKolourPaintxx + KolourPaint 画图工具 + KolourPaint + Paint Program + برنامج تلوين + Програма за рисуване + Program za slikanje + Programa de dibuix + Programa de dibuix + Program pro malování + Tegneprogram + Mal- und Zeichenprogramm + Πρόγραμμα ζωγραφικής + Paint Program + Programa de dibujo + Joonistamisrakendus + Marrazteko programa + Piirto-ohjelma + Logiciel de dessin + Programa para pintar + Rajzolóprogram + Programma per pinger + Program Lukis + Programma di disegno + სახატავი პროგრამა + 그리기 프로그램 + Piešimo programa + Malepogram + Maalprogramm + Tekenprogramma + Teikneprogram + Program Paint + Programa de Pintura + Programa de desenho + Простой редактор изображений + Kresliaci program + Program za risanje + Ritprogram + ilo sitelen + Boyama Programı + Програма для малювання + xxPaint Programxx + 画图程序 + 繪圖程式 + +

+ KolourPaint is a simple painting program to quickly create raster images. + It is useful as a touch-up tool and simple image editing tasks. +

+

الرسام هو برنامج رسم بسيط لإنشاء صور نقطية بسرعة. إنه مفيد كأداة تحسين ومهام بسيطة لتحرير الصور.

+

KolourPaint е проста програма за рисуване на растерни изображения. Тя е полезна като инструмент за ретуширане и при стандартни задачи за редактиране на изображения.

+

KolourPaint je jednostavan crtaći program za brzo staranje rasterskih slika. On je koristan kao sredstvo retuširanja i jednostavne zadatke za uređivanje slika.

+

El KolourPaint és un programa senzill de dibuix per a crear ràpidament imatges ràster. És útil com a eina de retoc i tasques senzilles per a l'edició d'imatges.

+

KolourPaint és un programa senzill de dibuix per a crear ràpidament imatges ràster. És útil com a eina de retoc i tasques senzilles per a l'edició d'imatges.

+

KolourPaint er et simpelt tegneprogram til hurtigt at skabe raster-billeder. Det er et nyttigt værktøj til at tegne skitser eller udføre simple billedredigeringsopgaver.

+

KolourPaint ist ein einfach zu benutzendes Mal- und Zeichenprogramm um schnell Rasterbilder zu erstellen. Es kann für die Korrektur und einfache Bildbearbeitungsaufgaben benutzt werden.

+

Το KolourPaint είναι λενα απλό πρόγραμμα ζωραφικής για τη γρήγορη δημιουργία εικόνων χρωματικής περίπλεξης. Χρησιμεύει ως διορθωτικό και για απλές εργασίες επεξεργασίας εικόνων.

+

KolourPaint is a simple painting program to quickly create raster images. It is useful as a touch-up tool and simple image editing tasks.

+

KolourPaint es un sencillo programa de dibujo que permite crear imágenes de mapas de bits de una forma rápida. Resulta útil como herramienta de retoque y para tareas de edición sencillas.

+

KolourPaint on lihtne joonistamisrakendus rasterpiltide kiireks loomiseks. Sellega on hõlpus luua visandeid ja täita lihtsamaid piltide töötlemise ülesandeid.

+

KolourPaint margotzeko aplikazio xume bat da, «raster» irudiak azkar sortzeko balio duena. Baliagarria da berrukipen tresna gisa eta oinarrizko irudi editatzeko atazetarako.

+

KolourPaint on yksinkertainen piirto-ohjelma, jolla voi luoda nopeasti bittikartakuvia. Sitä voi käyttää myös kuvien parantamiseen ja yksinkertaiseen kuvankäsittelyyn.

+

KolourPaint est logiciel de dessin simple qui permet de créer rapidement des images. Il est pratique pour retoucher ou modifier simplement des images.

+

KolourPaint é un programa para pintar sinxelo que permite crear imaxes de mapa de bits rapidamente. É útil como ferramenta de retoque e para realizar tarefas sinxelas de edición de imaxes.

+

A KolourPaint egy egyszerű rajzolóprogram raszteres képek gyors létrehozásához. Hasznos retusálóeszközként és egyszerű képszerkesztő feladatokhoz.

+

KolourPAint es un simple programma per pinger o designar per crear rapidemente imagines raster. Il es utile como instrumento de retocco e per cargas simplice de modificar imagines.

+

KolourPaint adalah program melukis sederhana untuk membuat gambar raster dengan cepat. Ini berguna sebagai alat bantu dan tugas pengeditan gambar sederhana.

+

KolourPaint è un semplice programma di disegno per la creazione rapida di immagini. È utile come strumento da usare «al volo» e per effettuare semplici modifiche alle immagini.

+

KolourPaint는 래스터 그림을 빠르게 만드는 프로그램입니다. 간단한 보정 도구 및 편집 도구로 사용할 수 있습니다.

+

KolourPaint er et enkelt maleprogram som kjapt kan lage rasterbilder. Det er et nyttig verktøy for retusjering og enkle redigeringsoppgaver.

+

KolourPaint is en eenfach Maalprogramm, mit dat sik gau Pixelbiller opstellen laat. Dat is goot för lütte Utbetern un eenfache Bildbewerken.

+

KolourPaint is een eenvoudig tekenprogramma om snel rasterafbeeldingen te maken. Het is nuttig als een hulpmiddel voor bijwerken en eenvoudige taken voor bewerking van afbeeldingen.

+

KolourPaint er eit enkelt teikne­program for punkt­grafikk. Det er eit nyttig verktøy for retusjering og enkle redigerings­oppgåver.

+

KolourPaint jest prostym programem do malowania i szybkiego tworzenia obrazów rastrowych. Jest przydatny przy retuszowaniu i prostych pracach edytowania.

+

O KolourPaint é um programa simples de pintura que cria rapidamente imagens rasterizadas. É útil como uma ferramenta de retoques e para tarefas simples de edição de imagens.

+

KolourPaint é um programa de desenho simples, que cria rapidamente imagens rasterizadas. É útil como uma ferramenta de retoques e para tarefas simples de edição de imagens.

+

KolourPaint — простая программа для рисования и создания растровых изображений. Может быть полезна для внесения небольших правок в изображения.

+

KolourPaint je jednoduchý kresliaci program na rýchlu tvorbu rastrových obrázkov. Je užitočný ako touch-up nástroj a na jednoduché úpravy obrázkov.

+

KolourPaint je preprost program za risanje in hitro ustvarjanje rastrskih slik. Uporaben je za izboljšave slik in preprosto urejanje slik.

+

KolourPaint är ett enkelt ritprogram för att snabbt skapa punktavbildningar. Det är användbart som förbättringsverktyg och för enkla bildredigeringsuppgifter.

+

ilo KolourPaint li ilo sitelen li sitelen e sitelen pona. ona li pona kepeken li mute ala.

+

KolourPaint hızlıca taramalı resimler oluşturabileceğiniz basit bir boyama programıdır. Bir rötuş aracı ve basit resim düzenleme görevleri için yararlıdır.

+

KolourPaint — проста програма для малювання, за допомогою якої можна швидко створювати растрові зображення. Ця програма може бути корисною для виконання простих завдань з редагування зображень.

+

xxKolourPaint is a simple painting program to quickly create raster images. It is useful as a touch-up tool and simple image editing tasks.xx

+

KolourPaint 是一款简易画图程序,能够快速绘制栅格图像。您也可以将它作为简易修图工具使用。

+

KolourPaint 是一套類似小畫家的簡易繪圖程式。

+

Features:

+

الميزات:

+

Функции:

+

Svojstva:

+

Característiques:

+

Característiques:

+

Vlastnosti:

+

Funktioner:

+

Leistungsmerkmale:

+

Χαρακτηριστικά:

+

Features:

+

Funcionalidades:

+

Omadused:

+

Eginbideak:

+

Ominaisuudet:

+

Fonctionnalités :

+

Funcionalidades:

+

Szolgáltatások:

+

Characteristicas:

+

Fitur:

+

Caratteristiche:

+

თვისებები:

+

기능:

+

Galimybės:

+

Funksjoner:

+

Markmalen:

+

Mogelijkheden:

+

Funksjonar:

+

Cechy:

+

Funcionalidades:

+

Funcionalidades:

+

Возможности:

+

Funkcie:

+

Zmožnosti:

+

Funktioner:

+

ken pali:

+

Özellikler:

+

Можливості:

+

xxFeatures:xx

+

功能:

+

功能:

+
    +
  • Support for drawing various shapes - lines, rectangles, rounded rectangles, ovals and polygons
  • +
  • دعم لرسم أشكال مختلفة - خطوط ومستطيلات ومستطيلات مستديرة الشكل وأشكال بيضاوية ومضلعات
  • +
  • Поддръжка на чертане на различни фигури - линии, правоъгълници, овали и полигони.
  • +
  • Podrška za crtanje raznih oblika - linija, pravougaonika, zaobljenih pravougaonika, ovala i poligona
  • +
  • Permet dibuixar diverses formes: línies, rectangles, rectangles arrodonits, ovals i polígons
  • +
  • Permet dibuixar diverses formes: línies, rectangles, rectangles arredonits, ovals i polígons
  • +
  • Understøtter at tegne diverse former - linjer, firkanter, afrundede firkanter, ovaler og polygoner
  • +
  • Das Zeichnen verschiedener Objekte wie Linien, Rechtecke, abgerundete Rechtecke, Ellipsen und Polygone wird unterstützt.
  • +
  • Υποστήριξη για σχεδίαση διαφόρων σχημάτων - ευθείες, ορθογώνια παραλληλόγραμμα, στρογγυλεμένα ορθογώνια, ελλείψεις και πολύγωνα
  • +
  • Support for drawing various shapes - lines, rectangles, rounded rectangles, ovals and polygons
  • +
  • Permite dibujar distintas formas (líneas, rectángulos, rectángulos de esquinas redondeadas, óvalos y polígonos)
  • +
  • Mitmesuguste kujundite joonistamise toetus: jooned, ristkülikud, ümarnurksed ristkülikud, ovaalid, hulknurgad
  • +
  • Hainbat forma marraztu ditzake - lerroak, laukizuzenak, laukizuzen borobilduak, obaloak eta poligonoak
  • +
  • Piirtämistuki useille muodoille – viivoille, suorakaiteille, pyöristetyille suorakaiteille, ellipseille ja monikulmioille
  • +
  • Dessin de formes variées : lignes, rectangles, rectangles à bords arrondis, ovales, polygones
  • +
  • Permite debuxar varias formas: liñas, rectángulos, rectángulos arredondados, óvalos e polígonos.
  • +
  • Különféle alakzatok rajzolásának támogatása - vonalak, téglalapok, lekerekített téglalapok, oválisok és sokszögek
  • +
  • Supporto pro designar varie formas - lineas, rectangulos, rectangulos tundite, ovales e polygonos
  • +
  • Dukungan untuk menggambar berbagai bentuk - garis, persegi panjang, persegi panjang, bulat, oval dan poligon
  • +
  • Supporto per il disegno di varie forme - linee, rettangoli, rettangoli arrotondati, ovali e poligoni
  • +
  • 다양한 도형 그리기 - 직선, 사각형, 둥근 사각형, 타원형, 다각형
  • +
  • Støtte for å tegne mange former – linjer, rektangler, avrundede rektangler, ovaler og polygoner
  • +
  • Ünnerstütt dat Teken vun en Reeg vun Formen - Lienen, Rechtecks, afrundt Rechtecks, Ovalen un Veelecks
  • +
  • Ondersteuning voor het tekenen van verschillende vormen - lijnen, rechthoeken, afgeronde rechthoeken, ovalen en polygonen
  • +
  • Støtte for å teikna mange former – linjer, rektangel, avrunda rektangel, ellipsar og mangekantar
  • +
  • Obsługa rysowania rozmaitych kształtów - proste, prostokąty, zaokrąglone prostokąty, owale i wielokąty
  • +
  • Suporte para desenhar várias formas - linhas, rectângulos, rectângulos arredondados, ovais e polígonos
  • +
  • Suporte para desenhar várias formas - linhas, retângulos, retângulos arredondados, ovais e polígonos
  • +
  • Рисование различных фигур: отрезков, прямоугольников, скруглённых прямоугольников, овалов и многоугольников
  • +
  • Podpora pre kreslenie rôznych tvarov - čiary, obdĺžniky, zaoblené obdĺžniky, ovály a polygóny
  • +
  • Podpora risanju različnih oblik - črt, pravokotnikov, zaobljenih pravokotnikov, ovalov in mnogokotnikov
  • +
  • Stöd för att rita diverse former: linjer, rektanglar, rundade rektanglar, ovaler och polygoner
  • +
  • ona li ken sitelen e ni: linja, sike, leko en sijelo ante
  • +
  • Çeşitli şekiller, çizgiler, dörtgenler, yuvarlatılmış dörtgenler, ovaller ve poligonlar desteği
  • +
  • Підтримка малювання різноманітних форм: прямих, прямокутників, прямокутників із заокругленими кутами, овалів та багатокутників.
  • +
  • xxSupport for drawing various shapes - lines, rectangles, rounded rectangles, ovals and polygonsxx
  • +
  • 能够绘制各种几何图形 - 直线、矩形、圆角矩形、椭圆和多边形等
  • +
  • 支援繪出不同的形狀 -- 線條、矩形、圓角矩形、橢圓與多邊形等
  • +
  • Curves, lines and text
  • +
  • المنحنيات والخطوط والنص
  • +
  • Криви, линии и текст
  • +
  • Krive, linije i tekst
  • +
  • Corbes, línies i text
  • +
  • Corbes, línies i text
  • +
  • Křivky, čáry a text
  • +
  • Kurver, linjer og tekst
  • +
  • Kurven, Linien und Text
  • +
  • Καμπύλες, ευθείες και κείμενο
  • +
  • Curves, lines and text
  • +
  • Curvas, líneas y texto
  • +
  • Kõverad, sirged ja tekst
  • +
  • Kurbak, lerroak eta testua
  • +
  • Käyrät, viivat ja teksti
  • +
  • Courbes, lignes et textes
  • +
  • Curvas, liñas e texto.
  • +
  • Görbék, vonalak és szöveg
  • +
  • Curvas, lineas e texto
  • +
  • Lengkung, garis, dan teks
  • +
  • Curve, linee e testo
  • +
  • მრუდები, ხაზები და ტექსტი
  • +
  • 곡선, 직선 및 텍스트
  • +
  • Kreivės, tiesės ir tekstas
  • +
  • Kurver, linjer og tekst
  • +
  • Bagens, Lienen un Text
  • +
  • Krommen, lijnen en tekst
  • +
  • Kurver, linjer og tekst
  • +
  • Krzywe, proste i teks
  • +
  • Curvas, linhas e texto
  • +
  • Curvas, linhas e texto
  • +
  • Рисование гладких кривых, произвольных линий и вставка текста
  • +
  • Krivky, čiary a text
  • +
  • Krivulje, črte in besedilo
  • +
  • Kurvor, linjer och text
  • +
  • ona li ken sitelen e linja sike
  • +
  • Eğriler, çizgiler ve metin
  • +
  • Можливість малювання кривих, прямих та тексту.
  • +
  • xxCurves, lines and textxx
  • +
  • 曲线、直线和文本
  • +
  • 曲線、線條與文字
  • +
  • Colour picker
  • +
  • منتق الألوان
  • +
  • Избор на цвят
  • +
  • Dobavljač boja
  • +
  • Selector de color
  • +
  • Selector de color
  • +
  • Kapátko
  • +
  • Farvevælger
  • +
  • Farbwähler
  • +
  • Επιλογέας χρωμάτων
  • +
  • Colour picker
  • +
  • Selección de color
  • +
  • Värvivalija
  • +
  • Kolore hautatzailea
  • +
  • Värivalitsin
  • +
  • Sélecteur de couleur
  • +
  • Selector de cor
  • +
  • Színválasztó
  • +
  • Selector de color
  • +
  • Penukil warna
  • +
  • Selettore del colore
  • +
  • ფერების არჩევა
  • +
  • 색 선택기
  • +
  • Spalvų parinkiklis
  • +
  • Fargevelger
  • +
  • Klöörköör
  • +
  • Kleurkiezer
  • +
  • Fargeveljar
  • +
  • Wybierak barwy
  • +
  • Selecção de cores
  • +
  • Seleção de cor
  • +
  • Окно палитры
  • +
  • Výber farieb
  • +
  • Izbirnik barv
  • +
  • Färgväljare
  • +
  • ilo pi pana kule
  • +
  • Renk seçici
  • +
  • Піпетка для отримання кольору із зображення.
  • +
  • xxColour pickerxx
  • +
  • 拾色器
  • +
  • 顏色挑選器
  • +
  • Selections
  • +
  • التّحديدات
  • +
  • Селекции
  • +
  • Selekcije
  • +
  • Seleccions
  • +
  • Seleccions
  • +
  • Výběry
  • +
  • Markeringer
  • +
  • Mehre Auswahlarten
  • +
  • Επιλογές
  • +
  • Selections
  • +
  • Selecciones
  • +
  • Valikud
  • +
  • Hautapenak
  • +
  • Valintatyökalu
  • +
  • Sélections
  • +
  • Seleccións
  • +
  • Kijelölések
  • +
  • Selectiones
  • +
  • Pemilihan
  • +
  • Selezioni
  • +
  • მონიშნულები
  • +
  • 선택
  • +
  • Žymėjimai
  • +
  • Utvalg
  • +
  • Köören
  • +
  • Selecties
  • +
  • Utval
  • +
  • Zaznaczenia
  • +
  • Selecções
  • +
  • Seleções
  • +
  • Выделение областей
  • +
  • Výbery
  • +
  • Izbire
  • +
  • Markeringar
  • +
  • wile sitelen
  • +
  • Seçimler
  • +
  • Можливість позначення ділянок зображення.
  • +
  • xxSelectionsxx
  • +
  • 选区
  • +
  • 選取區
  • +
  • Rotation, monochrome and other advanced effects
  • +
  • دوران ، أحادية اللون وتأثيرات متقدمة أخرى
  • +
  • Ротация, монохромност и други ефекти
  • +
  • Rotacija, monohrom i drugi napredni efekti
  • +
  • Gir, monocrom i altres efectes avançats
  • +
  • Gir, monocrom i altres efectes avançats
  • +
  • Rotation, monokrom og andre avancerede effekter
  • +
  • Drehung, Umwandlung in Schwarzweiß und weitere Effekte können angewendet werden
  • +
  • Περιστροφή, μονοχρωμία και άλλα προηγμένα εφέ
  • +
  • Rotation, monochrome and other advanced effects
  • +
  • Rotación, conversión a monocromo y otros efectos avanzados
  • +
  • Pööramine, ühevärvilisus ja veel mõned efektid
  • +
  • Biraketa, monokromoa eta beste efektu aurreratu batzuk
  • +
  • Kierto, pelkistäminen yksiväriseksi sekä muita tehosteita
  • +
  • Rotation, monochrome et autres effets avancés
  • +
  • Rotación, monocroma e outros efectos avanzados.
  • +
  • Forgatás, fekete-fehér és egyéb speciális hatások
  • +
  • Rotation, monochrome e altere effectos avantiate
  • +
  • Perotasian, monokrom, dan efek tingkat lanjut lainnya
  • +
  • Rotazione, monocromia e altri effetti avanzati
  • +
  • მობრუნება, შავთეთრობა და სხვა დამატებითი ეფექტები
  • +
  • 회전, 흑백, 기타 고급 효과
  • +
  • Dreiing, monokrom og andre avanserte effekter
  • +
  • Dreihen, Swatt-Witt un en Reeg verwiedert Effekten
  • +
  • Roteren, monochroom en andere geavanceerde effecten
  • +
  • Dreiing, fargeredusering og fleire avanserte effektar
  • +
  • Obrót, monochromatyczność i inne zaawansowane efekty
  • +
  • Rotação, monocromáticos e outros efeitos avançados
  • +
  • Rotação, monocromático e outros efeitos avançados
  • +
  • Инструменты поворота, преобразования в чёрно-белое изображение и другие
  • +
  • Otáčanie, monochromatické a iné pokročilé efekty
  • +
  • Vrtenje, monokromatski in drugi napredni učinki
  • +
  • Rotation, svartvitt och andra avancerade effekter
  • +
  • Döndürme, tek renkli resim ve diğer gelişmiş efektler
  • +
  • Можливість обертання частин зображення, перетворення зображення на чорно-біле, різноманітні ефекти перетворення зображення.
  • +
  • xxRotation, monochrome and other advanced effectsxx
  • +
  • 旋转,纯黑白和其他高级效果
  • +
  • 旋轉、單色與其他進階效果
  • +
+
+ http://www.kolourpaint.org/ + https://bugs.kde.org/enter_bug.cgi?format=guided&product=kolourpaint + https://docs.kde.org/?application=kolourpaint + https://www.kde.org/community/donations/?app=kolourpaint&source=appdata + + + Painting in KolourPaint + الرسم على الرسام + Рисуване с KolourPaint + Pintant al KolourPaint + Pintant a KolourPaint + Malování v KolourPaint + Tegner i Kolourpaint + Malen mit KolourPaint + Ζωγραφική στο KolourPaint + Painting in KolourPaint + Dibujando con KolourPaint + Joonistamine KolourPaintis + Margako KolourPaint erabiliz + Maalaaminen KolourPaintissä + Dessin dans KolourPaint + Pintando en KolourPaint + Pingente in KolourPaint + Melukis di KolourPaint + Dipingere in KolourPaint + KolourPaint-ში ხატვა + KolourPaint로 그림 그리기 + In KolourPaint tekenen + Teikning med KolourPaint + Malowanie w KolourPaint + Pintura no KolourPaint + Pintando no KolourPaint + Рисование в KolourPaint + Kreslenie v KolourPaint + Slikanje v KolourPaint + Rita med KolourPaint + sitelen lon ilo KolourPaint + KolourPaint'te Boyama + Малювання у KolourPaint + xxPainting in KolourPaintxx + 使用 KolourPaint 绘画 + 在 KolourPaint 繪圖 + https://kde.org/images/screenshots/kolourpaint.png + + + KDE + + + kolourpaint + + + + + + + +
diff --git a/org.kde.kolourpaint.desktop b/org.kde.kolourpaint.desktop new file mode 100644 index 0000000..e460214 --- /dev/null +++ b/org.kde.kolourpaint.desktop @@ -0,0 +1,206 @@ +[Desktop Entry] + +Name=KolourPaint +Name[ar]=الرسام +Name[be]=KolourPaint +Name[bg]=KolourPaint +Name[br]=KolourPaint +Name[bs]=KolurPaint +Name[ca]=KolourPaint +Name[ca@valencia]=KolourPaint +Name[cs]=KolourPaint +Name[cy]=KolourPaint +Name[da]=KolourPaint +Name[de]=KolourPaint +Name[el]=KolourPaint +Name[en_GB]=KolourPaint +Name[eo]=KolourPaint +Name[es]=KolourPaint +Name[et]=KolourPaint +Name[eu]=KolourPaint +Name[fa]=KolourPaint +Name[fi]=KolourPaint +Name[fr]=KolourPaint +Name[ga]=KolourPaint +Name[gl]=KolourPaint +Name[he]=KolourPaint +Name[hi]=के-कलर-पेंट +Name[hne]=के-कलर-पेंट +Name[hr]=KolourPaint +Name[hu]=KolourPaint +Name[ia]=KolourPaint +Name[is]=KolourPaint +Name[it]=KolourPaint +Name[ja]=KolourPaint +Name[ka]=KolourPaint +Name[kk]=KolourPaint +Name[km]=KolourPaint +Name[ko]=KolourPaint +Name[ku]=KolourPaint +Name[lt]=KolourPaint +Name[lv]=KolourPaint +Name[ml]=കളർപെയിന്റ് +Name[mr]=कलरपेंट +Name[ms]=KolourPaint +Name[nb]=KolourPaint +Name[nds]=KolourPaint +Name[ne]=रङ पेन्ट +Name[nl]=KolourPaint +Name[nn]=KolourPaint +Name[pa]=ਕੇ-ਰੰਗ-ਪੇਂਟ +Name[pl]=KolourPaint +Name[pt]=KolourPaint +Name[pt_BR]=KolourPaint +Name[ro]=KolourPaint +Name[ru]=KolourPaint +Name[se]=KolourPaint +Name[si]=KolourPaint +Name[sk]=KolourPaint +Name[sl]=KolourPaint +Name[sr]=Колор-сликање +Name[sr@ijekavian]=Колор-сликање +Name[sr@ijekavianlatin]=Kolor-slikanje +Name[sr@latin]=Kolor-slikanje +Name[sv]=Kolourpaint +Name[ta]=நிற பெயின்ட் +Name[tg]=KolourPaint +Name[th]=วาดภาพระบายสี-K +Name[tr]=KolourPaint +Name[ug]=KolourPaint +Name[uk]=KolourPaint +Name[uz]=KolourPaint +Name[uz@cyrillic]=KolourPaint +Name[vi]=KolourPaint +Name[x-test]=xxKolourPaintxx +Name[zh_CN]=KolourPaint 画图工具 +Name[zh_HK]=KolourPaint +Name[zh_TW]=KolourPaint 小畫家 +GenericName=Paint Program +GenericName[af]=Verf Program +GenericName[ar]=برنامج تلوين +GenericName[bg]=Графичен редактор +GenericName[br]=Goulev tresañ +GenericName[bs]=Program za slikanje +GenericName[ca]=Programa de dibuix +GenericName[ca@valencia]=Programa de dibuix +GenericName[cs]=Program pro malování +GenericName[cy]=Rhaglen Peintio +GenericName[da]=Maleprogram +GenericName[de]=Mal- und Zeichenprogramm +GenericName[el]=Πρόγραμμα ζωγραφικής +GenericName[en_GB]=Paint Program +GenericName[eo]=Pentrilo +GenericName[es]=Programa de pintura +GenericName[et]=Joonistamisprogramm +GenericName[eu]=Marrazteko programa +GenericName[fa]=برنامه رنگ +GenericName[fi]=Piirto-ohjelma +GenericName[fr]=Programme de dessin +GenericName[ga]=Clár Péinteála +GenericName[gl]=Programa de debuxo +GenericName[he]=תוכנית ציור +GenericName[hi]=पेंट प्रोग्राम +GenericName[hne]=पेंट प्रोग्राम +GenericName[hr]=Program za slikanje +GenericName[hu]=Rajzolóprogram +GenericName[ia]=Programma per pinger +GenericName[is]=Myndmálunarforrit +GenericName[it]=Programma di disegno +GenericName[ja]=ペイントプログラム +GenericName[ka]=სახატავი პროგრამა +GenericName[kk]=Сурет салу бағдарламасы +GenericName[km]=កម្មវិធី​គូរ +GenericName[ko]=그리기 프로그램 +GenericName[ku]=Bernameya Nexşe Kirinê +GenericName[lt]=Piešimo programa +GenericName[lv]=Krāsošanas programma +GenericName[ml]=പെയിന്റ് പ്രോഗ്രാം +GenericName[mr]=रंग कार्यक्रम +GenericName[ms]=Program Mewarna +GenericName[nb]=Tegneprogram +GenericName[nds]=Maalprogramm +GenericName[ne]=रङ्गयाउने कार्यक्रम +GenericName[nl]=Tekenprogramma +GenericName[nn]=Teikneprogram +GenericName[pa]=ਰੰਗ ਪਰੋਗਰਾਮ +GenericName[pl]=Program Paint +GenericName[pt]=Programa de Pintura +GenericName[pt_BR]=Programa de desenho +GenericName[ro]=Program de desenare +GenericName[ru]=Простой редактор изображений +GenericName[se]=Málenprográmma +GenericName[si]=පින්තාරු වැඩසටහන +GenericName[sk]=Kresliaci program +GenericName[sl]=Program za risanje +GenericName[sr]=Програм за сликање +GenericName[sr@ijekavian]=Програм за сликање +GenericName[sr@ijekavianlatin]=Program za slikanje +GenericName[sr@latin]=Program za slikanje +GenericName[sv]=Ritprogram +GenericName[ta]=பெயிண்ட் நிரலி +GenericName[tg]=Муҳаррири графикӣ +GenericName[th]=โปรแกรมวาดภาพ +GenericName[tr]=Boyama Uygulaması +GenericName[ug]=سىزىش پروگراممىسى +GenericName[uk]=Програма для малювання +GenericName[uz]=Chizish dasturi +GenericName[uz@cyrillic]=Чизиш дастури +GenericName[vi]=Chương trình vẽ +GenericName[wa]=Program di dessinaedje +GenericName[xh]=Udweliso lwenkqubo lwepeyinti +GenericName[x-test]=xxPaint Programxx +GenericName[zh_CN]=画图程序 +GenericName[zh_HK]=繪圖程式 +GenericName[zh_TW]=繪圖程式 + +Comment=An easy-to-use paint program +Comment[ar]=برنامج تلوين سهل الاستخدام +Comment[bg]=Лесна за изпълнение програма за изпълнение +Comment[ca]=Un programa de dibuix senzill d'usar +Comment[ca@valencia]=Un programa de dibuix senzill d'utilitzar +Comment[cs]=Snadný nástroj pro malování +Comment[da]=Et nemt tegneprogram +Comment[de]=Ein einfach zu benutzendes Mal- und Zeichenprogramm +Comment[el]=Ένα εύκολο στη χρήση πρόγραμμα ζωγραφικής +Comment[en_GB]=An easy-to-use paint program +Comment[es]=Un programa para pintar fácil de usar +Comment[et]=Lihtne joonistamisprogramm +Comment[eu]=Erabiltzen erraza den marrazketa programa +Comment[fi]=Helppokäyttöinen piirto-ohjelma +Comment[fr]=Un programme de dessin facile à utiliser +Comment[gl]=Un programa de debuxo fácil de usar. +Comment[hu]=Egyszerűen használható rajzprogram +Comment[ia]=Un programma pro pinger facilemente +Comment[is]=Einfalt forrit til að teikna og mála myndir +Comment[it]=Un programma di disegno semplice da usare +Comment[ka]=ადვილი სახატავი პროგრამა +Comment[ko]=사용하기 쉬운 그리기 프로그램 +Comment[lt]=Paprasta piešimo programa +Comment[ml]=ലളിതമായി ഉപയോഗിക്കാവുന്ന പെയിന്റ് പ്രോഗ്രാം +Comment[nl]=Een gemakkelijk te gebruiken tekenprogramma +Comment[nn]=Eit teikneprogram som er lett å bruka +Comment[pl]=Łatwy w użyciu program do malowania +Comment[pt]=Um programa de pintura simples de usar +Comment[pt_BR]=Um programa de desenho fácil de usar +Comment[ru]=Простая в использовании программа для рисования +Comment[sk]=Jednoduchý maľovací program +Comment[sl]=Enostaven program za risanje +Comment[sv]=Ett lättanvänt ritprogram +Comment[tr]=Kolay kullanılır bir boyama programı +Comment[uk]=Проста у користуванні програма для малювання +Comment[x-test]=xxAn easy-to-use paint programxx +Comment[zh_CN]=一款简易画图程序 +Comment[zh_TW]=易於使用的繪圖程式 + +Icon=kolourpaint + +Type=Application +Exec=kolourpaint %u +X-DocPath=kolourpaint/index.html +StartupWMClass=kolourpaint + +# SYNC: Run kolourpaint --mimetypes +MimeType=application/x-krita;application/x-navi-animation;image/avif;image/bmp;image/gif;image/heif;image/jpeg;image/jxl;image/openraster;image/png;image/svg+xml;image/svg+xml-compressed;image/tiff;image/vnd.adobe.photoshop;image/vnd.microsoft.icon;image/vnd.wap.wbmp;image/webp;image/x-eps;image/x-exr;image/x-hdr;image/x-icns;image/x-mng;image/x-pcx;image/x-pic;image/x-portable-bitmap;image/x-portable-graymap;image/x-portable-pixmap;image/x-rgb;image/x-sun-raster;image/x-tga;image/x-xbitmap;image/x-xcf;image/x-xpixmap; + +Categories=Qt;KDE;Graphics;2DGraphics;RasterGraphics; +Terminal=false diff --git a/patches/checkerboard-faster-render.diff b/patches/checkerboard-faster-render.diff new file mode 100644 index 0000000..7cc5c8b --- /dev/null +++ b/patches/checkerboard-faster-render.diff @@ -0,0 +1,147 @@ +At 100% zoom: kpMainWindow::drawTransparentBackground() accounts for +about 75% of kpView::paintEvent()'s time. Bottleneck is +QPainter::fillRect(). QPainter::drawPixmap() seems much faster. For +800x600, renderer goes from 10ms to 1ms. + +2007-10-12: +Have not reprofiled KolourPaint under Qt4 to determine whether this patch +is still worthwhile (I suspect it still is since QPainter/X11 could not +magically have gotten faster). In any case, the patch needs to be updated +before being applied. + +--- kpmainwindow.cpp 2004-08-05 02:10:38.000000000 +1000 ++++ kpmainwindow.cpp 2004-09-29 11:24:45.000000000 +1000 +@@ -838,12 +838,116 @@ + } + + ++#if 1 ++// (indexed by [isPreview][parity]) ++static QPixmap *checkerBoardCache [2][2] = {{0, 0}, {0, 0}}; ++ ++ ++static int checkerBoardCellSize (bool isPreview) ++{ ++ return !isPreview ? 16 : 10; ++} ++ ++ ++// public ++static QPixmap *createCheckerBoardCache (bool isPreview, bool parity) ++{ ++ int cellSize = checkerBoardCellSize (isPreview); ++ const int rep = 2; // must be multiple of 2 ++ ++ QPixmap *newPixmap = new QPixmap (cellSize * rep, cellSize * rep); ++ QPainter painter (newPixmap); ++ ++ int parityAsInt = parity ? 1 : 0; ++ for (int y = 0; y < rep; y++) ++ { ++ for (int x = 0; x < rep; x++) ++ { ++ QColor col; ++ ++ if ((parityAsInt + x + y) % 2) ++ { ++ if (!isPreview) ++ col = QColor (213, 213, 213); ++ else ++ col = QColor (224, 224, 224); ++ } ++ else ++ col = Qt::white; ++ ++ painter.fillRect (x * cellSize, y * cellSize, ++ cellSize, cellSize, ++ col); ++ } ++ } ++ ++ painter.end (); ++ return newPixmap; ++} ++ ++void kpMainWindow::drawTransparentBackground (QPainter *painter, ++ int /*viewWidth*/, int /*viewHeight*/, ++ const QRect &rect, ++ bool isPreview) ++{ ++#if DEBUG_KP_MAIN_WINDOW && 1 || 1 ++ kDebug () << "\tkpMainWindow::drawTransparentBackground(rect=" ++ << rect << ")" << endl; ++ QTime totalTimer; totalTimer.start (); ++#endif ++ ++ int cellSize = checkerBoardCellSize (isPreview); ++ ++ ++ int starty = rect.y (); ++ if (starty % cellSize) ++ starty -= (starty % cellSize); ++ ++ int startx = rect.x (); ++ if (startx % cellSize) ++ startx -= (startx % cellSize); ++ ++ ++ int parity = ((startx / cellSize + starty / cellSize) % 2) ? 1 : 0; ++ ++ if (!checkerBoardCache [isPreview][parity]) ++ { ++ checkerBoardCache [isPreview][parity] = createCheckerBoardCache (isPreview, parity); ++ } ++ ++ QPixmap *tilePixmap = checkerBoardCache [isPreview][parity]; ++ for (int y = starty; y <= rect.bottom (); y += tilePixmap->height ()) ++ { ++ for (int x = startx; x <= rect.right (); x += tilePixmap->width ()) ++ { ++ painter->drawPixmap (x - rect.x (), y - rect.y (), *tilePixmap); ++ } ++ } ++ ++#if DEBUG_KP_MAIN_WINDOW && 1 || 1 ++{ ++ const int totalTimerElapsed = totalTimer.elapsed (); ++ kDebug () << "\t\ttotal=" << totalTimerElapsed << endl; ++} ++#endif ++} ++ ++ ++#else ++ + // public + void kpMainWindow::drawTransparentBackground (QPainter *painter, + int /*viewWidth*/, int /*viewHeight*/, + const QRect &rect, + bool isPreview) + { ++#if DEBUG_KP_MAIN_WINDOW && 1 ++ kDebug () << "\tkpMainWindow::drawTransparentBackground(rect=" ++ << rect << ")" << endl; ++ QTime totalTimer; totalTimer.start (); ++#endif ++ ++ + const int cellSize = !isPreview ? 16 : 10; + + int starty = rect.y (); +@@ -877,8 +982,15 @@ + } + } + painter->restore (); +-} + ++#if DEBUG_KP_MAIN_WINDOW && 1 || 1 ++{ ++ const int totalTimerElapsed = totalTimer.elapsed (); ++ kDebug () << "\t\ttotal=" << totalTimerElapsed << endl; ++} ++#endif ++} ++#endif + + // private slot + void kpMainWindow::slotUpdateCaption () diff --git a/patches/linear-sharpen-effect.diff b/patches/linear-sharpen-effect.diff new file mode 100644 index 0000000..605852f --- /dev/null +++ b/patches/linear-sharpen-effect.diff @@ -0,0 +1,150 @@ +Changes the "Sharpen" effect to Blitz::sharpen() instead of +Blitz::gaussianSharpen(), in order to avoid parameters that are currently +set in a dangerously adhoc fashion (radius, sigma, repeat). + +Unfortunately, the results do not look good. + +2007-08-14 20:35 + + +Index: imagelib/effects/kpEffectBlurSharpen.cpp +=================================================================== +--- imagelib/effects/kpEffectBlurSharpen.cpp (revision 699849) ++++ imagelib/effects/kpEffectBlurSharpen.cpp (working copy) +@@ -26,7 +26,7 @@ + */ + + +-#define DEBUG_KP_EFFECT_BLUR_SHARPEN 0 ++#define DEBUG_KP_EFFECT_BLUR_SHARPEN 1 + + + #include +@@ -46,18 +46,10 @@ + #endif + + +-static QImage BlurQImage (const QImage qimage_, int strength) ++static int RadiusForStrength (int strength) + { +- QImage qimage = qimage_; +- if (strength == 0) +- return qimage; +- +- +- // The numbers that follow were picked by experimentation to try to get +- // an effect linearly proportional to and at the same time, +- // be fast enough. +- // +- // I still have no idea what "radius" means. ++ // (must be in range and not 0) ++ Q_ASSERT (strength > 0 && strength <= kpEffectBlurSharpen::MaxStrength); + + const double RadiusMin = 1; + const double RadiusMax = 10; +@@ -67,92 +59,36 @@ static QImage BlurQImage (const QImage q + (kpEffectBlurSharpen::MaxStrength - 1); + + #if DEBUG_KP_EFFECT_BLUR_SHARPEN +- kDebug () << "kpEffectBlurSharpen.cpp:BlurQImage(strength=" << strength << ")" ++ kDebug () << "kpEffectBlurSharpen.cpp:RadiusForStrength(strength=" << strength << ")" + << " radius=" << radius + << endl; + #endif + +- +- qimage = Blitz::blur (qimage, qRound (radius)); +- +- +- return qimage; ++ return qRound (radius); + } + +-static QImage SharpenQImage (const QImage &qimage_, int strength) ++ ++static QImage BlurQImage (const QImage qimage_, int strength) + { + QImage qimage = qimage_; + if (strength == 0) + return qimage; + + +- // The numbers that follow were picked by experimentation to try to get +- // an effect linearly proportional to and at the same time, +- // be fast enough. +- // +- // I still have no idea what "radius" and "sigma" mean. +- +- const double RadiusMin = .1; +- const double RadiusMax = 2.5; +- const double radius = RadiusMin + +- (strength - 1) * +- (RadiusMax - RadiusMin) / +- (kpEffectBlurSharpen::MaxStrength - 1); +- +- const double SigmaMin = .5; +- const double SigmaMax = 3.0; +- const double sigma = SigmaMin + +- (strength - 1) * +- (SigmaMax - SigmaMin) / +- (kpEffectBlurSharpen::MaxStrength - 1); +- +- const double RepeatMin = 1; +- const double RepeatMax = 2; +- const double repeat = qRound (RepeatMin + +- (strength - 1) * +- (RepeatMax - RepeatMin) / +- (kpEffectBlurSharpen::MaxStrength - 1)); ++ qimage = Blitz::blur (qimage, ::RadiusForStrength (strength)); + +-// I guess these values are more proper as they use an auto-calculated +-// radius but they cause sharpen() to be too slow. +-#if 0 +- const double radius = 0/*auto-calculate*/; +- +- const double SigmaMin = .6; +- const double SigmaMax = 1.0; +- const double sigma = SigmaMin + +- (strength - 1) * +- (SigmaMax - SigmaMin) / +- (kpEffectBlurSharpen::MaxStrength - 1); + +- const double RepeatMin = 1; +- const double RepeatMax = 3; +- const double repeat = qRound (RepeatMin + +- (strength - 1) * +- (RepeatMax - RepeatMin) / +- (kpEffectBlurSharpen::MaxStrength - 1)); +-#endif ++ return qimage; ++} + +-#if DEBUG_KP_EFFECT_BLUR_SHARPEN +- kDebug () << "kpEffectBlurSharpen.cpp:SharpenQImage(strength=" << strength << ")" +- << " radius=" << radius +- << " sigma=" << sigma +- << " repeat=" << repeat +- << endl; +-#endif ++static QImage SharpenQImage (const QImage &qimage_, int strength) ++{ ++ QImage qimage = qimage_; ++ if (strength == 0) ++ return qimage; + + +- for (int i = 0; i < repeat; i++) +- { +- #if DEBUG_KP_EFFECT_BLUR_SHARPEN +- QTime timer; timer.start (); +- #endif +- qimage = Blitz::gaussianSharpen (qimage, radius, sigma); +- #if DEBUG_KP_EFFECT_BLUR_SHARPEN +- kDebug () << "\titeration #" + QString::number (i) +- << ": " + QString::number (timer.elapsed ()) << "ms" << endl; +- #endif +- } ++ qimage = Blitz::sharpen (qimage, ::RadiusForStrength (strength) * 10); + + + return qimage; diff --git a/pics/CMakeLists.txt b/pics/CMakeLists.txt new file mode 100644 index 0000000..c56ef12 --- /dev/null +++ b/pics/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory( action ) +add_subdirectory( app ) diff --git a/pics/action/16-actions-tool_brush.png b/pics/action/16-actions-tool_brush.png new file mode 100644 index 0000000000000000000000000000000000000000..372ff624e715c8ce737123076fa599eea96a6c23 GIT binary patch literal 675 zcmV;U0$lxxP)#dEwU9+cA2$^g!Eln6 z*LFU3S#P)3LMMMyqXHB~se*U92TV+$DG|pD0B^Ad6)NC8*3z-LZEurEWkj063S?9? z*niO4O_yTe`Gxd{@Aw88iX%82r>ncVT#sgEq7N_H8XB2ZKXNn{pM;Scfed*l9?!iC zfk3#xaU5aUC2z-RFXX>2Yw5q%8*JUvZmz4Z(TidUqL>3IWTDb*1c3(me1VHZLiWBsH&>Q{nWE^Y zJus|Hw_;K_lQg#2^)y2p2r)oQ1nBiTWV0C~$K^7LA)eZe<2(1kRIU9=9TB|x&(Y>z z9}^u(b%Rx#&#P;Ao{-h0dCup%&MO)mJ&CjW?Vun65CB_Ul(7i|47a?kFurvDhc5Mw z2|XVlM$eV7gS0;e*lr|LN&z&*|B^bw5QmSO+uEjkVjf4O1ip{E3NR^umyM;RQq`F?ypW#%PQ} zGB3RF!ejyw2?k~cC?U9o(lMYEMmvXF$@;&pw52WmIpzHIcxuGVJD%jto4ik+C-3_{ zKvmTzDh<%5yDgiw4zHa*k@%U7EVFd{>pNm@wZ1$tzVgW~`;NyPThHyet8JZqCQ(i?Ly6)JGL&rvMUO00^ambPj-?UQDe6>nxUt zI>wOpi!ftqIbvyRYly4GVx<BtoND^mwUdXa07N;m3Vr01r{j^gwo;lLQ zWmbsAj<(7SYy9c;bMd$S%9KJd&>%=kP10@$5L9Pb7LvqTOSxQ0(4u;SX{^;^HHwl_NvC-aO-EniIF6NNnSufI03&d6Ih!wC zX6#FsrOtafb-r5@%iv$Y`wxBpEkWpuk_1k_6I!D-iR~ zMUHJfG=8SLy9-4SLIe7By@ZCy^-m) z+34x%OYNfYDD*#rfbY_((>at^RnQ>FTBWv%^G%c9>0b-7EdCQ!EE+x4)zzB}EiJyM dCE#Cc@Ha^a*X4y~GcW)E002ovPDHLkV1k-A*mVE^ literal 0 HcmV?d00001 diff --git a/pics/action/16-actions-tool_color_picker.png b/pics/action/16-actions-tool_color_picker.png new file mode 100644 index 0000000000000000000000000000000000000000..2aba49a76593a87bc5dd3aaaec3aa68b38b960cf GIT binary patch literal 634 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJbFq_W2nPqp?T7vkfLzHE*NBqf z{Irtt#G+J&^73-M%)IR4v>btgLi%b8~lh_w@Ai_V)Ji z@$vKXtEi|1lG)igtXa$%85x{qoQ&lR0(AnyRYH30dK@{dKyGDa6+;b!=@b)|7Us#5 zr_7o)$8V`GV<+RDJ$qaxJ1<_mbjgxsOP4OsoSQXk^Q=vqHUq(yEnBv2+rEAK4u*LQ z3`-dpRxvWHW@K2)$T*jQaVaD7QWoZw%q-KHSr#z!)N%5)af@~c*=@6PSZ*J>DJ1N0 zSj5V(h|OVb7uq^Ebx*l5Wyg-)Cr_R}dGgGuQ)f?~2BLFk&H&N*bLTFeKY!`Mh07N& zUb=Yk@})~xE?>TS`SP_ZSFT^ZdgI~eho3H8m;nq~%aS0!U8c3Fj1|8puZwV4 zvH8s72(#vZRQWRtQ0X+9>t6Ok3Hr8@KvR5f7x4Dlfi<5!oZv;jTA@DlkBVW3+pyM3rLyiHpgD>^un8}Z z@JY__X4yu#S2)a%QX&5t9&`$98X?`S@oHIP0tkz0>VUe8iaHS|7N~KSrE9dGou6hN z?YaYu>=_hiNH#hgKSH=ktjn#qp@!~4A)iBBH^IhRRcld1Z8Vt9outbK{QqtI1~IH> U^@~7-xc~qF07*qoM6N<$f+x1ndjJ3c literal 0 HcmV?d00001 diff --git a/pics/action/16-actions-tool_ellipse.png b/pics/action/16-actions-tool_ellipse.png new file mode 100644 index 0000000000000000000000000000000000000000..4e2fe52462b19e9d30faaeae7888c85b418b79f7 GIT binary patch literal 807 zcmV+?1K9kDP)sz zAz?4A-5`4LlCfe$fyt6)uC1fH&V9~YPG=@IP^uRX9De5<&gc8PoZlf3LV=A8@VCRJ zy)7&@b$lq&=8xX}j zh?gZltt>B`{V+Z{wMGDYB$Aa|)4En{@9pVDot?*Zf@Cddxe!{&GNB74Nw?DJr^1Wp z1LZU;3`@nuE*uf^Q>aK(Jxo-@j^4O_&j5gf(8i$Qc%W%|0;UU18(0U9oG#QibbPzt zcjb^}#qVJB0?NWV@ilb*ftBVVHsFk1~ zOHzE0q!^l@7>1-sGC*YWQ?(ReM}Z=*_Pu#E*k851byk%5rKvBfXxYdufdWr~5Kq#0 z4T8*X5vUj0`I*=Cu5H}(``~cfvAc-mI+8oD4pVg4&od%WG@eYDr)ZudX)Z|8EEaHV zl^J?`tx1*(LnH5oFaBvH5`-Ti7oLbF+cLGgPR*3X!yppE0wId^1ON%y$?>78_ao1$ zXKyZ zDpXLRQe3LIMxzyDtys0Kp{7Zzwx*fMo3}WrC?fsyz~%DZyXW5X;2w@K2L1P?M}En+MNTxFG&^%WHrvnHw){ytRI*J~1@9fF4}o)!ux*EOu_(0wQ~ch17) zJs-N~FI~C1ha$2i8hz09oAc)j?|Od}Gi2@WxOcO(tbAD_GjUK`a{)6Cr9kY->Cau&f-hOe_Ben5G9h z9r1f0&%>j&bq#TkN6t8tOiY-W&o+jpO_-B`g%C3-WjKeJ)jMD%@5V+x7@?Id9OSCF z<0sD^*}82<*yR>Yf=OO3A`IA$)N#pxa(G}kmrkUtZ&nuCqL;ZvFDF0+?(#P zjUEZs5CX;tgb~|za%PNv|JauuOr+U&MWP;`#*4BDJzkgKcDpzMp{)Drl_thNkKMl> z42DyIDdAy+X)ZH{N89dF-s9# z?q*7H^Y|i_RaUDnlV{Mh!dr`qj7077n(bXBOCo}y@|vbXU6FY;{iQ$j>eZv!wRIcf zyd<8F6scW*Ht{#g%a_e(Jh%)1D=zR)d35;1#bzs;44yfCXy;IRaE2^votBqh8*OVF zozM%h*yFc(FXfH%^=o48P`}y-8W$dvtjzCoc_Ja+u(-9CL^ zubRNI)j$knKfQoUe?h{N1W_gc8RsHI5p3lijm-$Rx@xuBI_vez&kh}EsZKA*f_|J*RaJc?JgyE&ag9v4A(j}8#w$HNJ%;2DAM(|B&fOk&HE;re zI0sOY0!XR=5?I?eRe1=7m1P~m0tiKDPGGTED$Qo|Om}zp=}<5zSaBm@k@2r&!;sZyHTt`|@Y%JRmMzI63PV(`^ce1X?8`34}B$v}c(@>nb;M^W^< zsxmYVpC4PUI@wIQ3s`;HgWPynG)scfE@n0RTk*^&;2gX0^zBH=JMIXUSY z9O%CW01BSS-_p7@wzyueKQlf){&{|WK4dbPUTZX(_I#YTi+f@A5uOzWgW)275}U5u camU~JH?K^VLN}qmng9R*07*qoM6N<$f|D6(3jhEB literal 0 HcmV?d00001 diff --git a/pics/action/16-actions-tool_flood_fill.png b/pics/action/16-actions-tool_flood_fill.png new file mode 100644 index 0000000000000000000000000000000000000000..f0900a77b4c17526d0e8a481c64944b9509573a6 GIT binary patch literal 636 zcmV-?0)zdDP)^+L%VuxQdP7M-hd<#zIh8jlm+O2qY5G1ng`U3u}!CNhL|C zh=ms7$7aEQKm|cW!9puR3$q6E-puvf*<@J~S;c|N&fb~xd*|HuSVXAP;?ju4&tB9U zdAqRTvgsu5IXuz5DRnhweucje0CWEg*wH`P!%Vd_as6px36Rrh&jVuq3fNVi+U|_0 zJ8KRk)==UsDFo|0Y7P)yySu?sU-`;T5vf_ryJeNX7F@7d$?`F58>lJm#UUQ4OefzP zdC3Rb&&FI2fqFeIdo=POl;?pmA0YOWA7w9Fbnn(kS95z4D^T3rzZO~eSL!HbKA=@_wDhKnv0 z1eCgfi5SCcc~Lhi)XK8VpNrUHAsxW^p31@EW|DA|2pBCjf*TGSt7Vy7^R28T&JF!~ zefQxXzr@n<$ysJI9-RmZJ>Qy%EW_E*=lbo(F;3sW_#oiJnU_l+Yjce@$UGn;8YK?4 z8Ruv2rSj>;%#+rKlv=)%SRQqj$J$+-JyaYO3HPJC8D|O6PPU69fSI*G4udI@?N`$y{!7w_HD_!p?dGI#^0kZ<3+XR~$skM$dm WNd2~UPoo0>00005r00004b3#c}2nYxW zddg1vx2*%t%L50bqY zC8FR*n;y{75dv8QIJ?{ zvy6^NISvLq9xre@=P|%xevTLXEc!wyaB_K!naW1m8k%Ihx83B1XMW z9}*&oot(0Z9@pJBj#`H0=~>1hh837O)UDsLYMS>)7rGliFy$)!%_P;`K2Zn-V zS*B8}J8_|{r9sVcZ1?ok#5Jqk>okvpNyuWt-e^)95g8mRli-ry1K%`V-`kTAe0qIi zTZ~jvf)Uo)!Y3)Y4?iYoRFrCuSgN3dWF#2~t**LwbH(rbI?&rwv7--Mu527*=Sv-y z*98$_VtN&p(zxtHdqXxF8zF*x)viwL?Cb<+`=@uomL;Qb2~FcP9`Ox^)7m}fSd z|81-%v28*^D=Dd{V{T<*WovI`=V<5XV(;wg;Ns@u;RR7?Xku<;Y7SIv0#);Gj z>FDa=;^FP??FTXws?5sF(%Re_sLbBl&e7HpsLajD&C}J>$HT|}QufhRK*z|H1o;Is zh>D4ei%UpINJ>gcNl9P2eC6uZYuB&exOwZ=?K^i0s=o^XRVI15IEGZ*iamdl$w7g^ zHIS<_plw2>+`s?Kx$*Db>8wvy{dK+QjxDp5QThC)%IZ>1iyS}Y18eMVNyxm|+j*dF z>s6UIQ&;mH+!CS1ruH+2EyGmQ?#9&QcQ?aC*f#|VvH$+fY{bi2)Ett#8)zGYr>mdK II;Vst00`WD6aWAK literal 0 HcmV?d00001 diff --git a/pics/action/16-actions-tool_pen.png b/pics/action/16-actions-tool_pen.png new file mode 100644 index 0000000000000000000000000000000000000000..467b0d511edca61f86661d0ec46bd41cffa60f5e GIT binary patch literal 572 zcmV-C0>k}@P)1jIsl>gY{kZhA9$h6bYV|m1o{mv5g!QpzeIjh?Y7CnN?&d$mh4QI~3;M zK%hr!c6%aC#00EkB&{|(@P1*a>_GiR?-R$mKx!72!pu$cvVTgfvI6tHn#=U0&JjHT z_HiZa84a}74zNbK@to{ok6*@b(RhEmJ>b~}O1EnIf>093Rh zs};;GTKMx*+3cbWL!GwOowc2= zxi(8Fpjeo7qXRJ$!o~Oc5H%6OfUd12q)4c(XnO2pVLGXB2qDVaTUHe_TGlWlnl6TB z%v@J7C#tql14I@3lF6p;E;&E3@5s#{l#m^A4%$OQzHAW|hD~9WWx||jDx-`sU^W5B zIAn8*p8WnjBryQTp^lR+k#N6Fm@^#!147dpp)F0f%6mHM%76)ig(aE{4yTs-23HrA zI7tJdxS(VjK>&cRMP({CmBy5DL@+V#%2=Scv3MmuyB1%Lx)$ENu;&!i%sK2Pl;=dU zEJK)_j&0{$eFw!aSrvDg9FO?3RZNJ{`uKPlg@-(Qh@Lvf8L8HmJjb)pv^&pfmfg8# ztJ`I^5xkhq84W`qM5yMO80~i^k_>w2u?UZS-k`sJEoX)`}Y3b$a`RL$|nX%4YcrEHOTWoRRA?pCK zNOJ%KdHnTNfSk9#`g?wE4re@ZtHXV{yR)HWW@;ecBHN=z9c~z)L>mAgO~KEeIF!y@f#p-{?9Fl)`~5x7`@WM}Yw;hLq69ka&Ml~2NO^PhfZ*WB}%Is1CCLn*y+`O`Ee? zeXjSX+3IjrKnpZRvEj)$pa&K^C<)fpB?Ewu1`9lgHHT<8^uo4B$=p>^O}RY-W>Ems z8!*qXT!CLHCM#m{KwG355{bk_Q2@U#^ZkZtDSJ(c0))@c9?=iEPu{PaouA(2VBQGn zzE~vmkv+dqR1vhEC!nda$}(o=hl^F}Di*oi&O8b1g!~5NJL<_$#;lon#$BVd+gZAP zoL6W*-{^IFoM@A#SvE|*g^&Xi^C@Bn-TqiO6w8~{5W1k&diwhJ+47h&jApK%Im1RW zrPqT2|FenUrqY&p!E_~C(bH^&TaXU5PE7{`$Ns`sS;`>oHT2#T9#h_8Y=hrY$<$yd gc=9fO!N32`pXr%sj}tV+?*IS*07*qoM6N<$f@f~|+5i9m literal 0 HcmV?d00001 diff --git a/pics/action/16-actions-tool_rect_selection.png b/pics/action/16-actions-tool_rect_selection.png new file mode 100644 index 0000000000000000000000000000000000000000..35d9f18d89fbcf211618b158a0a428175dd09a35 GIT binary patch literal 910 zcmV;919AL`P)5fCdL?0X$(cNU9>1F6m?NSTog)hqi*cJV!J3x zw9qcp){PWOgBur01ffOS#;+&Qik~5QnwU?Y`F!`zojWs~n<}_>;Bc6k^E?0l85q!7 zqxV^`{=<#COr|C-C=H(i()AdFBuHJB5;rKK+AzYg!sxn^l3{?4Kx2upzEiw$qi+H9 zy!?X`T|aL;b@J$Elamv9Q;4>0+q322!@jz~(z8YxOIXv|ek}+qR{yhqHz=iGaiQ+- z4ecGBi35H5^e?xXn_IY6D2%1z%4E}nD}=ZZic6`(I8N>m=dp~0(BbXdMRoS4uaDB| z+7G^3+1RvSN);N=6dFJy1O4Ab#F8(-O7lpdkaHRFNQVy|4JZKQ(V407O;gjeq3e3{ z38WBytC}bp7>93F6EKQ^54?=Vd;WMDduDzd26OvLrIHd-%^UlbC{pv0kn`MRZgLpO zN)RM_84isE<2}8vpIj@>jJ1FK<%H1gR*KAur4EEZl{c#4JE}b7dTwY$OpcZLEWv=A z>eGHpgT!N_&r^?3Wf!&VRCm6rHc_23vvMZ5Q(xySO55Pr z7PT-|WO{6(fIxjtQ*OS;NoCgxffpX?#Y_I;hhHb|FRQg!TVp1-ye^gVU7XEku*(=> z7DWsIlfvT6qQpI(DG#6RyH|9~uyYr$zUtk1;7PCND%xDnyrnfXSMaf)_pQ92we!A9 zYP<|hVo6);YNj>)F?j>1R7B8yX{z`5xslP8D{O1|lC)D@Mbf@e&7`U8I*?-~9ELrM zptNBuEtxWCks^S>KX1PN=lqqC(ZtelZd*(?}`EI`y!nJJ+ zb#@-S+tu~`(sFrx{laft*U{006>Qd^2$uLhKzv65-D~a`!a7*h(C}OT;NW(xq1u## kjO+`PMV}bM`n|vNe^7AD0S=6RcK`qY07*qoM6N<$f|5hGX8-^I literal 0 HcmV?d00001 diff --git a/pics/action/16-actions-tool_rectangle.png b/pics/action/16-actions-tool_rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..b373fa46bfd8edc9296f14a0afd259fb9803b848 GIT binary patch literal 548 zcmV+<0^9wGP)r0`JA~9X>ay*I1zq-n03{VM2qKjP8ZL3{ zbD7RW)QJREB{&+-v8DOG|18GJdk;@3^>X#~yUopt63@89EVTG|o z!V*7ukDbGI_e~_S_hV-{3e@)2mJdBc@=WZR1z1=SAv1d5*mDB*qNpf57bMIq{C;!& zMHD~)qLnBJP!ux>V}Qg5U@Qg303uV6DJ~djaK*#Ojs*n@-b*E!N(GK04x%gj$^kC}wfUet}QNNXr23UwhZX!S;x=WuwelD z91B+`x|%lgvnFZOwRKjR{eOa=?cwQ{-K&!-P#dk57EEoSXOrn)=+Vc5@9=tIwFkIW=aj2k@P5AKr!#{ovTnn4YsXNsNi9)|&8Wjl>*Ts{l6e mW`6}MCKy3=1vY*1@B9}*x^o$e&Z-vx00008+~FEE4<7arcn`7WIAeCIyy!2p1c6Xrg-c=-y} zDXuY~aq^IQM`Kmv`^wTH>^F!mfBE|0Y&>zRu=C66?Tynwutg{ZN(1x<9`|zPcbRPF z)4@*mzk=B)HN-0fI3ph31Td)@v{jlnco@lHI5K_wu`U@oZSOaSLXahGay4xBa+Z!qADAzo`Jn##HbW?lO%=Zwe+nj-+;#Tr z+>_P0MJKbl&1>k0Aci2G)(p0sa59+`smbo#>_R!GCXvss#5$t!@xRM-{_5m_pX8o{taX^pom&R6>;J~K z2x+oR)8``ugljvce8g{uoEkedyfF80#cOb^Wjgjc{{c{=m9|*cr11a%002ovPDHLk FV1lJDSy})9 literal 0 HcmV?d00001 diff --git a/pics/action/16-actions-tool_spraycan.png b/pics/action/16-actions-tool_spraycan.png new file mode 100644 index 0000000000000000000000000000000000000000..f34b2a1d4923f60bf549024b59f7347ef91e8f13 GIT binary patch literal 776 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 F0RT4*sEhyr literal 0 HcmV?d00001 diff --git a/pics/action/22-actions-tool_curve.png b/pics/action/22-actions-tool_curve.png new file mode 100644 index 0000000000000000000000000000000000000000..9723e2093fa098cea3f1ceb6ab55fb9622255f34 GIT binary patch literal 639 zcmV-_0)YLAP)b zmA`L|VHk$5>pe{+jnD*xAS4VTv3&9QPF0bx5z~v|7qqi3gPSWfGa7G1q1AG8?D&N5!kH;RpTqyWY6iJ$u zfz#@H;9!h#^ONz>qH+vqLax>AI7zc+;F-D}*gtdZ9mQQELJ{3QG4k+vJ&up78-TvN zEQNj)p=+Uz84p7rO=rFcI!t_yJvkggr4bDaJ7E0 z_NyO52#0o;Iw}B3nuSv5z7rvYF5vaoFQ5A$XS@ANt)+5DAruDCg7Qpl*04#9u~s`g zTphf=C@?@7!KzAYtxdB~);?y;7M7j-*mFw z>o!_`sFY0YeCb8g)wNyL*EdRpneY3?SAGB$`7W0lHo!fnzR8_AeS8g}tU?HT_v}wS zM^=Cz+tS%Bxck|2J9d~?UwKW{)Yi!mqI3{Lky2_AE+LhI3OPA3Mvi^`c|+l1Umc;= zo?p-P9bN)M`%e=>zT35PmwsdS9`0)dfEWmCfG7p)2CrmNvaffvcaQ@IKB#De6aZqSPyr|v@?)?P0F*a@B7&~ZZM`||yb z)03BLfJ8l|gTUA!lch6NFXWq=d`yy_$4#s&D_eu(d(}B zd~tTcrz|5vgcxiqz)+F@o=)XJoYl}QpS4eMkA zS!Yfh?5H?J8=>H>r2zq3PByhXaV6WdyI1QN8LP~uDN6$+Qfy-emBD7zVuUb?2&g7K z$3JboG}yC#X+VOQv9bN}Hv{7zPFFuTba(U1{fSIdIh`alm!VcFQbt)S8Nx9@!49y3 zF-)ELiFEJxU$p!=bhcr|NJGT%t*?VWhY-?Lchzk!Hdnpq5uy*+$?PVKQ5#0^!T1lsoql8dORHV<|G)pde*yS2 V{NZ{Oo^JpE002ovPDHLkV1jpD6LJ6m literal 0 HcmV?d00001 diff --git a/pics/action/22-actions-tool_elliptical_selection.png b/pics/action/22-actions-tool_elliptical_selection.png new file mode 100644 index 0000000000000000000000000000000000000000..10e8ed3c16a1dc56335459294033fa1a9808ea66 GIT binary patch literal 1316 zcmXYv2~g5`6vzMNjA5HQJJaM^ZM*GuIqfW4T*{$Bf=ZT09%-p*0cB<$V5F^Sy55?K z<`fu`VI_GN>ybx!q>@%(nu>~ffvBW_iMpHntKB!B-}m=^^E=-Ay?Lpw&JL!VcWj0r z$du|xJ^^O4F9T)-RClq!1WZQEBUBF<3?~2JG6t60Srl)Udnkj&4ve5fY&IJnbUB!L zE-;J^4~;m_(-3w*(8dtgV;+?C^>qk}6N%mtiNxk+MN+cx<;!t69JhHd?=T7jfcC$- zTdMl2Z2>ya*(h1uitXNRD?@rpExl!q7lvFfzn2X!^^Pq0M~?)?jMAcq;?v85V&##k zAEbkym>HwjvJ|no6E|*6l#AsF`4h=^CR2D*8GO}E!A#b@sVu=XM>w5VJbg!`epJ!U zE1fMUo8_0Q3M*8F)w4y_vj`04{*yW3)45{t+zW~JQO$fw&HUq<`2qPSQLTDdu~N~f zL1J)~O`56(O?9K@X|o38EPk#Lw=RlX=4x9%HoVkYlc@D=U!2AcZByq$lSGR^VHn|D z;JDE$@d$xA#T6=VDJqv9;y7t$L9a3HA8(k+*sSZo9?UF5a$#mT^9gaYh z^y>W>$&%j1?mm4_pZ;~f9s~u7w8Vp7O9%C`q5m11G=QzQ1KK|M@_&%Xj{Wkb{^6xb z<;uY5a^H|{aCCW4@rB5kTde5ZA;r?r*wXO$GGOE@cpE$L$jF32F=?*R8hb|Dm8 z2?~jDargG|4Mbq@mIR`wx1TkUOrkh~D&ee&ID#$8(u!b1LZCquD{%f05)Blv$7yjp z=+4)y6VC3?Mq?Pv#Kg?pe8C?V`e*XUcfoIRrXf!&5aUR%(py1$3At9GT!@|Ou zz*wxv$jB>KqoSgt|A@IB8ygoFcOyO_Au%y2IXNXYEj>LW^JZ3d4u`|Z&C9=io6F@D z6z~fR1%i9`?hA__J}eQHmR43)KB<4!@cczfOUJ9XQd!@7xkB+_VtRI7v!GvIURhoH z`yadXOb!HXpi#*r5A$2+0(Ki6;=#GQ;bv91lwCfk8AIJxIm6k9X&4^d7Dh5#E4$YJ zhL0(!xF4mS&XgSc&aJVQ;SnqSTij?0mb5NiP)L&oS%0( zGZA5TakWsYhtGM+H zdz*w&VUx2!R#c$Zd$`z{?lMAt^K0;r#@K8bCw?x^d#5Mb4;g%b{d=fwP?CCIK=J2O zh_oU5E9IksZsDo;8swaf$G5Dwrj$I+&ANn>SA7}&Qa!VAOP!J-+~f$OTBvU0*C-Fa z_vo*gx9)0x9u=LN6DN37`ruB&nM+>bF%IjKd+5Y7_1FlViIYerkD}1?QIRfucZmct z&iH|;+{AnK=Uy{R>9DvxKXZd|ej%1EKqG=js+b+U=GoA-w+Dan|IDX6=-xtupo7R% USrj%Y0dx{Xr8twzj|9a33z$u_*8l(j literal 0 HcmV?d00001 diff --git a/pics/action/22-actions-tool_eraser.png b/pics/action/22-actions-tool_eraser.png new file mode 100644 index 0000000000000000000000000000000000000000..e3fa0282011d9e7bac45bf87978979d4f8b57a9d GIT binary patch literal 1218 zcmV;z1U>tSP)E!QQ?Ln%kTH`z$&=IbzE6Jd|NTGB zb0}3+;b8;~|05`h(tKW-u?L^rmPM#|+ZksbE>77w+H!1ub+}g=JpJL~@%@?4<2v)7 z+6RDYnvOj6_58UOUcAw#N<+PBtnY&A{O!lc&u{I!ke6aKZ>+zs#}vjB_T;QDY7X|4 zlsg`^aD<4lD8Q-+oFbxD5hC|QJnNGsEk1yCk3c{OLU>{Yify*r^BhS}GH7>^%~XDLXz%gz zvV&h9C@wOwvzNes?mTEmr$A>^fwuzqE&=3lXnGF7tvP^HMJw5!-E&PW{M}AfOM?P5*kcsf zOcbg?aKIoJ9!17@;dF24Ij_EGBC>jzRdE1(GCLyULuq<%03^J(75MvcDr`VSNn2x^NJu=&$P`TOI^ffdz9O>!l zxhbN2v&9m>I&cU>pB1zO*!ACVv44PXlal^A@#ob3?h#iv@&cPdG1iCBzjM`ATU%Rq zsJ#5`#|jEE%w{u%Ryc7zXTjx)oo#O)Zf$KnIXO8wglijs+nC1~Mw7|3tG>Se_xASo zfXCxm?C9uNuB)p%je9#WHQODto6TJYGr6LoqPnrMakR9wv@-sVN!<^&oq3$FM~5jS gI{qN>&_1UB0Q%IMrkzBxVgLXD07*qoM6N<$g5eEMbpQYW literal 0 HcmV?d00001 diff --git a/pics/action/22-actions-tool_flood_fill.png b/pics/action/22-actions-tool_flood_fill.png new file mode 100644 index 0000000000000000000000000000000000000000..0b98f3ac27a7a9b687262e2a78982ca57011e512 GIT binary patch literal 836 zcmV-K1H1f*P)M5}RQdo-(@hH(fuI;_AB&(UT4ale7$wS0Mu8}`h=OP`!-z<5 z)h;klEEEbMh`y*%g9uthg%Jf|Iq%-v|IEDC8_cOO>foPqcW)<$zBBUX~ZQdRu24X%MU!q%Wpe=Ov3iULuj;p@}z@ z-%@s;Q`BvpVn{j2;9wz>C3!>xNly7j&PnTBBOdf)8eG*J--~OV#s$_`MTO|6q@8tP zhOA_POB2APQ2-a20FJGBin1ozV|kDc*xI1T0?BC@$%({m*3c{=yWkhVlzgY36Af;^ z3GmATmUssR6u>DOy%QFiNMuq5?vS%OhVZaKEeE_8DhRo}NCjC6C=G`gB2i}TvNt7M z#h9nR$70^8wBy2M+&4FGssbBHW?RI}CxPe4DB-1pCZPq2jB3aDMst||?#~aejpqE9 zu4!#Qfx@$j7(5E^A`dCbJgSYh1rPfbY3JK}{o}vg31*`D;9P+2Ejh+=#9DhJ7)z)RfNz-@ zx%gOW76Gdj(E_Scl_U0SE;$6}g8&-nHQY8%k6wD7tDcQhGb&o*$y|lKy}b_34;FY- zy1${UB018Bzsnt-G46u)s1{>*;xXM`ZM<06YOEKS5|bnvBI6GmnKQ(( z{=m>Nq%E}msI^$N%7zp+B)W1zL_l6tl2^IB+{=AEZ@-=^t2tJHJ=?Q$_WS*w&+~nr z-#HJal%nMqzh-N^-i2n_SU?X9elRiGaj?@>@hvVb-j^yPRH=#v){qJ+}yOYzRckJwe^LthW z#+a07sv3}%e!!&D2~Jg|o`}fUv9R#yX4t+J6&Dxzd_q#gz8A9-8HU*mYfS(@0nR+^ zaZJo}@ft=)5JtfS<6wA-sVOg)4Gi9V)9rR{t^VZV*N9?SRgtWyn(Xr6?!25m1_(V* z2xju$IdNLAGde%3Kb32rKml3gyxjwgddx(*%w@REPP2D>aH&7&yN z*{YK5l5;GBAv6|4(;ox}Z$js58-CIrs>6#wP^PI!@s>^j>{SYKWm%yzLK=cZ8k9`! zPGlRpYn%S+^W%H1mcI93tqXi73uSgqO^qiivgQG`UZ6rXD*aPB1)!=bkU=Jhq28X# zGLbA+d3JPKnRD4SspW@?G=RPfz#i}vv#H%pDAl?oNzx4t4e&Fwv*=vSnTUfqnYjbE zdwJ-AM^+G2i6pPO8LMjP>bDQJcRTw>Cp>coou=Ecj`f+I?As0d?cm7(wIDDS{0bpV zk;fy;MTKv?lzf5D%11YgU%k?I0EzcV}-Q=tSIoTUQe)|AqrP` ztp2s^jF5ixh_R62a?PjTCKJ(b!fCg6U`#D=iMwWIrqb_Ec>ZkdwvV|TR1IFQ69d-h zc^{DoB9A$qV>wB(il@qRjyfF+UD+A!8IOYhb5=yB)f$McrF2`=+rNc9`!osk*9(3= zL@;v1U@$PeUWfVWk4yLW-Rej{lopw<|53fFUs{>EutjwZmyh(GJG7{j3PzrYqJcH= z91%NAKW_!-E@=I)ogJpLtDY4%Or{=0QMY}5G~Ml*@HGl7Zx9%s6wDGN4h5Q}M**=(L(60-8;6~5p9lz|&ah>D4KTgGl|{_=~; z!wa^#5IAkc6jhk7w^uGFid`~)!zjfcV8VnWs)j%h^*2q=9HU5@wb|zUF-Da#%be-C zc5QYEXT?^;0Bj$k%3XMG1n-?3zg+|0y30EX(PP+OUE@~L)e-U100000NkvXXu0mjf D6Ms#E literal 0 HcmV?d00001 diff --git a/pics/action/22-actions-tool_line.png b/pics/action/22-actions-tool_line.png new file mode 100644 index 0000000000000000000000000000000000000000..22f5aa594bd64e41c713d7752b57fab316562409 GIT binary patch literal 395 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H3?x5i&EW)6%*9TgAsieWw;%dH0CFWuTq8aw=A z0BW%XYjJS42WfE!YH{)KcJuc0^bJT*6`uffgIGzBUob;KVNr2OX<2ziMO9UGO-*fG zeM4gtm(809kSjf19780g=ALjDI%FW?a`3yMWpl%Cd4cFBKBDv9cEpthcxf)WQ@T5K z{jIaE`dh2tw{AODwJvwVyiDV3!EYH~cYf>m8c}_yYKd*K?UlFOQ+><& zTI^fq*IvJm?DBG{OT2s2p)OfAI>WBw{epkLR{htpzb39;l^fr~2Xq62r>mdKI;Vst E0Fm*DX#fBK literal 0 HcmV?d00001 diff --git a/pics/action/22-actions-tool_pen.png b/pics/action/22-actions-tool_pen.png new file mode 100644 index 0000000000000000000000000000000000000000..fcedd23f1600f922f7fa04538a2099300b5389b6 GIT binary patch literal 827 zcmV-B1H}A^P)w~o5YY_(4OIRRC8X2ht`Qk%CU|1s3LJ--LkVplI zL}FP%WR8~0G_F@uQPE~bof&3EXZd$-j1)M`rTc^L>D==>-~FG%5fQ{NKF;e2OD@z| zW+r=#ov+J_Ud|aI1``L($0Fo=96M;;@`Re7JtF?ob8qv7gJmN7#KqIE_8Z6tqQxwt zrL#oCmDG3t=$24f}A;x+PEf687IQB^9T~(9;SdC(0 z0JHO0apcli-^^{vGuD?tYo9#qAy+ifxRQw9OVkE>SDoD{tVZ;WQpi%?XQ3zNtc&&BDayFMZjzVC<5RIF#9XT?ppJ-Nfr-X=CY%*R#RZx^XGYC zJp<%bpu2;=Q~jMtvN-nWz6T2VC}36sObC7g7H^h!e#YDgh9vRmRW2qvM}8cxZNTy% zTg?GkEg0xsc~PY&I|9IQoSb6>6^HKLbxRd8$ib2Xcmd3Vh+S#N1crB_e14=soLL8!y?CKgpnvViDi+=X`HqcSYqYao5koLX07q2OP`_l zH`M(7UK|BBP-(S@ECw=DfyHlFOIt!$Q+thH7z|=r7eYkd&6g(+4TnHnKJ0OYXzv)V zFL~>15fQprtW4+>qyZcA7GHd<%?_Evgbxi>b!VRbs6}8r{)TR1h>pUhOx#*pQG?Tw z6ZPksMrwoo?tjfVIWGL?`SgW`Q*|RZ|B?AvyqnvJ^BZXj>PW4^xbXl0002ovPDHLk FV1i<>dny0` literal 0 HcmV?d00001 diff --git a/pics/action/22-actions-tool_polygon.png b/pics/action/22-actions-tool_polygon.png new file mode 100644 index 0000000000000000000000000000000000000000..9bf2d40bf9f6bb6840330d0ab5aa239e3e9f13f1 GIT binary patch literal 1303 zcmV+y1?c*TP)Q;(&=k;iWOU*Ri{te>`s{=|Ps4f@^ud1tBWN4~UH^{1 zpc{KR+B38=Tp(zj42?{%KnAbi*%pmXbisNCbo6z=2pS6dDWpi)ovw`)mVOOLIuNoDJmDwov_ZB~`2#iFckwzdmpW<|N!+)^T1lwV0} z)cFO*8EClkCdwGp3-WbrKtYwWcOs^`b0KxG3e88O5o*DQVdG!U4j?*9zbj2aI6<5*d?Z zMo}J=dX3!NQ6)3CRY;av1$Cz8qI=Jp^6%_GCD|25$q%LT;w*u&A){!n-ZwB|8lr!U zO2K8jd9R}eLFyVvy+^9FcGtIAyF|^F&bv+Kc6*d1qGxi5@;(os`NGAxCYHpF+zdB9-!@V(b&*oQKseVRO$SY4t}*s#;XBA8F*C-vh?D)y6Du$ z6+Y)D_+Doxcr=Cw3A|efd=wn9c_l?KWp5CQvQoOFC~GjapeV0%Rch$P7K81%&J~O+ zaw?6I_)LQ^ApTL2Ps{}W)WzS50^_GjPA5JrMP3KRB5Obcnc9HhSkvApQG;#?4RRNc zQ=}8lngTs%aDokPkq-;}VkZj`EHr6G5OHm$3`HHD&?!OLgWT;t{+7;k-KcC_+Og5? zT9dr+9@G-j4T?yagpB@#J)b$!7$=aWT>WnJhbK>$M%|bxzmWd4G9l|})i*b1?}lBd z?AZl>Xa^JMhqT;ojon7j^~SLH#+IGb=*$$m@o5ZqJ{+_a94l_V#GS3Wnro;@<PMkbzM1i0NqZ0a@47bpkoQ}Oi=rYBL*m&1bpYe%vaWw^9Puwl}py`FPJcCjB zViI}d=td&Xqx%5vxw#ko1=@HF&lOSVZX^|VA49b|Y&1G^{A*#HZsiFyL*h15@9wNP zw-;+;F5}#}0wo|ML#h)q9rX>AzMY)cm~b2=;!1zG3Gf?t1Zc-xL^w zbDU1F|8Vp2+t>xazaN+d%6ZfGo5zq N002ovPDHLkV1j(QeVzaS literal 0 HcmV?d00001 diff --git a/pics/action/22-actions-tool_polyline.png b/pics/action/22-actions-tool_polyline.png new file mode 100644 index 0000000000000000000000000000000000000000..e5ef4efb47b24a0cb671cc8ae120445b14ffd47d GIT binary patch literal 772 zcmV+f1N;1mP)T~# z3cC5zn4~ekZQ6)~ii=KNT+&4l&3it-BsV1Fg(O3J!g=T2d+zz(x#u2kgl*f}YKYNl z!Tv$-2Fy5$h#$FZCfW{Iwz1|qI;{yYErd2R=sVE4Qs7PvqEI`ebaEw1fe1@M;fvo= z>XwF(`DP}<_`0zL?D4r&VzDjoKF^RLr5shzqPZ}iPW)Bwt?Xl0X}aw5po?vl0_Y{o z_99ft&#D)!g7S05w&;c+&;mA50w6WqFFa?MT`F>=5bN6l!dZJHJ#Ag zs(r3}T$s1Y9z|}FHMPD4yx2*T4zRB47XkDU{(d2odR;xpGPYSYlCkI;{GZs>9u9F_ zRu9bAO=OT*li}sM9;qH)b9pXVqLZ~z%}U;2ZX<8^vYF)9y4EW*8(gsm1BZLr{#mKZ zQ@YgqRRX<+$LrrgXg}i#W|k?|wFW)iRji*aw7m@9aTaoo+kF(^Io&Wu@pni7$Y(C8 zr)2O@&;`Gka;qEc$$UC_x)$m?6!6nVsY;=FMgZ+bH=#_3JfN2(bUzjeaO(9HyRmZ{t2l`J5sFX54qqQazv*D#y_wfi3 zJJtA=qxloDSq|tVupWj5m#gDZH1yWgTF9#Xnf?d$v#L7%uOf#400006YCy=gj9fhc;;%qEAfn?aX`+zxnT( zlU<33a65*ix;=31{JTG+c@83L{dLmXwX-fZt!1d60l*M|DFpVT@~a!|eCKvRr~v>) z6#(Z=A+WzKjJDpSm;%&2k+N2tIrDSYBwo%K@0m7Zj^6iv{imOAzclriBZsRa9RWrN zRpV>?*Vnx5Y<;SDLPzeNNLddd57e7CX%MIR}hL;;oX#}M;27`0OmMvS4N3@SL1Uf<$M)6wRv*7;iq1lJ1 zf)ImE@2q*Oc30D819#L+m;eZ9!WbxsK}{ecp)srs7!hEwC`Mx}2qRD`L@FI9h3cQ) zY&DrP4LtXy6{|Pt2x>;8jM3pKX-z1^X@{pIFvhQ53;X)>0|kScT7TfXeHBmD*B6H1 zTcwOq0!OB#eOL@I=*vd~pX}_;AmUTMuUH>gqEw`u{0oRBIh61xWj-^Rl{f z_~`X)X(^NWlwCIA5+c&P4Z%Z{c)u^wryiviv^*_J=w@ZQCC z(_1jxcPmR|qBJd>RMJZLo^%Lw4Vth-k`jBA*pr+)LV!(aInbD0JO9s?9kA~vOk+5j z@;M(pO2~AITfWC^b4dxCB?wDcoVijm*S5JY7$ymKiF7#Q#?thow2_?8Zf6;ev1d(n#)Ej@JSU6YI3wl+S3h!*uB>HB)a zhK=oe_Iw?l&zzkWU*4(XNA{UVq?fY{m6 zyf={c=|nJ4!vNA`Sio5D;_{U%ITY#$NKtRUH2R4XCkAMG4sJ32qyP9VdcRXO-q!!T Ze*uL(04rsqT#o<%002ovPDHLkV1m)PNQeLc literal 0 HcmV?d00001 diff --git a/pics/action/22-actions-tool_rectangle.png b/pics/action/22-actions-tool_rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..8f94c8bcd581e2e2acd4a2380e96179c28429f92 GIT binary patch literal 795 zcmV+$1LXXPP)?3AiiIP}2CyF2=J{M%2vYgszy#6^S>7ZmdT%EM>Ei8V!`J*`hwxUwsi zQ%NbfKhGQaU}e=U-?(#d@9u41Ybkji9o0}Eawq7SjfFVVi?q^UrgipO!H;MfkDnS$i*I6p3ikwxD1(c1X8Cd2OU_^p~vxQ3(GneQH0~RIC3dl>B zj$FDqm%K>fnssRICjAo31=hMVU4m1T6Veq6o^qMz4wDHSf8Ct)CX;-*1T!$FBCrew z0*fRA212KA$E|ic+=oT{(cJ{Mmr!^hMJv*MJ7RAY^ zz+jokd_$3K>O8N1^tDKzgB3+Vg}O)s27R9&c=p zQ0YdbO+A#RR#RRr@U$^P8&B$PGzZ>^$AjbUV$gbBd6(+iZ$zG9cn}Cv3lzCl+9;`` znrBLId+#v0dUG-8<%SNH8pbR|%8x>2Xgf^kE8`midxExR4m{|6xRp-gYf_#KRZ2=@ zv?c&GO%UEwrDjcQ#UW&~xh)E?6Y{ypx@$)VJ$?RZ6}_V~0~(>ys)0Xgq@a_1@&Env Zegk~5o=R9~)QA88002ovPDHLkV1h5GVR--m literal 0 HcmV?d00001 diff --git a/pics/action/22-actions-tool_rounded_rectangle.png b/pics/action/22-actions-tool_rounded_rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..758e6175d7bc14291087972c4bef992aa24043f4 GIT binary patch literal 1096 zcmV-O1h@N%P)7mQFC0Y> zbfBVBrfj3=L>Phyy0IZEtEc zd!CoW!AdD~GmM~{WNgXUdjI%m>rS2cWLu+A>ka&c4N@r!H4|!OwS0oHZ<_X%jzsGF zM;?7_r!ay1E;_Ea_8{yksizVD}fZhk1c-x7SpY9$UdTmvI|J~uKm-p>^?v+h_ zcZK_39H z*wkck)y9pRe8)*ddSi=HB2dMe2Y`tthE$pdE~C`t(g!uJi^Z`VnK5J8PVW7BC#%=1 zUD=*3^N~=^^dM;vhWaTWYt<5nAjX?f2_1n$T2NGKe6Cpb-9pKqgAyt;wt9BK%WY<` z%ZSI45eV=w<8Q;gQxrVkxET@#|Mhe(g%$uQxm%{_C5^g;3CdTS=glLCE=N zE+~zTd%pvl`cGGy5E{a5)6`sxmZ!sOUw_vqx(yMuD`HxN+7=-O#bmw`BoM+rgL_^q zPF&4|p%RyJ3*~bcy-GV`7$Y2u5gM~8vn2zm4pTFFF_F`#z zEXUdswQWK>V$_J+*l--e9E(xIK$M3KyHU$`QI`0YJo(hq$ATbKpPwAOqaD$BoUn9~ zTInRSGAYwaC7GQ{5+{>l&iSw2*sP==na=(p^7z&VM_7FQA>aG?_Tr_3Tf)T|=SHF{ zI;fRQ80LxuwGwfng)Jg53Aeo;{)WI|Vu}>-9ixje3LHxJFxV40_Pnr3H$?cQ+w<9CKy-{}AM=KTRg+SQa*V(pj! O0000~y#2hV2x?rVGkyBt(R7eo|sB}R^^F_pjE)SwY zLSZkJ`P4($qeuiol7S!R+GqcZiU)H{?9*#3*h&q4x!+Q9y4?kF}wKSy^ zcdH8*F6z_MlRrr*A9b9T!vg~^9OpjDyw^afRN80T)&|#|qqD1f-L?s zx7vXHfH+p!*4F&zi-RMryHv7|m&=|pIXOoX16T`aE}sJwZEZ`|9X6;gFPJ_yEB~d3L#lSKte(MHENp-T9&}FEG$c)X&|M<_XDcc8lLB)GmD*T z(I4r{5A59eQOE7@&)SoOtU@->`P8eIegB~lVyuBTdOo%5R`9(+3e81AV-+-YcK7u? zaPnXH2d$Nm{{MpWy(0JHM3U&|48u_K$u&)fNB3Yx?F4EC8N3Pu<32Y|o&1iZC$1#ax&uk=~;yHY} z34s7(A+3*jYpr(g-d+h+{`3=VeT zM&nf^c`{rhq8C98n;8_#$uN#U0o<(SJ{U4LyuQ>LT^o2IqtzBBD>jak#c_OUwR-Bh z;ctb4u|>yc*R50zHP#T)ilR)FXGkd`NeCM7fdf0@;%xTtgt5L{G#?|-+Y!PMgnSf?$v=foepYg$iZ+%-Tg%^b|Bg+y9 zVb(k^9xN7XC&$K0oA>YEUT?g|l35JK2-%3iI5Dfx@kr@IFoDG+KDRyOGc)yWg)ZAMd{u!)3^QOu&zfld~|-+8QhdND*i}1^j`*H4J{m;xao`x)}H$;D8A@&_f}J P00000NkvXXu0mjf08%BE literal 0 HcmV?d00001 diff --git a/pics/action/22-actions-tool_text.png b/pics/action/22-actions-tool_text.png new file mode 100644 index 0000000000000000000000000000000000000000..98602e3630c2795d7626e8cd7369a3d37db27b20 GIT binary patch literal 728 zcmV;}0w?{6P)tzv#bre*9;AqiU6ta&KcRZ^;6=oXA2*Lh zMA1vJ!cx1Xwe_RwNqP{`tKhBRD$MJX$(U~QQ5Kdac`_fF_cw2zOcG*du@jr!OYqU- zxm_r7k?z0HvfX21pP|DE{ZA6Xx#2>CnNPu_HA(W$>hj{Bq;W*_1>>S%1+B|C)SE)Y zBLtm;ZYfsx($f9#c4Zue1I?JOr%-n7-Rcb)@r+2)GzIAh(gj^Qk$3yk;DEF-Mwup~ zt?zCNIO90u7<3R49dHY^YcXQnmarZJ1Ji&|(V7e4MCWlgvI{uHzu5hn;F1oFgRKc>7Gzc(Cc4Q2I~)1ItO@xccVqzme~mf8y4bX$RH>dM zq61KH#mLxVButcc@p2bV2-~vcMX@>21~Y#Qn>twy?nh%k2Z#xrrlaeZjU^!%Hk5sW zQaH1E{<`_99?hsw9J`9xhk=W70Be5m&Emp(2O2wX@mP{1i%7y}J?Z_#hc4+cLN`KN zSXkjl%k>=e*~Uf-U&f4%DOYP}0d6Rz=9_b~cSXupD2<)L@GH!!wX`sQ+q|tWQB+08 zLpJr9o_c490000< KMNUMnLSTYCL00+z literal 0 HcmV?d00001 diff --git a/pics/action/32-actions-tool_brush.png b/pics/action/32-actions-tool_brush.png new file mode 100644 index 0000000000000000000000000000000000000000..1f60a55847eb89344f30610b1870ff440769edef GIT binary patch literal 1557 zcmV+w2I~2VP)Vy?T$XbkBLt^StN0I!Z*S7P@_^y|qr;IAJ;`-oLo%#UB>v+5F9 zlw5Cb-x4lmfM}hzPLDLBB!f4891L)KNCJ7vfU6R(dwTY+fB4};M>;!OV<%3GyxY^W z`-eYY)7Gd-7T@%9Xxit92l9C)0gEzF)=jv1^RAYjo-K#BZ|~XI)D+PHL||fK(oQ|S zJ9pE%(SY&+UXTLThi5pj>OnbD6$_M7T5oUP$^HAcb=B2{^la9o6uF#9ef=`MDHaJR z1Nb7WBx47k<}UzMA<*5ur#=>I2o#CnI1b5VigbDo%gSS#d0cw_%f&^(S!dhwuKouG zqtU2e74d)9Kr~vvIUKGvlF4*&;Vh+{+qO-KjK||47~2D*=V@w8!N zcuLll6mn9jOni2B_D`oyz4qYH&``W$1ze{d551K;mtEGpR#vpE@O!=PFA0?L+?Nbe<2bgqpav}1;-aOcu}CV*)KmhW&z%??yrn@z%!(A) z&_M6TZxO{0OMOtg4C?AG(waWx|1|z*-ag+ySJQTb=IDmoG%cM=V44LIi43LUjYOiK z0{M!Lp(>*7Yiy6LLdOrKPytvLf#w*jZA-E9+9&+qh~MdD{6#ae)MM7v1oHFV8^q%m zLF__={nvhJv%tbHnr*F$HU45HwIR?6& zV97@h=&v6g8TrL2ZV-`VMfX7eH~C5L0Bw#JbREP7B@20xfZ>I35b7d1qUpoF_=RVg zHCNKSWCU{wq*A;JWGcFdT7xR^(CzF9tw3{WfB^P4D;ky|oEA9o; z7kFj(N#2_rV-6pbY{0*m#Rn>|I9|+)G5SxkpG@+&h}c)uj+IjSkwJz+^>j3L(zvRR zh^~P~Y1fO2C;^SH*2dA_aAxNp*dQYLD{4k1=0y^F0R5Recy#ImZtUDiQ^RW9$^n2v z77X{LHp~+lf1C8^7>|?|ugc2^P{+Q{E6)$`*#|E$Rr*}8kX#yZa)_Nlq!@M*Da<0r ze!vO9UC!Jt%77Hn7gOJTbsS!b@SNdgiziHQ`8r(r{Cv^L7boOje`RLm@BD0G_f>xz z{`7hRLj&CY@RCwCO zR|#w!)fxWZyf-sD-pj}OSlc-GwsZIhP6#Q4TnLmvl?svuwWX=rN)%dE&=Y7Wt;AhX z%2g4Sk`{%~5GBw6Q4S|2^Q-8yj~w`@9Z&m|CxBDC~91(rRpbte!Fix z{{H{_znR%3mSw^J6CC^xKnM|@xTom0S5}ukjITwQ4(Py_Mg&3n{_*3cts{rGs*`8i zjFzWYUs^pse=hFRe|656DK4y@U$C(4EJSTu!s(0$)s_pZAa|G&yN56 zFNb^nhPsM#epA!DTX9{2G?1N+h(ek?A-?{SZo}j>yNmYTwgbD#m1R$}Q#Yj9ZGbbujiK(k^>ibnAywSa7Y{jZo54kHP z3lfuq(0{5MfW708w~$Om*GpncZcIJX!t0sdt;KRh_l$Y2VLQ5P;L z69uQlRTOuMNML|Mp>FZ=7u~lkUweOk)irEf&zfG8*kUJ*w@TikxF>83h795Gw=?kse% z)XJ}|x~GsZo}N)SVgGI47$2#fH<2aU)dO(oU6l?{mjyrtM*|Xr1lY2*_{K1ywRsi> z2vF7`Psx?Wk+so;SWYwl7lo*^|LW*^?3k+wWdnHS)r+TdhJac%pwcWkE}!p)2cx4yMuQ)g~& zfsEq1t{dv-pC5I_CkO7XuBrjQ-^Y)RL>Q$MIF55jh-2ZE#?V?DY-Au-qDM{s^`Cg9 z(P0kk?KE|OP(^{#oh3$IIFfkiKv(2A?q0S>uv*tu=seNUvt;RApXT@P$H z|7+YGK*lTtgDK!;raR&W2GF#0Lo=O@1wpVo<_8D*bF#8?;l{w8IVjQK#om%E~<)ETwF5g^?D?ZQdLz{b8K|Pht2?23`|Km)+oTiu9{NgcuJz{ z-yeS*v$h9iCM*QlxV8Wk#Q>rM4nfs)9D&Cvz_u4WUN0EB4irZ)Ix_6z8BIu%OVl)l zF$|_@n$9?=lAy>brPR5wQTg4DE3^WQ>7nnFPDWoG|A-UL8&_-;9aeZhz&4uIfjgk%^RYxm@;f z9j*h0TwYPLVA0&07M7v7crnpJgkW@Tr%-Gnj<7*ePEJ$srHCpx=NXXRGxZM?Z5 zQB*v`;5aIB2$&A(3`|5vIm7g8cKEOmd)2WJt+g|G=4Y=!PgUJIsV~~~-oTCvt$h~< zAFQn|t68>u83~8O<_8~ak4DDSPiEFH?ysz>RWdVu_I2te03;*Bt;QzD0bfSpIKl_KH~I06(9r%!ieZrid^Ydf;<(Nm{R$=7q>cFQv1 zc?IQROuzc>N2BepZVX2NQGSES=lB`R_EVuC5aj&TK{+K4o{5ePyi{<5Td1t6F|)J% zJWq+pQNmLW1m`{lK=AX0R$4x1_v}iyJlzN!=!U7=;_av8?zFg#`~Z*QctvJVUUG4~ z8`u0mnN?F$Axk3X76p>QGa@lcT+}WC3&0srAV4sDCLR+3@$U=J4E=IM4gd6oOFij5 zubblnnLO`xUlB(!Uf>-HNYVc1j5>5NECLIVumB$0X8?q3+xqw5#&_OcB+HW_413$O zUrZ87HTA^HUv#P()}8J?ho>k?P@JNmO@^Wjg($P_rhp^@QbeK);KF@3(!&^dTlVfM zL-8U2STmgiGSYMGTaGn+#z4gjJ|e7k`{!UI4|aoo!fobibzxoa;`uF!c9+COcFchAt(M3y88 zF1#B{E|=h%H-AAYGt(oWxC=+%I(zoCCzVR^Fdfq0QV0P8rLB3jna{6t)Bfk+8t|e! z^zbjhQ~$fk&>J0q0v% z@6BO}47K6wI9vl=O?JdD2e!-M%uVfwn>XNA46eaQB=Tt_Oz$2ZqD%Mh-~T7vN@ppF z+&!%WS7YA1`RtmRSy;XDAN-BmHV>I}j$H@;1jV;AKS$Z7O_=hfWW{O5p$z)3mA{KcV6U~UzOgpYuKE_BNLR zaBHzdwumLL@0pg}VQMx;)yz~ZgUTBKb2pwu^>nswHc}c>V7O_(*2=A5sSKQwnYlP7 zbsJ8pVV}Fm~%IFo$t(pr~#=uf&h**~CF8p)Z*RmI-wo+IgS=1?n=Sgn`fakDk07k^Y}2glf5N;05!YhaYzKe^9xaED@0gBI7H_~ z@HpuKu7HE&f)vGr#b|lZg8d!);iLBf+ryr6(JaIz?*XP_lag#tcpvnF@4*=m7kI+s zj0ZeRro->_LTIjNaQpFX2eBZu(t3FW8`zP>JAR>MUgZBY1 z_$?tHMF>@ne=@V$ZuNK8yzsnu#9*3{I(X1jsz zZZq;L3o-M!4^)*(7&;A*rAct;j|`;$mIi6O1l*S_mhTlj0Hyv&qtX0TU47Y(n@nc3 zwOvO`ixIWWzo31n4JVt6;Z0`Dm;oI4}e7~RpXyq$-oKHSDIE1dQp4ae!fk6}$79rr%fJfpl z#1)i!GvU+uenA7!%9pFv>Iq8j#Er%d!dvb5@bECK)&Ue06k+}P^<&}T;Y(lb7c>CD z%Ty}WIN_AghR)9J|8V2~@VvYNJ02Dm=I`hgJOIJ|@$vCv)zvTY*4B13H#?HwgT-Qn zUZ0Q1$jEUA?veorU#e6p$0#`^9DF0*-*2(w5fKsN>(;GX=I9kX0Ht0+_yY%i{kn-( z-sZ&b-J8NsoX~^rJsuhw>cE8$K=9(YxHulC^}Y!oL0?}#a&z^dm53A z#>U>KpBmu<=;-Jo+~|DTM@B}_+uH}7E|kt z`DAc#uuE~im_nhD$Hc_o>eb)v>uzYchWh%eFqwMncxL7?!sU~zSFa9m%QwWfZQJTf zN>1@F89QEA_bd1}pTY1m4jf41lUoU2?zS(eEnBwSFDg38qjvnQp`h*^~Lb~??H^RVIT=l}o! literal 0 HcmV?d00001 diff --git a/pics/action/32-actions-tool_curve.png b/pics/action/32-actions-tool_curve.png new file mode 100644 index 0000000000000000000000000000000000000000..ed69479eb0090c376b1f3b5b7fc7e37b97bc510b GIT binary patch literal 907 zcmV;619bd}P)?*+Rna1B%# z^7m{{4a6f3BO^nx2~<0nH8mm3Nq79t` zl>G>*#LV?j`eY`j_(0!6jmI~LW0)X{&|aorWFzwiL;Fw%l?b)799B$M)wu64d3Twe zn~Ja%S>?4d4`^3b@<3?2RywY=eu#d626-{f+F4L&UM1CNVzD%b03txjC#cG1jM+8^ zP{LupLRzWa%bQS)a+bx##bF$#RLZj=s{>Zc$>muH^}5N8m_CW)C}WS?7j$OfUqLRE z8jPore*-^3R>5ruAWHLcUnbl6xw%g?T@SQNhJ1m~Hza@naHXKCS*+x+na;5gF_V(O zlS9SM8FP_nv^R1op95z`PFb*Su?T=07sLy_E98i{$@X$_vrWKq%bY z(A1nSYjxp9XNlAKi1P0>{hyebp1PNIC)~J@`5DN&l{uM^8Pr#T+7AlMF+ZD3E>*pX zJo{jK03w0 zNH9@C2=L$!5QszxK@%cUs0h+hY>@(O>C$bxZMWU-?##V&$M5W&Wy=a!`@(}xdV21; zcV@otJLlZpyQj(wc4cMSm@O31b>q6$-QA_%yxFaX!#$ovVmy66NZ6{X?GnP?3L<@zA^_lN zP_PjXzY7H(UDuV>t5%D3>(*PPC1o6dHW{}a+qNA6qnNv6fqC8=?$r(**i(G=%ugXl z2$eDd5uX8TJBY;ZNdVx*9*_4VW7eY-H$K}aUVrTktD>@sfB~S`5)dYV0Vv0ujLY@R zTvyjM&*6h#l(%0#S0qbgvxW)`^^@kpP}vy3 zYZX=7J23;K{!NT`hC(4{&+dKW1%*XWDb| z0^;d;hFlIs`}G5R-)>}9A`f_PAq$_Y3!hx|g3 zZn3*}wyd*|nE@)3IdUFG z3FHHmGShM_dy<_^+c6xm#%_eo(b2fg?gpf)ilXbPs;L+vpuhCIt=-=Klz~q8dVG8; z)&3$<%H4WH{hgf+0z94&R(IHpb>A|_Kv?908iuMGx~c&f9+FA^r}W=TS8enW4c{-7plD z2pCu+a6?lxVxWOgrUZT6r)$X*HW4_2TcgpzTg9h;{-LnGVMQOerE=`s_@yg}Xd)$e zZf*0tn(hU#*Q3EyU6LU6@NNkRl*%F1%S^Pl{!pGAj~3u9QDoqVhL1z$IARmM_}I}e z=L`(=Yl)<=E_Wsdnp@%#0?(~+z^@zmehsF3@&lSDKw}>~Y?_~9ImSI+zdKKJf<^N$ zH0@vLrhgM*Py9Cg9&Tl@aOP-D!}~`Qe8xy_gOOrio~A=ROig>LxrZ`9-q5H7TBZqg zje0<;kB|1}eY^j|MX}+&AaZl;x8|mmxrfqM@JPp4EK<;VYU}gy==suIaH!G!AOLTW z`9Q!2k2#7xFgcbUv9w6IrTCk@?>$Z6^mUAkufOMk1e{JNigro<|GC=AFBG>bOJhrRm+MgFnat|c;J(6Mi0}S6moqQOJ!ji{3 zY8I?*Eh>E|g%W^>YPd}ZlV0usNn&KEH~-{`eY4wIPL)Beiju+7#j`;M~3_I$t*KzxUaX-1-l&gP&(yf zIofC&j{j>=8ox4$3yM?tcNoAQP3BlWr5`4EZay;dn2Nx*t=6gjF!}$-=>sS57dxJT UJgZ;Z#sB~S07*qoM6N<$g4-A{ssI20 literal 0 HcmV?d00001 diff --git a/pics/action/32-actions-tool_elliptical_selection.png b/pics/action/32-actions-tool_elliptical_selection.png new file mode 100644 index 0000000000000000000000000000000000000000..dfc8eef889f3c156e2a1f31ba64ec49504f9610e GIT binary patch literal 1855 zcmV-F2f+A=P)RCwC$ zmw8N+XB@}t%uE({I9=xU$F_gAnavEbEiDv~V?%MCfCnCcVC9&@gG0q*I2}$Mf~bg8 zKtzgKytLmEVb zf7G|Af3Ip#2619iqn+1pESzI)BePrXRKypm39bH4A((WOiw0rn` zhfEVMkqw_E7V|7M5f5#;rw*xe}QsjtHM2)Y1P&O@xDp z@IE3OSYEH%T~@2wSz4{ycDqvLS6r^#a;vn@JHKS$r<9x)SE4ga>=7|b=<3AZA=ZkV zol8i$LBwB3Eoy&C-5^pQ9*erDT)FvXsq$wc?sdJm&plJn!)CJPNVWBGS>J&t#g=_t zm^LQ0NPTcD>VmTVO`zrr`#d3@TR6bZDd=@`bn@mwM;nRh5T8snV#;xIt;>d1P@~XB zqejTbq9)>-Zkz_}!_x@AJ14gInQ5RzbfTH&yO%I7%}2 z;Ax3XNNm^K(6f?x9=@mDX-rcY(T$_JT467n zmmyykme6Gz7S~}Lp3u4A^yThFF-g)TXOg8$V^gGd=hEbMvB{nGk&z>BQ>vOrJ|8Fb zE`4h9?RTegU>gzk7nBZo#9xuygA|03JlTS%#O}oq;-e{Bk1EWxrFe`l?QxhjL1PYg9=R4*HoofGHCk#r6^0H(r zA(on_aKitx@_SvgZ}zSy-2h|5l5*OoQAi`cRarl9U_8_aG5Q(;0byc`U7;e&;25b5 z!mJYp>6Ki?T8KgnEl9e+3mt)iM0=h|5bQIznk%6i8FMKp1Y=VR#f3yZXEbUQmf+5S zM`(;>p7XY5hWGwf*014hR-oowk}X82L5#3w3VPRp4x(@t-GJxu9)RGTOB~jeYr8-K z2P$yku)?|WMbz**g4^+<4ZGgZ(Z7kVFOK4hzbdy+%(fe~*59hM9An0YdL{@SYi}9R-J+quA1KD4apA6Gyl~UDG(Y zAKe6DbQszkLoPb@Ql?hC6$_xg1D4vn4AT~;&4Rd&e&NO%s{5Ws6I48+f`M{@j8(K3 za*nV=9H~PMA>5z_F$NnJG_tnDU}+b3sKXo_U6*3%f6;z`MT}0d5BcBOhScs-jjekg_ zGR;J0*7iIX&ydX=-aqKs=xxjfv@CLtu$NUzPaFx0y5_iMU8}W??LhdcbD6a*>Ur4a zz4WGZHjPoHzri#zF_$g0b4m-1%=W|bs-s8VgOA2uBo$syXPP|Fe+C=dSKbw4OO35H zaYTTvw(g}2mbomiNbasHypR)d(fXoffm71RRvugW*LE<$VFQN_?crlL?qw9q-_I#g td|+U}`WS~599nRA!6D|&+37EAyD002ovPDHLkV1k)Jf^7f* literal 0 HcmV?d00001 diff --git a/pics/action/32-actions-tool_eraser.png b/pics/action/32-actions-tool_eraser.png new file mode 100644 index 0000000000000000000000000000000000000000..dab639103c81416c1cafe795961be58d0e2c1cb7 GIT binary patch literal 1939 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 literal 0 HcmV?d00001 diff --git a/pics/action/32-actions-tool_free_form_selection.png b/pics/action/32-actions-tool_free_form_selection.png new file mode 100644 index 0000000000000000000000000000000000000000..ce0f518bbeed660f9995e86f34ef2ff801b7e8f7 GIT binary patch literal 2085 zcmV+=2-^3FP)c# zOs9ZY3k;}ODwT>@8LC+6RHfx16ub%C0LI8G0g@2%PDpZZa_`w|_XjsXt&R|w>7VYI z{cE57t?&EJTIZ|<{Ervw%q3rDb{>F%fdc!n1ta_!F*Bd{vq~ug4fCK;;08U-HSaIw|@pvZ;SOGvPWkrS!yVF|yBaGBO6$*49?j>C<2I1#m}?)U+*^E><*N zcQ30)YMQ@CYTBS4scCyIU91Rq)`8Uk5EJ7GJ~ID_qnZ%WWFlHqWa7k`CphOinpYzM zASWlsHGcf`B;Q?QrU6I;@ECw2zRYajjS%499X>o|$cPb1Q5CrWMgc(H52k+RcE`3E zVTJ|Mj5<_Pw`2>qu-g>;_p}8tUT& zTeBaXKY245QeQ1h&N&}5dhF!9yeXvz5ANIEf54Dx#ze#rcQlbU(cs2#{n_E&IxZpU zkO6!E;PJ#?d~o(pH+sA&&EbgSjJnsB&1-qDe^;dhU&AWd3R zR3(AP7>7E7e#LS}lhzuVh&f%mrZ)HJnSONKeX~CvF=|37nBhc*amiRyQ>5ioX|U!` zuPs^>4%JQp0B-m{D=RB@N&rOw#+)lHP1G6@B9JlYQ158EX8RS@`btnkj@CwNO~jZa zfSAuWvIgLW8Sr+;MCzK_>d#k18bhJ5-u>QFPqznaCPn`MyW_m>#Q-1y6apAiSy`S) zM2;qC^WUmYdSb1MS{{)jI#d&KOyPc9fR3 zAP2ytPBkRHtgO*}`wmY{NdH+$W3ZLF#Y?MuH+B(p4wSxO0| zB;?8BBi+}%{PU-Yv;|;ipuj)%YaRGD0)hPe{Ul`?FPV8%$e8o}r@yi+E|x2gM_Dm$ zZn+h+U5eS3#VkuPODU#c9Bb>FFVBt!Z~floggd*!Ctd$hp6;kLA74CX}uh_na4v;UJnWZ8l#%VNq>ObJewlCTwL zc6oV83K2;FO`5X>0L<;`39lw>P1V_cQZOYYmshq>k_%PeP#c$hckWIwlf#Yv-f_Rb z^xHt?jfWQtVF)oX@n_~d{M;_3m=fG5DYy*e@9VYm{Vn6mE|jKoE;a@7^B3MI>PL4(@P>jE>yq!Njm-FO~H3SdD{i zt1UjG-@tH;#oceb_UxR~Cku10VlY%2u-`<2Ew2_86@_n!fG;yU3BXK13>DJ$8H%J6 zMUnxq0fX;ZesAs%_ib9g^dC`#1_QR5+C%G(A3xrDvqQ!=5hw&O>}!q)z|25_e}^wK zdmaP2kQ3fqSXkKD+1c=$a^MJno&e4PIC1h5 zGfNY5OH&IgQwwV|OKWp08*^)03u`+|8+$8T2P-=V8+#`k2WMMH7kejH2NyR-R}UvQ zPiJ>87Y}bYFCVahP@{n6n3x02u>zW7hH8$rog>g3TSsR*Cl`BXH%AwDCpQnEA+DZ2 zZeG3~KK`D5fk2}mP9n}6SCBcbKyy4@JW$M;qq%Gb(AQ=qL4Lsu(J`@c@d*iuiAl-H zDJiL`X=&-{8JU?`Sy|aRe~`dmWDvJ%(nFvLTRmMILn?07o^cmyb`Wq5Y&1M)X?WxN zeo3BlmhDBnN!nGv=UX|PoOz=nRl!IwfGKTVx|Gl7bq8B)0~5~7b7~OW-OAwnjfaWn z#&m%N=l3-+*nTr*d9f*9!J)mDoAK5gYmN&^_njNmels$e+~^lraIUV2!StIk3&%}; z0fnB=b*&7c@A3r{9{l5D?E2ThF!j&=S|Sx izfQ~t)a+mUX1;$Sd|A0&_idmD7(8A5T-G@yGywovzS8Fa literal 0 HcmV?d00001 diff --git a/pics/action/32-actions-tool_pen.png b/pics/action/32-actions-tool_pen.png new file mode 100644 index 0000000000000000000000000000000000000000..ba35f90067aeafe9ef269b91db5475a5ec6de747 GIT binary patch literal 1192 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 QDgXcg07*qoM6N<$g4kDiwg3PC literal 0 HcmV?d00001 diff --git a/pics/action/32-actions-tool_polyline.png b/pics/action/32-actions-tool_polyline.png new file mode 100644 index 0000000000000000000000000000000000000000..dd4ec9be5f25af962da7fb53a9eac4994d4b624a GIT binary patch literal 1061 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 QTL1t607*qoM6N<$g8mG$Y5)KL literal 0 HcmV?d00001 diff --git a/pics/action/32-actions-tool_rounded_rectangle.png b/pics/action/32-actions-tool_rounded_rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..2937e29702ceb84ea82362822b8c9e853b417f21 GIT binary patch literal 1514 zcmVRCwC$ zm)TQOR~*Ngj`gvBL*F`e+G#CYup$VwP|!-L(h4FVb*W(y0Y!mA6qmrbA)5&Z24p88 z3xN;=cKL}%=S58s)bn|tr)_c`aDbI)~ITU+~} z`B=ak5AJmH*aqSSf(9uE;erUZ81Bn!(J$|{ZL}OOz|} zDfx(=lKeZ12+#(0iBq zXzZn)yt}Pqt{z_hKvrtTom^W1LZK)iATVSV9=wVzFam4!maNABe1o-t3#~i)^@HvG zhJJd_QXjQj*VEFe>u&CN+I1o!S8kI?eKJ9K~EVAn9i_B{B9Mfx0@DgNxDY*ML)#*= zmiCm9Uq0J-EJJ=T{EWCT@G!1ry#SAX7x2_uV1qAQP;6p7?r7Zu;2W&_mNr-unL3+w zS>pmNw`8XFc$ORo!AQbR594eFxOtdekcDC^)?FO^M(b`}FSLeWeEmE%r%+uJmnlV{ zZw$h<7ce1luzqY3zO6MhF0I#6GHF)k5SzH7y<1%IUPGjU{Nmms2Q}d=P&)-lIE&{38 zr0P!lLIhq!4`AcW`Fu5)UIQhy+7@t=B4^ z;WYK((b+=#QXC!;oeCQPL7~e91hvy}IKHtLq@~a}`Py^^1n1mUKZMY{GEKwHirJ<+ zggN3Jz}Vxz-)MgsvAX)c_V)d81s>QFoWgZ>oMCszUsjZ-5e_<&@=#*Qc@FNLx-g^VB z$fopAWFj+UfBN?S{4V%DFzJ9R%3u4wx8*+m!G^emyx0%=Ve;|Y@PAprzoM>6rDR+( QH~;_u07*qoM6N<$g5BHK761SM literal 0 HcmV?d00001 diff --git a/pics/action/32-actions-tool_spraycan.png b/pics/action/32-actions-tool_spraycan.png new file mode 100644 index 0000000000000000000000000000000000000000..215be802b043f40809c4cefa7884aec80d917842 GIT binary patch literal 1912 zcmV-;2Z#8HP)zjp&h_>EzC60N;v}@| zTEctv=)C7#$2!a6D8z)Y@b62TU{H^Evt(unH`@sD>eu*25>jrrH?e{)2 zUoM_*%jM+Z;bHXj-lQ89w|nQ#`{s@vdrf$rzt(lV%L4;nvi~mt$B)1HlB(jnQ&Urb z5NK~-ysd5XV|ulZ8Yg3DYtOV8Z9ql5U3Y zd$25rvf(%`Sc_OBqWpAK0e0-zuI93@pXL^WXj2pm3uRo)=P^D$P8b{9J>`UQT?cJ# z%?}&vs8-EIbAkX|)igDeNJNjU3P7pU zdrP6v^<333Mb)SxqNr$6qv-7HMy1j)=Mex5mLKd2(bbjSa^S$>&;4%z6%n_L)+daL ztva@cxw&}^4Gps*Ma<9Zb@RsgXR{f|vH~Fl2$IR=nqS_(|L4+;1NeBbR9w5=GHeha zG^&@nZcNqJIy!Q!hymAi5sO8`vAa8)Bh0sM44@#k!o`Nb4J6n0;X3Y0bB?v0x3;#R zP?%e+h%8HB>#`$|>gsA8eDcW`;@1a|7p?GcF9DE<7S4(Qa4a7*q;bPs(=;TLaeVaA z1$IWIeiRPn@?8}C7T6K-R3>2#esX|!g+%k6aG+9Hll=RsOe$d};u@Nonh=dPamOiS zGHFaqX@z8<{w@H+ae~K_BLDeWU2ZnLCYHMjvrln37ieAS07NjP_*7m;WZiG%}HRe1yv3vG=ICtO6X&tDQdZZZ>Zee& zJ1|k%fKwN4$JxnG!7yv>sq2+`XV~VMGv{A;>Z$LXQZS19z|Mk#A^2DaiDl2E%xZGp)(7asE%?Z)9iZgj$OfM=iG6O>BTNBB|BFYShU zzSx%Ka&>NKXzU=(9|jQA06dIP?-QV#OPAoo#MC2q)H$kY33R{ z+=U3v6XYz{EIt9Q5V~%CWpvbjUenaC0)E6W981^TGrH~_IePTK@2>pE~? zsawDnrO_CGPjh}5Dk~8XMj%6o(|l`j{Up8~yoHa6M;|>PjvUz^;2I$zzy};VsXO3d zlJgn3xD7rWY5_Jr8nWbAE7o7A{N>>;gLjWsxgam z-bZmnn(Hc7DI^MbJUBzs-86a~3fc&9kx=7cWteBUp5pj#n$L0kkWjN&5v%j>_*Eg0 y$GtSj0W=oa#?j(@h9}DgFbBHU93fs5Dk!2yrIPofNgvXSmWoou2d(u9dXb{|I#dvP^5E5jcv6d^ zh_t1x)fih11@Yvg6$BM;DnU{D*o@yKJ7%3GyIVB$;-owO%+BBc`~JsFCWJAjEM%j~ z0yjso4A**mOBKdCFmy>vBIM)G$CJZFNx^21uL5=_FkE4w+HETZZeMUit=nrmS^zIo z6asL|w#GC9o#smAmhRC_gT_8>xM8a9w6X#1hm1vWedEAf=-%ir;Q;VHm# zvLFF(Al$4FZpni z`$8hI+Tn0C*oV*gU`c>d+6MLV40MilH{?g7707m*;Bsx5*8tY{32NU-eB0998DyxV!$$8o(l=3>JF6NGT;C> zM}jJ(Hai8NSk%G)B%yRdEomMDmhGh+Q2p>}=SpI`@Q*PaQeozk+k=rC;ASYq73_JT-uKdmu~13CS4OU_T&Ix_J?5Ch`MOifJV=qyvrHxmD== zh2dNxj0BMXt>VHVNNDNw`S^puONG1%*2UI=#-yj>aFph1J(m8eEi;jpj!XmofnyuYv%{vb+QDKPlGjs#Fu)y;MPb0WPs{ zq|@mh6k!kEmjiqf^V5orrfJ$`RaLj)|AI*DEVk|Sr-}G0aVd2=orf_zfvRyqXE{7W zsyI!y6PMja%-=JnUrOt@G?#$d+L6KjHzt^7_3-&geB`Tm=MUA@0Q3VO^@E4$A}6zg zy)4QPlRzX|2{8e*Dl-z014a9B>I+3GP~k@avlw6kQxHMN-p$O+T>Jm)_!q5UN~Yb! Raa#ZY002ovPDHLkV1h|<-9P{U literal 0 HcmV?d00001 diff --git a/pics/action/48-actions-tool_brush.png b/pics/action/48-actions-tool_brush.png new file mode 100644 index 0000000000000000000000000000000000000000..71c5a8e5026a78bae279231514259fe6e7c130d2 GIT binary patch literal 2662 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 UN$0~ZIsgCw07*qoM6N<$f(Nhfp8x;= literal 0 HcmV?d00001 diff --git a/pics/action/48-actions-tool_color_eraser.png b/pics/action/48-actions-tool_color_eraser.png new file mode 100644 index 0000000000000000000000000000000000000000..118fcfe9c9c0ef09a39b602b07d5ca7fa841b80e GIT binary patch literal 4381 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 XG2cTF>mUck00000NkvXXu0mjfKCMkc literal 0 HcmV?d00001 diff --git a/pics/action/48-actions-tool_color_picker.png b/pics/action/48-actions-tool_color_picker.png new file mode 100644 index 0000000000000000000000000000000000000000..a630c5a95f04213d7a1d3e287b50d6df60acb90e GIT binary patch literal 2422 zcmV-+35oWJP)*T^8zgZTT+0Xaf z-}&7Gy8?hwS3;QZbE+sIRJbpM2!E$)5_SkTsN#iS;cctm9aV6xU`Oy|g8zrm5O;}C z#j2o+Rzch?f&xGc6229;3vpDkQ5AsT)2X@{Tp6Q8z`XzzKP$%b@1CRo-~IUd@pXu) z6k;!h*v9MAQ4@gRPPFPKAzZ+_8SldMHxv55?ML4~`(Wxfq3oM7xE8rWxIzIgQH4{0 zK;b(vK={>Yz}phOiVE&I?r7<6;Q^}tQiZ~2MaX=ViMMj!8o+-=b(QK1svrvCFPs@s zJfX)>6|lDP&G$vcpDR#A=o`J+$mq_%@s{J5ertvWPQ||F^$7*oDs+z|UPkaMY-7$& zpN;ItgzmbC&)Uu+zBwLyjK9Mhx26%=8b2jxDx9u6L5LI~YJ_h_3a=!53ZE)bBG#6! zC3FJz8}~!2*J4YR52oClY6WL4C0-E)Ueg%94zeg{IJ0MS>-k`iu5mBrwE6vh!g zLk2NRgzSn8s%vVfzE%%ib^h~@3O+(utrn4nNO+Zc!6Dg!;UW469#Y{QsElmVC=-vFBp4x3_NunNbgEVczrmg-IxaXML87bKuQDN|fw0s?}@|K&{EU#0ke&@;8<45H4BrbH)OOVH5bAw4cr9`Q-iVw+S3`~W zFU-Q$I~v54Mj@^K90IcfaIhv0c44*y_)S72070?Vpw!xt#(EJX0t?Qq)XWX2!-q1XB`@Y8ne z*wMl8MMd|}*l5Djr%zE=XGCiDIZRHRgpV(JBC+r&Dq1U$Qk(*ZeQLa&{4?aW ze*xRraj=h7VP%>-oV8OSY#{swAr*iiNdUr6CA^8@i;GLp(9rxne0{wUEiJ9+?d?TR zPY=p#DiQK|2-HVxpiUe|W7P&qe}x%;hDwwq0JY5IprD{;3Sot>t*svxZ!j2Xa~EO% z*-xR^tbkl2gRqIipHuDqUta%J@vjlSk?^c-tnihUH7GByqP8&%;vYS-;7^@812+#h zyyy8|m(p92Nbpe6TbKzzkR$-L%%ttxw>MCTLHv^^7ChUqmX+^gczSN&_>(73W97<~ zShj4LVd>JPPOq|v|1aK&@J7P(QTgy;6UW!o)V;u055Yg;SE8VxknpFlV#Nx$xw#pZ zELp_9fbio9um54Z z;d}VIcL(sxmoEpkj0G=c0BRY>z`(#7!n2+yd?VqXykzCuUf{cFjMi~{a`H(tzJAf7 zMH2xCk_I5W1L3Rb3RvN*tMv>{Rmt(KtqlJJU0vO@nd^|BKY(|4cL(*m1uta)Y8m^0 zfPl*3@Rm{8M&GyK@7yUE#8b=EFI>2g<0TD1css(E(-q+3&uDBIihr^4W_%a5l%C^{ zA3wpyq!nJ$0Ms(J{{H@DL-BNXWi2xdz7~0TcX8}kGRITP)VaF4a=fGg2(R9{b?be) z0>j{0&l_HX&qq?yaW*Ec@RA0gmSOl3!qe;?4sTic&dx4ssaoXbTJY2|bqf|Om>|Xf zoQC368jYsdj5in_@~J|1*O#=-Q--HtHMo8I4#(4&WO%(9FL?mMNBR2t@>XbSGO>Bi z@edwU46b}zTRT6nS@3yCOgzRmE2w4k^XJd!c*z6!`T5Cxe0+N2;}iJ7jLz~Fyrpe8 zzN4d)<8R%{!_lKj!{8+jK=_Y0ZQ8_7k+dnzc=l*nd5-U(?^T=eY_oz|M(^U{!to;( zfbcpB!EaQ}%_fwUm7}Djls550_MGs~(B97QRk(RG7xD2&&G=rZ*OnJ z#KiK|r-{Syg@yOngyF4YGPTgvVffn|Pn#9gGRArH=J8QEQpNBS2;WAJd_&Ut-Hhi4 zF)G>v6})nCtndwlpETlc60TprKE}(-3zsfs@lz$OxEar8J4au?p3N&Z_8`Yo%QVcL zJ9qM^c#D?sHCpXXl$Ms$^l4=ONwdOdWnJZ)b^Us8*6$2|d(NCW_M_$v^3|(Xcg4jW zL{LyL($dbeBQU=)rKEfgKR=CmM6#A?rKEi+pJ#H)`{`a|1+ oj=veS>JO-8Dha=Fbi*z6Uz#J?;LX$itN;K207*qoM6N<$f=U~}IsgCw literal 0 HcmV?d00001 diff --git a/pics/action/48-actions-tool_curve.png b/pics/action/48-actions-tool_curve.png new file mode 100644 index 0000000000000000000000000000000000000000..98e220fa2f892a1305ce5a1fd188d5d512b57916 GIT binary patch literal 1310 zcmV+(1>yRMP)_!}m3>xtG$__F>-(Nl$v- zxvy{Uyw5r3-uv!uiJ6VtSp>8mKs|tZ0JpLDjVk2X)vfcHIY>kyoCcvy(BGgZq0!0l z^nlv|R5XpQYFXoh*^NJckI1;IjJpwojK0ZCYSQ}v6#O!ngU}WtD(A|uFV8@GvYFJ7 z=K%n|3uXX%XGQQzF5$b*Y-TL$*&qh|(=dlh!2b!|3*7)+09}vEUl#>*FJk(_>U!=D zVg>&w%umqy8azWkgT95qdf8BN7CPL#Ch!65+kh{8miT1K52FeZ*8%{%fZ10B zKf%m9D{+RI=LF1;HoWzL(q=a+&gLr6n(e-`RI>g+68GhngQY?L?_7xyw3yuxiwbRIc z+KE#}H?rXi-6&M600{l_N!T9fcIYy_FAE`Fok)*OSL<|jpluQ0f7iOv3=!RzPL5oy z)>m+CM~@(J09m*nxcP+G2h(8wT%Z+$eq00(p_e4{`-TGG5blJNEz6SaW1B~1##IymY=<=`zVY6K5ol(HKI(-a7W8>?rh67b53^6<_h z!i;ks_n%Q-$Y(GI9JsnP7d+&OK6kH&^_eykv` z;On1_+DN(pZ|Ba@KsIM*R|+BS27I9^XO70Axti|_NtPKHlho4mv0x~?(7`MewBc>T zCtcat8Xp_As(VVW0tmJ^n9*7keEx9U&7HY#6nq}$WhVguUY{+;-3x%ZACHw<6`pAe zM;29j``XT~b|PA=;K^j>lxqQ`Mvt9E6dA1w7RJ_!*ZU-PXz);uWA?e|_U7(a7I%Rk zP_MUlbUsi)_E(Y+y?Xay$p<{xou)7G{26Uk`8eAB(oXh1d ze&>1s$z$;gc=#v!!i)sH5Q?nZHAm*>nWp?+2wJXpAIbaJ(y-_zL6zRGLDf|^U z3$(S3$p%{)J&5zuIDZK3un^)k1h_px<`@ZUB#+SG+J&hHkQu zzfE&?d15$v-qT-bMiWO8EV&!aT&3{LoRv~GD|nB8Vj4-rMgY|YvO!RC8F&WxepDc7<21E>d355QZw4tv{a UCrMGwjQ{`u07*qoM6N<$g3yX^82|tP literal 0 HcmV?d00001 diff --git a/pics/action/48-actions-tool_ellipse.png b/pics/action/48-actions-tool_ellipse.png new file mode 100644 index 0000000000000000000000000000000000000000..e23579f4e713a87794eff0f05118bddd89eae951 GIT binary patch literal 2590 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 literal 0 HcmV?d00001 diff --git a/pics/action/48-actions-tool_free_form_selection.png b/pics/action/48-actions-tool_free_form_selection.png new file mode 100644 index 0000000000000000000000000000000000000000..5eff82223520f247ce16fee2c82f243ab933a7b6 GIT binary patch literal 3631 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 B_&Wdq literal 0 HcmV?d00001 diff --git a/pics/action/48-actions-tool_line.png b/pics/action/48-actions-tool_line.png new file mode 100644 index 0000000000000000000000000000000000000000..95a2faed703d4789fc2385dc1785bd8270dcc5c6 GIT binary patch literal 802 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 N002ovPDHLkV1iB}dT9Uv literal 0 HcmV?d00001 diff --git a/pics/action/48-actions-tool_polyline.png b/pics/action/48-actions-tool_polyline.png new file mode 100644 index 0000000000000000000000000000000000000000..135ba328b69c69b7e545d533da3f51d9777fe1a4 GIT binary patch literal 1394 zcmV-&1&#WNP)0AC<=cOl@v1>OhV2i}j!SdJehk+{>{b^)MoAirqfKPL&@XMsPdgD2v6 z%Ss5mtbrGKP5zutKm$))(q;j`?;w}z!^ayCuup2??UfODPYWL{CRiYjfCfHhd*Zl# z0>HC(Z@E8I=lR04LD)7WXz;;7z z5n%Lp*IBST82z!3fq3xW?#L1Y(wmkpLq;B;n&C zZsH2(_qS94_^dMW3*_GNlXYCehYbZ}06tZQ_boRm+V@)~z|`3c?4Q-U4S7pklLBRU znZw2cw)dojFUCtQa=x`C0zgkGTje?04b=h_cv}Pkm^xv= z3(7vfN2zVbE47WefDBXU83}JEQofT&=MlQSDFML0u7+o{PcvF;Q~{;7S(X6MYb`I8 zE5jGV@sVWO64DY+Cj1W0&ECBp|1%81%pefD{l;LGwZ{}&wh>uHnFV!#d2Yoj; zEWp(1LExfN9=sh;oE?zx(H02A9q%Rt&X?dxdO*m$(y##V?<(-;xf>e;YMX4Nrk+;? z%)_*KRl7sIBMSrUh8c8@zP?Uq$%LU@@GJH6RfG zq88qo<{h;wSis3j_!yy`5dF=DY4fIpPxcoxN7e4DnfKFj+LYjH1ZL_j+-|@xRKbh9V^Gf@tgh$D8wQKD z3-H};s{d2K7X`QuK0Wx*KlJUY_f6TP0)LV87zs#tw=n^>#4uasB;e=dRv7^QE9VpB zKJi)cc6BGCzNe4vk?d!oE>^({krxf>_)U!dM`d`D6gk21^JJ^?gzW}=l==OWD<4vN z_&moS()WlcwQEWVpwFngO}LVI_Vw!w-lV>&hOe&Y$t(|`HB^{G)HfgVbj^$MAP_z? z_#>Ygq5l@`8<`&77CCEJ!{_S4C;qGHf0Kn-0Q@{V&JnI{Z|K`;4ZN)9%?0%EegOOe z(8uX}oW8Y2U$5)o%>@|!oC!SbY5K>0T A7XSbN literal 0 HcmV?d00001 diff --git a/pics/action/48-actions-tool_rect_selection.png b/pics/action/48-actions-tool_rect_selection.png new file mode 100644 index 0000000000000000000000000000000000000000..fe228d09dbf3644bcfce094fd3fc4e1b17dbb819 GIT binary patch literal 3509 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 literal 0 HcmV?d00001 diff --git a/pics/action/48-actions-tool_rounded_rectangle.png b/pics/action/48-actions-tool_rounded_rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..3787d0208193b2f6900edabe64cd898f71ab2d2b GIT binary patch literal 2237 zcmV;u2txOXP)~{Ul;JHsrm0v{(`a(<&#~?KDZ`ad$$V!;NPRTqokvVQPzwc@vpz-4!`{_ z1^fxc6D1-gBcDOGwp)PksgEK%qvopSkn-<9hj zi~26Q-;A(kd&KdL>rt;)F7BSMsP3LWQ{9fQ#~F+3dRClVkF7YlY{b#!nA(xL5r_47 zs2!X=)b`FE%MK_MTl=5)n^~;t`_0p50(k&RP#9~69P4FoWIN&<1bIVp>BX5MBoDm9KpBs zDSKLaHsBjO7dn|8iVg-#!K!OlwC9zwyN;domFe95)eL%39AZ6>@;7_|RVtOL$C2v( z8gD&4vtWKoyfQ5it-P!C;jOGUbuDx?Y{0V^2)wRM(O%uEXsvFZZ>emWZw`H!{*pk{ z-VKVa8G)#2Yj2-RJ)35s+WO{xG=Ma>DmI>`z+2}DS9Z&Kk6!C2cl;Dz%TX-<76O-`AlI<0WF{OdcML`$1f_*M#s0&1`ilG6$#Tq9er0eC&ui|Y`)9_yJe<|@Kt zh1s89XUBS88jC*U5m|*Rpau#U#XhKE_D}D&b%5Wg^=HU>R*9tQVf<7M29Cw~%ja}l z0i#fWX3NksDCXg<1H2*YT?VW-cN?(YqG;WY^`!jS())43?9<aSQ9v9^a>m@-IE)rJlGv7K4W{ zN&#D0NAP;B!v+k$n!=nN^8F-D7ux6r{Fn8C?OTWb{wYIL;Cq{sxCMam3g~$U>)B|P zyz@ku_1P6|Uw^Oywy@rTw+{V%6Sc{X0gPF|cC5oZjux66tiO#NuV8k}Ni=IgyswSN zp1M9BXRrW$)?ovNS%*~;E?{h63>H>nSSQZjnuzxe zapnYmhJocCdfiyYF>q)N|9jjd4EmeL8^E`t1esxIhPxLdq~DL5%6{;4D(4ITm{y(b zwDy7KPA!-%%P5+w$Sjdm!dB%;N=J(+?qJK~ zGru<7<8?DzFTamX2(}W^w6wOh}K^q5Uz^%wX=zTw6Mmg<_ZqG0Hm5yL_g5&1POnH|T6$UKy zJk~aNcxV~ey+WUZ_4QUMmQ-Tk$+;s4kIUV4Yqfu8oYY>O6ZPbP|7{ z3n5VsslCtM4BFN!CM#;tA1x@yoA4LF4|B)AhuxG!%3-yk7aT{u&W!|I`{B(_cqLMjO{Ig|`00000 LNkvXXu0mjfo83lw literal 0 HcmV?d00001 diff --git a/pics/action/48-actions-tool_spraycan.png b/pics/action/48-actions-tool_spraycan.png new file mode 100644 index 0000000000000000000000000000000000000000..51b397b746a5d1e538c3afeb2f647cc9e728a3c5 GIT binary patch literal 3253 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

fsa{o*q9Izj!SEe)+Q9FCT8--CvC6FD^bU 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 literal 0 HcmV?d00001 diff --git a/pics/action/sc-actions-tool_color_picker.svgz b/pics/action/sc-actions-tool_color_picker.svgz new file mode 100644 index 0000000000000000000000000000000000000000..e680a1a70a6f4ba19c5625f0935c32ebea3521c3 GIT binary patch literal 10939 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 literal 0 HcmV?d00001 diff --git a/pics/action/sc-actions-tool_curve.svgz b/pics/action/sc-actions-tool_curve.svgz new file mode 100644 index 0000000000000000000000000000000000000000..9aaa3e7f9377d58c6b3730785ead3511c645bb32 GIT binary patch literal 2380 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 literal 0 HcmV?d00001 diff --git a/pics/action/sc-actions-tool_elliptical_selection.svgz b/pics/action/sc-actions-tool_elliptical_selection.svgz new file mode 100644 index 0000000000000000000000000000000000000000..63c328029d90d4b9dd3417074cbd9dab3facb118 GIT binary patch literal 3597 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 TFXI37h>!mPR#^u3q&WZpSxGBB literal 0 HcmV?d00001 diff --git a/pics/action/sc-actions-tool_eraser.svgz b/pics/action/sc-actions-tool_eraser.svgz new file mode 100644 index 0000000000000000000000000000000000000000..d7415948df9a70b22dbbd097373a2657db97b168 GIT binary patch literal 6959 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 literal 0 HcmV?d00001 diff --git a/pics/action/sc-actions-tool_line.svgz b/pics/action/sc-actions-tool_line.svgz new file mode 100644 index 0000000000000000000000000000000000000000..622248262bf864d58d2f3e9beb3b6b63a158d6e5 GIT binary patch literal 1333 zcmV-51ykJ~m7zUNn{+Dn42B$9eqQr5{KL5cur12ntnnHFhV zU6~XpdTr;|cO;8?df`RUzO;q3-@kix_lx6L9~7sWGwYD+t@T+vRv9h1^?IvynR?#gaB%Zh z=t{Nm-dK(Umetedm1SArR#Yj=mUfFT_o`@cEc1B5cUMeu`{A@ zr5(gBYOYc@;w{fNTRoTOY-vNjI6z7BZX!y%o z5v&lSII^eS?-dmlG~RA$@2FP!@im5;h9pF{sLii`!0_NN7Rjv>y0zzU0U`n`2g#de zWt09m<0QnSQ#YF;-6{^7_@<~EFF{%&qwmSiWKr-8+L9Irda7*8Yw4_4rNzIaQvq+- zjUb1x=VE;V-Xb@_K@j=9^w{i(AdZ7YkIgP(vkCS(#{|k5`IkAyksqO3XW=G`m_*Sn zOn?wC0uqi%9`k?^iP3+^^1Nw{-@$TcETAhbOL!ijY6*@LWcOmZURNAknOujuN}mdT zwsIgQsnQ!C`+*<5ZA21End?*9nZAWcuX`sKyyKU7k5`GGBvbDsiD7RMzjuG)p<3a zZSyf`c|W^n2^z$5#M6mjdd4^6QO`s(>4{9R*NfLCedpB&b@SAHY1E$#>?!Am{xo0F zuG}Tv@Mc0luIuJ%6)UM2S4I^z*GSb4k!ApaVnl7dYNtn+_0^U#d05(Hs{Ae`v|Q+h zhJZ=gj^_@D2u#zdvS}p*oe;yYohkQ90fA0IPaKkJZf4e0TY1>1nw&K)Dn2!3GTZNJ z+x?mS@;F7*#?ERbAv&%0vjq;hfNY)iixne+o~o{wG|s5!CvJmAG-*}QE`_NLrZ;tIOr z#nS##D=j=Ps@#`5$#`pSh%2eyz8askYY4PE$Io#5~_%=F5Vc#i5PeY%5sI$2R z#QkoCfeY;j0a?fZtS^?+zx04F0|-pE4jj5ZCgDWdKO6l}I(Bz4i0{^DD%WKpC=8)j rt`rJ-?e)!kh`*Uu?*E$ojGAGeB;5(Nfj_*!M1k8s=lLR@@eu$3j7FK6 literal 0 HcmV?d00001 diff --git a/pics/action/sc-actions-tool_pen.svgz b/pics/action/sc-actions-tool_pen.svgz new file mode 100644 index 0000000000000000000000000000000000000000..0dfdd297e9f956d63130d127581318ce73281afe GIT binary patch literal 4924 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 literal 0 HcmV?d00001 diff --git a/pics/action/sc-actions-tool_rectangle.svgz b/pics/action/sc-actions-tool_rectangle.svgz new file mode 100644 index 0000000000000000000000000000000000000000..a4f8b15c7c2b0ece006205eed92bd5202246f029 GIT binary patch literal 1960 zcmV;Z2UqwXiwFP!000000PR}aZrex_ea}~L+=0Fwm!Op`5% zV~P|=%ChqHTTO{-Qli(BK!dpwHb~Z`tNPUGZo1%`@1ItA@Mx<#D~sC+L3k3_A}Q0X zSlmuNfBGTRlb~+mB8~I1u(y+RFoHP7e0#O_jHHX%gAou58iN5kVxHc=N=Y zZ|yy*Y+Y`uWIue8^ewA&4yHbnEo+U4F($$^qBI1Hu-+9-{1g`TRnHX?9CM`@;|O%# z^4D3-o^pWU*<@O6KRQ6ahTo&Z5ySml=iq5kVVmgPr*~0j9b%L=X>XX$%f~$}M-i`V zy^a%GA49bM+bnICw-c@rZm)IIikCK9ESqzAmflWazJz1bzIPeTj-0}X(N@)k>8m&$ zdZlG@AJ>p}v@BOPO5>u~MccA^h#pcKy}$eE$B)rNnFAE-I4hbnXP6Z@Csmzv{{ZE9P6bVd8A_*~mbXSJiZ6DE;XOcHaH zJ~^pDS}077wZdb)@Sy@=cSGi-J!A*%fy$3rf^V}xD z%Q)Z0yLu8t2RPlRasF*^R)8Bp66PR07V{n8CXpeWOXj6d?ULYHbLDN5QQPFi_GOGI zlvA>CjEQ6tPeX-_(p1W6h>;+QQ!xa2hy%e?ltDk7z=<9-$vbhBK`+&`%*Ql`&I+7?%WN&%T$}+Biok*5)^(=QjWP+ z<8U%ci~u+RMy4s5DugMOjC>26!x>X-=s$2?3{EEHMK;iXc7Tx`z++7RJ{WUFsTu!} zB1#JlM>`Q<1yS4~&zQoRTm)qCm)sWwmq6NaJS8(mO+!wRrkFU95|Z)D>c3}S>*C5G#ix*bj$MZE1^Dg={^{2q+u_$$!7 z#1SVECW;s{g$JdnqC>_C0gTp)^C4pmlAI8(JvT>f=Y88h3}YdiufX^MpK1azAvniV z=|9y*kAU&xV*F?sJ%$c&-bAkb(movU^pMRTJb^>|!gFXOb_!m>M6ekse9gMm@4xjIujb}|M$me{Z} zCB&l&NrIJ8XUa`gK{3NT-dUJbd+QicFU#$sa^TG4ytb#VX_;(R&Udrt%lhfm!a0Zc z_a$W4RGYH_wpjtQ4G)b-f+>E+qU%XYt)8)f2+uh?v^y%SY!jz(6Z^7v65h4vpj|xs z`P~o4ZbFjG{tInM-yLuU@qM|0d|ID_U7F0?Vu+i!*$OJ@dc*&M7VS;s6$9gDx3(Y{ z4BPd&$9m=Pv&x)P^s&kE{Jjg(?|MXsM7utOp9j2jvuxiMe)hiHecemYDV*UNoV=sbi^5i>yzC%tc6K=D6; z(1>tBB%Ugyji!|2;6qO;C?c3BB|%D%7F=^YE*&4CFZ~h>_Hze2k<9Pnev^Y5Il{ow zOx)G7<>gW-!?~19?@I*)e0_C(X1r;&=Z&~OU+EMwf^cJ6h?Ox$u|4pZYpi5XdKXBH z2%(bGDIeR6vf3V{UqJ1VvbKCaKS;C;Q;D z_fi;>J9-&T`%*{*+$k15j+gZ^`rxI#?C-qP&(p{8c#PsXPFeAP33=$jW4Jml7G+_d zJw94_waM+wK3brDdZZ#F3=kMnks&3(ji|4o;wr8lY}KZ5cn<3(u9|^lm8FAsTkPL2 zMP$F!9X$wZ4G=9e}SVxcv`KHYeM(AOHZagT0~v literal 0 HcmV?d00001 diff --git a/pics/action/sc-actions-tool_rounded_rectangle.svgz b/pics/action/sc-actions-tool_rounded_rectangle.svgz new file mode 100644 index 0000000000000000000000000000000000000000..359eed2400eb85821e776c9b70f3988b61797829 GIT binary patch literal 2220 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 R{MY?|{{x@Cp;+6S0RY)p*Rucs literal 0 HcmV?d00001 diff --git a/pics/action/sc-actions-tool_text.svgz b/pics/action/sc-actions-tool_text.svgz new file mode 100644 index 0000000000000000000000000000000000000000..52d87f8124ad68c888e6f6c3f682289bbbf37a48 GIT binary patch literal 2666 zcmbV_cQ_l08phj0ksh_TqxRN{Y9mIeQ>sSwMzuzTqRLTw#R_e;(uz?lsM335R!}oU zjAO-#5vv+RsIldB69QMe>~bF(r=o11SWdxZw|S zSvg|fW(+tksX*b@Yq@LD&X+BRJ+E0eR0-#a+%&*$(+%;hTFNO2Ai<2R!w7~helgCw zV`g?OL)&XSfW8{rc?Wy5-xnH)otQY7n@5lbV$&O-5dS17GhmXc-zDED!$z<4j;yG^%`^zV=ZI7#QyCy%QY+^;QmFL1n-X_4n|o+ zz&1-UR)e}WWm57)8P{*b^5>nwrAQ#BliyzSfLyE>r8Fv)cv^zn0e%~ z>gVCQZggyYB25ko)mv01?fs_Hao1Po>KN8@oKCHF@vP}(R7d)Vo2|M({Ns?(r@2@^?|eNSc=v;(a4n`PLC` zjrsU_%$=Oxp$KLTi{6{RFwDYEXXP)Dp|!_vTjM)q>^fOTD7CyL7axL9wzCYclD4q2 zT|)Lep~z0h(+?f#tE8KxYLGpqqgmXS3z$~P_IWh&=zWNlZ|0=^!ZaZNwqxPK`%7mV zdu=EsR@1IAJBrXQ2twEXU2iu8@mSR>+2{d$VMEAkyi+JeLwwYyv&f7QwHng9gp08( z@cg#Da>e?JvCLw1RE4t2#v|P0bm!{S24z+~OAS_K=b0KpHV|C`GM{{vm-V*`R6}=` zl63S^Uk&bDS;CC^?qtRca)*heyQ{-8G_UlMRBe*=Z>)FwCdb)S<|l9Uun2!F9|3}S zq^^Fg0Tq#7qT`Wy;A{+7?{ml9$rP}gd^GX4ir}bGUo|H95v~9z=xyjb4C`F!8)RB1 zt1>VW`W|m`hX6-!4pE!2gM3Im^ZwDqAkwB}|7cZ6m-?XPrLxL8fW!4@W7v}CxoM%s z#$YapEJ{p)rZAKzAQlde;L{C1FD$Qi@{nK@OqEh;-i)XL;s&k~aKQcpHBNGpB7vY0 zoZm0NwM0au5D(XGFY_n57obSaZamdmIbx=H((=WG-|XIBz5(PvYn+;DTQ=tzL0H6H zdJ|)$CpB+{YDe{-b?;XuU}6__oR)hL6&lhozT2Xoes_8?M!WoaU#qPlY=8dVL1$K% z0kTAz!*Zvxp=;P=`CiP&kV;|eUw@rW3NiyKilD^U9?v3vFI`V~$_9+-iolYDrP^YU z1fNoHDmqoVS7)3}f~$}$8IaTigel29;4RP?GWc}SD0qCt!?g7Df*%i2_S*jJc~OIN zUj%*a=-i6VJUB|$)XdZuYf1T(-CR2xEBb)CmbjGyD1`-VGnFRii4a&T=Q2Mzra6)F zh;W8WvkTp<**#YP*)C|_XZ2iT5fxk_Osrv}^mQ=!1$78a{f9`&>Tw+`0kUkzZY4*k)RHoCf2$F5l_q8h#Z6bN{*6$YV?B+OYZYXbT-;+-wHr3u( z7%OtrC~59f8hByX?oHx(RGfDBkm4?@njeg8-%$&-1x(4R_d6BxnAGf-AC(Tju>S?G z*avQws4Bwl+@E+E1e7jdGXPIzCc4n(v%>WrqYLw+4p zkJ3gqi~!%Cqs0j{PE(L4MfIL3r@xFAA@jKCVIr&k zNnPhI@ARze8g@N=>K6JUivH;OMI9AnBh(kW^m{{W1q_S55BKIoc~XJulgT{Pb`}G<#0bp)3=&bZsxyO Y!XumEy+4%1q<5TV2$)LEDV#d>Z|0#cCjbBd literal 0 HcmV?d00001 diff --git a/pics/app/128-apps-kolourpaint.png b/pics/app/128-apps-kolourpaint.png new file mode 100644 index 0000000000000000000000000000000000000000..b0f2c269c051e887f2d828cd084403e86b51de5e GIT binary patch literal 27246 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

  • nE95yb&Lg;MT_M2SUg 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 literal 0 HcmV?d00001 diff --git a/pics/app/16-apps-kolourpaint.png b/pics/app/16-apps-kolourpaint.png new file mode 100644 index 0000000000000000000000000000000000000000..5951eac7bd425151f07681759f552cb8b8295511 GIT binary patch literal 812 zcmV+{1JnG8P)jQE*}J$*~0=+utFgfG{o5-3E6{Kkf7uPMgJ

    Hs&B~!7laTvy9mm>m}@rds$KA!SbuSL@s`mL0sv{?B;2(K?fu+wuQyvs3$xS> zv?n^8Z zMIDab;gGy1MKy>_{OSlLxYGRzu@?QjD`p^=4U%@c|;S-ukm4%L^B zSA>cpZU}^Y4(%s_KuIj4{m|EQ>|#;cJ_QaxCcnzZKB+ndq8LQt2&RXWg!E*3s=B>l zG(VEd*{8rkCiNhF{DqzICz8(XSxJy2BmnIl0@E7J=f=|Fgl97h`e%V%!-&bcpqoEG zs8kNnUL@$o#$j=45QG6fu}K-(FaJ~EkQ+wO!XV!^%O0G`y;(|_R;K}{4lq5$T>!FzkhketkxW&%4rf6`B`RX--Z18e?`#Y&$ChO;>_|x&Tj=dYPsO1_{Y~Yo z3)=T+Le+vYrGimh;O%{M+A;+uHv^wY27f)60r=<5Ry zjRy8sR$RAjfajGrWdeZxJGTgSg}t`ox>^%=Fy3`=ZLn55tlH+fut8Db{$dx#c)t&! q0U3r>u`HYZD=ci}`225Z)#{6CY0000mR~QEH=k)t}-{N8qj91ks35tx!)ph9@9E#N_upb#6C%$?jTlvYdhrUPe3IZ%LD$3WwyuErQCNXMSPYMcC#ty{8_$&Ns>B2kN+}+zKcLDD zh!ll37c)O7NsEq~s#Ubxd|t-c@ooFvo4bpzbn_Dk9J$C%U|+8>Vt4NfF*0T z(B4j~^ZL0H+jf>!l}|YMqh)F}&q(*o&dlDZ)2k;1MubSsSq6TD3DsUWb*|>Zcllo% z`sp}>$9!3k`>R@`dtu?qP4GIcP$cLemL?EG<#V$8N&<>IIG`ctWy8HvIu6=r1Rpv_bd{oH5Zu=o2aN)N5s z_d~-k|JV4R&1?HBtu6Np?={})CBy}cy`KK>u%jCeI1a=N95YH%roy~DO3J3it%`RF1IQ^F`?Me>(acq zb~!@46sqYefMwBCb<1kJ^7EpymSB%yw004hCf+anUicZa7Jnknr}IVm^gO$k&VGDX z+T=Scy4t=sxnp>+*Ca={s#ah!v_BoQ#AMMC>L+GjrbJ{Toi5lgn#52BN3T^e3mMqN%bPoCF93 zyjg{d)H?)>Sg>c)f{htckrb;l@}@I^NPj}6#EAP3fAC6_Rw@SGSjx{4(t!<;djWt0 zSp@}Zp+tf?3zkF~4j=td9;Y2#y6$zj9Dc-4Q%c)9ZCeG5Ot5ScC&Qh=+wijiFtLJk z0A7_+l)*^|x?O-WhGjU$l@|phiU`SOcpgJ06BH~fVN5;vc>9jb+lHG4pLDXnZz!71 z(j@e-M2M&p_T!izoeh(x2~;?`P;btMMsa;NpyYr~H^50kECoUgrD8{EDLAbbL4yH7 zVA3@^hOQktoi{5QDf3d3GH=}LDP{To@J}tS7F|c!Dv1j#5%!1Oi0JEXx!l^5tm&1- zuz;V^kjw6*w09jLSF;-$)-z@^;bUXNgFrU`Hvoazg9dZ0#Ry=S<@b*iu|-UrPN&a| zg<>S~F>FCZSF3T`RRkqZr|HuoEd#cWmaV`7)B&3bz-H@ox**578f~^hN@*B>^E>|y VBx3I1pDX|X002ovPDHLkV1jJ9U8?{9 literal 0 HcmV?d00001 diff --git a/pics/app/32-apps-kolourpaint.png b/pics/app/32-apps-kolourpaint.png new file mode 100644 index 0000000000000000000000000000000000000000..19ae9bf21852df0decb860547318ffe647dbb35d GIT binary patch literal 2139 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 literal 0 HcmV?d00001 diff --git a/pics/custom/color_transparent_26x26.png b/pics/custom/color_transparent_26x26.png new file mode 100644 index 0000000000000000000000000000000000000000..7dafc0c60153f4bd3e63127f4d6f12b8a4193939 GIT binary patch literal 972 zcmV;-12g=IP)UBVV$#1}J}8#|yJRk$5mydXE5Axf(v zJD(;>t0zdPC`6(uTevA)w=7q(EN8+jXu~a4vMqAWFK59pbqVsx;}EoKVYywY`;H#(Liy*KzGVPYq&#&)0i5iqc$};9Z*FW0cuupyOzy8mR zuIhKI=6I~)RMB*lcK_usK=G6$(FR&nYY-Sy4jw< z>7b^+puF3mtHYwN$D**=qO!@N!|S8J+@!eRrM={(!sw;N?54uqsL1WA#NVsS@2$t+ zud>6h&G4|d$FR!bve5Cf%;UAb-?h{8w#MeS$?3PwGrz|E=UC8UO$Q0b)x>L;#2d9Y_EG0bNN%K~yM_V_33e*)k~Z zG%A9#ftUeF%D_+$MGC>Giq}u9L?~Q_B&9n`cc})l6x@K8h{ZZngBN16OMB5Gt;vfN zvDnq&(%-K>zu&GM(=N4ny}hbHERA8;%<%5Hb5(#ir~=(C<+iptbCiI%O&raxCimI1 z8yXaVc(!*Vs$KH6)2G+g$^r58=>jNr71~azshKiG28e5FteTPSlASoQx_aV7scJCI zgJjo?(DL%WzVdR(J}~Vy4PlpLPgz+{Pnns7DVQ$ffZMgezND+G#8)d{EKl041jx3j zhuI}wT-@rZms46QQd*iV;MCe$%nG$@l0}`XQDy-U2!V(UPU|}J2@tzPosH6S!GIq^ zr7~MGLF^Ka%7Owu7$blIYFCO;LK+zGLa10qKSUTNYlbBO0S|}>WcEjmuSgl6=xFZf uXb+Yc%(M|A=*Z>39)~?0ny?uor$Yd|0AUIJ82=Oi0000 literal 0 HcmV?d00001 diff --git a/pics/custom/colorbutton_swap_16x16.png b/pics/custom/colorbutton_swap_16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..b1352721596fcf87421e2f2b884df41b12c973d9 GIT binary patch literal 119 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6>^xl@Ln>}9Jr~HvV8Fw2aqs`7 z%TH-7m0kKw`H(QD?u6iM4GylRrUI`07tYq*IB>?n_`<)2N>vxn?b4MsMGRgR{~5Ic SEkl7OF?hQAxvXiJ}#2xCc* zUoeBivm0qZPO+zpV@SoVt5*&A4k!pPJHG#KKUc{0IBWD0hVlqz5rqKGPt2XF8&#$q z=##ki<;EFLq6?Z1P^~lD#NZ<+{7>T95m^mCuER6U+*?O5fP)ksYJ{ecCOF zvXGK#%Y3qSt;_oVbCKtdj$Jl?!*tdMe`eUF&ct?L*7=h_>lr*<{an^LB{Ts5oz6{| literal 0 HcmV?d00001 diff --git a/pics/custom/image_rotate_clockwise.png b/pics/custom/image_rotate_clockwise.png new file mode 100644 index 0000000000000000000000000000000000000000..cd1e00b7f75fb7f4dbda85265b8f3e7dbab3422c GIT binary patch literal 196 zcmeAS@N?(olHy`uVBq!ia0vp^IzX()!3-o*CUIK;DYgKg5ZC`ez`*c2>iJ}#2xCc* zUoeBivm0qZPL8LGV@SoVr&kQQ7z}xs9pC@ApBq(>_V`eNz|>0_oWE4Svxp|$RBP95&MYu9$N$DqkIKQZt8nT>Zd@)q50bvv#Q sm9^${>o$+QAA5i8f4|2>@&63weK(lc7W%u@0_|n+boFyt=akR{05dsHxBvhE literal 0 HcmV?d00001 diff --git a/pics/custom/image_skew_horizontal.png b/pics/custom/image_skew_horizontal.png new file mode 100644 index 0000000000000000000000000000000000000000..b6c39068aadd83f53230e60fccda1577cb790b3c GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^Za{3p!3-oBr1sSSNuB_o5ZC|z{{xvo3}ncB>z@qd zFqQ=Q1v5B2yO9RugnGI-hE&{od(n{ZfCC4!a5k~V^=FseQ{!oiRtO#ocsA4Ll;O-N$#pZYm@iqfWtVu} XT&~GW&seSkTEO7x>gTe~DWM4fC!Ipp literal 0 HcmV?d00001 diff --git a/pics/custom/image_skew_vertical.png b/pics/custom/image_skew_vertical.png new file mode 100644 index 0000000000000000000000000000000000000000..7c6fad9a6ac95e78513ee43d6fb943aaa34a2733 GIT binary patch literal 237 zcmeAS@N?(olHy`uVBq!ia0vp^CP3`Q!3-p~__zxLDV_kI5ZC|z{{xvo3}ncB>z@qd zFqQ=Q1v5B2yO9Ru^n1EEhE&{od(M!H*-(JxVD#Pp|8v$XVeK|8d6r-;%k^yLvh3`& z^4^spPXjqwDpgmlKe(Zji^;gE5o z0;6ftMeB?U{}%>vC=046bEqkII8W#>Vw{=2z)z-$MTNt|mf2C}mWx7%gN<;8u(->@ d^jRKX7cO}1c`Dh4`@!PC{xWt~$(699L4PEG&- literal 0 HcmV?d00001 diff --git a/pics/custom/option_opaque.png b/pics/custom/option_opaque.png new file mode 100644 index 0000000000000000000000000000000000000000..6c88cb4174f5cbbbde53e7f80687bba3e4c5c522 GIT binary patch literal 717 zcmV;;0y6!HP)hR}V~hkMr~qhJVB4+%?X-UM3h7X$=XP5F-L$)KeN0H9iP<$5ps@(VGI?rd zm98~wc5rY!0|NMTs);U+-B>4$R_`25j8ZbB04=DjLGd1Zet~Lm9FARs>X7bVNZOLT9bc;HufVmTU=_`F; zk5?i$oMGQUhT=TnV^#g9ZS|5#Ut1hFI0U7S@VyRREvPll9^B&ot0AQAJEuoA5z&T_-!hZ6PyLHkDWGstqfH60%pbpf%FS2o*%0O_oI3c<~I{{yKRbDEP7g4-PXxL!$c>3|j^L?H@-|hRy=lf-nPAOfz zVKo4$S`}{)JV?+cu}GL&t8al|t3Fbt`4$-@{fDl^5rCjj5Od2;0(4duNV5L<>xiaTld%T9hW?_iFvN5gRMN`iu=mJ z3YVt3hgLDHt8jXqxb8Nap7FdWg)I)Y1lW|YsUV!S{{njp>}{~yVQ+`M1NO^s1p7Kd zd>wCuIve5q8O~-n+wdei`bo}CR~TF-t;?iiIJjNJSSjPA)ca8{qd`XF78(PX8o(Sm z5A9?#cnNF;e+7R7?O;3D0bT+B03F~}uoLV8yTNPVb?^pw6YK$d!9K7b8~_KwTi|VQ z2pk4Sz)|o|@GtOha10y=?|>8FBsc|5gLlDu;C=7`=mZ~vbKpGq2m(n>!+50t)1$zk z+r2{dqB*@vFt>LTmGL<0xltwL!|M2$Jw>BdOV}>=Fh7%h%qQtR|Cp5CO~zx6QzhY* z_s>WiR}_9;J9u33t@+i!5_3sv>Sv0I3Y#L(Yi*NtZE@as=J7Wt?##tLt(vJ?=o)C& zHu{(y^y@&h zEiZk4H0RlK%Dld8eeem-;jDslZ!K-kxKLSLI9l58Sh`TM?(Gi;&1U1a-M?G1B7(LR zy6awOkjbMzkSId~)pIY7RZlB|_3x*t!aHS|yTW^qO-?2ichA;F^ew8QZ`AslJ>sk* z`vGnXD8$`~=GPvxeB zNImY=>k|9xUk;L&c_beQ7%tkE=6}8Gn?(cr+&k3M3sI)>Be~;cEwn7MH8R`fZjorK TruMDbvGOE!f-e4itg-liW|x%w literal 0 HcmV?d00001 diff --git a/pics/custom/resize.png b/pics/custom/resize.png new file mode 100644 index 0000000000000000000000000000000000000000..ef25380f7269572576139284f2c331ed942b8545 GIT binary patch literal 4009 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 P00000NkvXXu0mjfQ8cil literal 0 HcmV?d00001 diff --git a/pics/custom/scale.png b/pics/custom/scale.png new file mode 100644 index 0000000000000000000000000000000000000000..6941475741702ae62c6943493f8204ffd2740725 GIT binary patch literal 1724 zcmXYx4^$IJ9>*7`-U4dz4u}8(R;8 zJwsZ!nuXIgo+9xSS+E{kOU0FfZjj0X)eaz-MXC(7lU>LSgl@LcrZLVZdxR>OgyY8OBFi(B*_8svEA*E88n3zZqgifc+ zc4IQQk-XXD#%wa%&2Fq_v)$^3v|6RWL6TO|jh!SNlG*K&Bph}-C7Ht^NrG}XXorJw zP%K4Y6oFH2Xo_MenxzSx)?l>8kJhcGDF^L_p`|>{(G16Ea7KqQI-D`c7^%Pw%Sd?! zFf3rRG1e5pnq;g=$yy1PC0REd%L2}fb5@*VNsgsCH-O`KU{?Z%5>N)fm;pxvjs-xH z1P=fNKma`C%{WhDJZI&BQ~-Ehk_6;=0a|fL;*i!sRtY%+X&AZ@AVh*4 z7aX`iYXn9ouo{8Q7C46hC;`v{ZxEnefYMshR6&5EATWX(DL{hA3nCI7GLgbXN+!~n z$VgI%EGDuMBC8ZRg~(|{fQi6Q1PT$DL>?1)oya>xNQsaYAtyp!{NK8w8zf4%>%b5t zDFRV4L}Q4-5GzT8I1F(z#3>QL5Wo>wjervI7~*A!mx64>lZdAg#1O<0R3fNB(25|1 z1Plo)kYGZB83|ICM#uV5u*R^jUhxJq(O*6h(U;#j^o^1WRsqthmlaf zg>8mInHc8vOckfxQ9SG#u%rgX2RkPlQbx`vpWFKI=*k9}CBXaXg2@d^nPtZ5`^f3# zTR4#FQLU!6F_WDp;=APfUEg`8chvJUk29t?&pTPRg(_c5^@k@z`*Mn34f&|MV%?sL zyHZNhq6TGKgEp)&tcn?Z_H>c)`oZhRV#gb+QdM9;d#yVbtyLc(cPQ)$+s98sdu>?d zK4QLXAch#q%l*o9{OfgfFQ=sTegJE2R>texnon)*E#p);Db}2WI6BE z=)2!rVw-vTl#A%UXt}tnr_|3gZe+AHyk%mW{&m;Ig0}wq4UU`Nj#sw!41F!kO%N|u ze{$&Od9C}?FQer#fz{y}${xSIHAu8A{Wdz|*GJn6r`9Wt!1qq%1#3~RX6ZX0n+qRW z&IC_fX>UJNTKd^?pTn`Scw{)OpYELqo9Uf0KD)Iuv(OF#eO9IP1Zv96g&P~oZD;Ip z*%J@@eigS6NonNgCDE7i?hl(|h@;)i+q;I#VP=gY;%45X>NkG=A!Otq|Cye-+ftpQ z8Cmh*R_BQ#@5?J(d-Y!_BcZXruCA`3AtEho!-3j_!O5BHpMIf#Y4c}tCkh~zVWL6th4(l z>7O&Is9@eoDbQ-QdcEFgq}$qpf&%at*6k^4v4YB$$L$@vZ(d115W1uT$*(9pc7Hrt zmi$5M!0M?!|BYY3yO+VJ{AO=Qc3;E1W(z30eL8fpYVXaSY`*D)@l5;6dEd#*nW1{@T|M^=CAn%JQEL bnElPpd&c=yYtI);Ujn9zSI0HSYRmr%eQVWq literal 0 HcmV?d00001 diff --git a/pics/custom/smooth_scale.png b/pics/custom/smooth_scale.png new file mode 100644 index 0000000000000000000000000000000000000000..e2b590c982dc8f5f0b330b8ebe712d1f5e8a6d38 GIT binary patch literal 5097 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 Jmvv4FO#r`lRkZ*B literal 0 HcmV?d00001 diff --git a/pics/custom/tool_spraycan_9x9.png b/pics/custom/tool_spraycan_9x9.png new file mode 100644 index 0000000000000000000000000000000000000000..06c8639ef59741dd8e7584134b1379fe84739381 GIT binary patch literal 92 zcmeAS@N?(olHy`uVBq!ia0vp^oFL2rBp8nViaZ0Pls#P>LpWqv4>B?Wc?}c(%@*eW2gm)GFJ0-+fo1q`09elF{r5}E)wuoRL2 literal 0 HcmV?d00001 diff --git a/pixmapfx/kpPixmapFX.h b/pixmapfx/kpPixmapFX.h new file mode 100644 index 0000000..7c4f3b6 --- /dev/null +++ b/pixmapfx/kpPixmapFX.h @@ -0,0 +1,249 @@ + +// REFACTOR: Split this class into one for each distinct functionality category +// (e.g. effects, mask operations)? + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef KP_PIXMAP_FX_H +#define KP_PIXMAP_FX_H + + +#include +#include + +#include + +#include "imagelib/kpColor.h" + + +class QColor; +class QImage; +class QTransform; +class QPen; +class QImage; +class QPoint; +class QPolygon; +class QRect; + + + + +// +// QPixmap (view) Manipulation. +// +// Anything that is supposed to be manipulating the document contents +// (i.e. kpImage), should be moved to kpPainter. +// +// kpPainter uses us for its Qt backend but we don't use kpPainter. +// TODO: We should not use kpColor nor kpImage for the same reason. +// +class kpPixmapFX +{ +// +// Get/Set Parts of Pixmap +// + +public: + + // + // Returns the pixel and mask data found at the in . + // + static QImage getPixmapAt (const QImage &pm, const QRect &rect); + + // + // Sets the pixel and mask data at in <*destPixmapPtr> + // to . Neither 's width nor height are allowed + // to be bigger than 's (you can't copy more than you have). + // On the other hand, you can copy less than the size of + // - no scaling is done. + // + static void setPixmapAt (QImage *destPixmapPtr, const QRect &destRect, + const QImage &srcPixmap); + + // + // Sets the pixel and mask data at the rectangle in <*destPixmapPtr>, + // with the top-left and dimensions , + // to . + // + static void setPixmapAt (QImage *destPixmapPtr, const QPoint &destAt, + const QImage &srcPixmap); + static void setPixmapAt (QImage *destPixmapPtr, int destX, int destY, + const QImage &srcPixmap); + + // + // Draws on top of <*destPixmapPtr> at . + // The mask of <*destPixmapPtr> is adjusted so that all opaque + // pixels in will be opaque in <*destPixmapPtr>. + // + static void paintPixmapAt (QImage *destPixmapPtr, const QPoint &destAt, + const QImage &srcPixmap); + static void paintPixmapAt (QImage *destPixmapPtr, int destX, int destY, + const QImage &srcPixmap); + + // + // Returns the colour of the pixel at in . + // If the pixel is transparent, a value is returned such that + // kpTool::isColorTransparent() will return true. + // + static kpColor getColorAtPixel (const QImage &pm, const QPoint &at); + static kpColor getColorAtPixel (const QImage &pm, int x, int y); + +// +// Transforms +// + +public: + + // + // Resizes an image to the given width and height, + // filling any new areas with . + // + static void resize (QImage *destPtr, int w, int h, + const kpColor &backgroundColor); + static QImage resize (const QImage &pm, int w, int h, + const kpColor &backgroundColor); + + // + // Scales an image to the given width and height. + // If is true, a smooth scale will be used. + // + static void scale (QImage *destPtr, int w, int h, bool pretty = false); + static QImage scale (const QImage &pm, int w, int h, bool pretty = false); + + + // The minimum difference between 2 angles (in degrees) such that they are + // considered different. This gives you at least enough precision to + // rotate an image whose width <= 10000 such that its height increases + // by just 1 (and similarly with height <= 10000 and width). + // + // Currently used for skew & rotate operations. + static const double AngleInDegreesEpsilon; + + + // + // Skews an image. + // + // horizontal angle clockwise (-90 < x < 90) + // vertical angle clockwise (-90 < x < 90) + // color to fill new areas with + // if > 0, the desired width of the resultant pixmap + // if > 0, the desired height of the resultant pixmap + // + // Using & to generate preview pixmaps is + // significantly more efficient than skewing and then scaling yourself. + // + static QTransform skewMatrix (int width, int height, double hangle, double vangle); + static QTransform skewMatrix (const QImage &pixmap, double hangle, double vangle); + + static void skew (QImage *destPixmapPtr, double hangle, double vangle, + const kpColor &backgroundColor, + int targetWidth = -1, int targetHeight = -1); + static QImage skew (const QImage &pm, double hangle, double vangle, + const kpColor &backgroundColor, + int targetWidth = -1, int targetHeight = -1); + + // + // Rotates an image. + // + // clockwise angle to rotate by + // color to fill new areas with + // if > 0, the desired width of the resultant pixmap + // if > 0, the desired height of the resultant pixmap + // + // Using & to generate preview pixmaps is + // significantly more efficient than rotating and then scaling yourself. + // + static QTransform rotateMatrix (int width, int height, double angle); + static QTransform rotateMatrix (const QImage &pixmap, double angle); + + static bool isLosslessRotation (double angle); + + static void rotate (QImage *destPixmapPtr, double angle, + const kpColor &backgroundColor, + int targetWidth = -1, int targetHeight = -1); + static QImage rotate (const QImage &pm, double angle, + const kpColor &backgroundColor, + int targetWidth = -1, int targetHeight = -1); + +// +// Drawing Shapes +// + +public: + + // Returns a pen suitable for drawing a rectangle with 90 degree + // corners ("MiterJoin"). This is necessary since Qt4 defaults to + // "BevelJoin". is passed straight to QPen without modification. + static QPen QPainterDrawRectPen (const QColor &color, int qtWidth); + + // Returns a pen suitable for drawing lines / polylines / polygons / + // curves with rounded corners. This is necessary since Qt4 defaults + // to square corners ("SquareCap") and "BevelJoin". + // is passed straight to QPen without modification. + static QPen QPainterDrawLinePen (const QColor &color, int qtWidth); + + static bool Only1PixelInPointArray(const QPolygon &points); + + // Draws a line from (x1,y1) to (x2,y2) onto , with + // and . The corners are rounded and centred at those + // coordinates so if > 1, the line is likely to extend past + // a rectangle with those corners. + // + // If is valid, it draws a stippled line alternating + // between long strips of and short strips of . + static void drawPolyline (QImage *image, + const QPolygon &points, + const kpColor &color, int penWidth, + const kpColor &stippleColor = kpColor::Invalid); + + // = shape completed else drawing but haven't finalised. + // If not , the edge that would form the closure, if the + // shape were finalised now, is highlighted specially. + // + // Odd-even fill. + static void drawPolygon (QImage *image, + const QPolygon &points, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor = kpColor::Invalid, + bool isFinal = true, + const kpColor &fStippleColor = kpColor::Invalid); + + static void fillRect (QImage *image, + int x, int y, int width, int height, + const kpColor &color, + const kpColor &stippleColor = kpColor::Invalid); + + static void drawStippleRect(QImage *image, + int x, int y, int width, int height, + const kpColor &fcolor, + const kpColor &fStippleColor); + +}; + + +#endif // KP_PIXMAP_FX_H diff --git a/pixmapfx/kpPixmapFX_DrawShapes.cpp b/pixmapfx/kpPixmapFX_DrawShapes.cpp new file mode 100644 index 0000000..6e164e1 --- /dev/null +++ b/pixmapfx/kpPixmapFX_DrawShapes.cpp @@ -0,0 +1,285 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_PIXMAP_FX 0 + + +#include "kpPixmapFX.h" + + +#include +#include +#include +#include +#include + +#include "kpLogCategories.h" + +#include "layers/selections/kpAbstractSelection.h" +#include "imagelib/kpColor.h" +#include "kpDefs.h" + +//--------------------------------------------------------------------- + +// Returns whether there is only 1 distinct point in . +bool kpPixmapFX::Only1PixelInPointArray (const QPolygon &points) +{ + if (points.count () == 0) { + return false; + } + + for (int i = 1; i < static_cast (points.count ()); i++) + { + if (points [i] != points [0]) { + return false; + } + } + + return true; +} + +//--------------------------------------------------------------------- + +// Warp the given from 1 to 0. +// This is not always done (specifically if ) because +// width 0 sometimes looks worse. +// +// Qt lines of width 1 look like they have a width between 1-2 i.e.: +// +// # +// ## +// # +// # +// +// compared to Qt's special "width 0" which just means a "proper" width 1: +// +// # +// # +// # +// # +// +static int WidthToQPenWidth (int width, bool drawingEllipse = false) +{ + if (width == 1) + { + // 3x10 ellipse with Qt width 0 looks like rectangle. + // Therefore, do not apply this 1 -> 0 transformations for ellipses. + if (!drawingEllipse) + { + // Closer to looking width 1, for lines at least. + return 0; + } + } + + return width; +} + +//--------------------------------------------------------------------- + +static void QPainterSetPenWithStipple (QPainter *p, + const kpColor &fColor, + int penWidth, + const kpColor &fStippleColor = kpColor::Invalid, + bool isEllipseLike = false) +{ + if (!fStippleColor.isValid ()) + { + p->setPen ( + kpPixmapFX::QPainterDrawLinePen ( + fColor.toQColor(), + ::WidthToQPenWidth (penWidth, isEllipseLike))); + } + else + { + QPen usePen = kpPixmapFX::QPainterDrawLinePen ( + fColor.toQColor(), + ::WidthToQPenWidth (penWidth, isEllipseLike)); + usePen.setStyle (Qt::DashLine); + p->setPen (usePen); + + p->setBackground (fStippleColor.toQColor()); + p->setBackgroundMode (Qt::OpaqueMode); + } +} + +//--------------------------------------------------------------------- + +// public static +QPen kpPixmapFX::QPainterDrawRectPen (const QColor &color, int qtWidth) +{ + return QPen (color, qtWidth, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin); +} + +//--------------------------------------------------------------------- + +// public static +QPen kpPixmapFX::QPainterDrawLinePen (const QColor &color, int qtWidth) +{ + return QPen (color, qtWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); +} + +//--------------------------------------------------------------------- +// +// drawPolyline() / drawLine() +// + +// public static +void kpPixmapFX::drawPolyline (QImage *image, + const QPolygon &points, + const kpColor &color, int penWidth, + const kpColor &stippleColor) +{ + QPainter painter(image); + + ::QPainterSetPenWithStipple(&painter, + color, penWidth, + stippleColor); + + // Qt bug: single point doesn't show up depending on penWidth. + if (Only1PixelInPointArray(points)) + { + #if DEBUG_KP_PIXMAP_FX + qCDebug(kpLogPixmapfx) << "\tinvoking single point hack"; + #endif + painter.drawPoint(points[0]); + return; + } + + painter.drawPolyline(points); +} + +//--------------------------------------------------------------------- +// +// drawPolygon() +// + +// public static +void kpPixmapFX::drawPolygon (QImage *image, + const QPolygon &points, + const kpColor &fcolor, int penWidth, + const kpColor &bcolor, + bool isFinal, + const kpColor &fStippleColor) +{ + QPainter p(image); + + ::QPainterSetPenWithStipple (&p, + fcolor, penWidth, + fStippleColor); + + if (bcolor.isValid ()) { + p.setBrush (QBrush (bcolor.toQColor())); + } + // HACK: seems to be needed if set_Pen_(Qt::color0) else fills with Qt::color0. + else { + p.setBrush (Qt::NoBrush); + } + + // Qt bug: single point doesn't show up depending on penWidth. + if (Only1PixelInPointArray (points)) + { + #if DEBUG_KP_PIXMAP_FX + qCDebug(kpLogPixmapfx) << "\tinvoking single point hack"; + #endif + p.drawPoint(points [0]); + return; + } + + // TODO: why aren't the ends rounded? + p.drawPolygon(points, Qt::OddEvenFill); + + if ( isFinal ) { + return; + } + + if ( points.count() <= 2 ) { + return; + } + + p.setCompositionMode(QPainter::RasterOp_SourceXorDestination); + p.setPen(QPen(Qt::white)); + p.drawLine(points[0], points[points.count() - 1]); +} + +//--------------------------------------------------------------------- +// public static + +void kpPixmapFX::fillRect (QImage *image, + int x, int y, int width, int height, + const kpColor &color, + const kpColor &stippleColor) +{ + QPainter painter(image); + + if (!stippleColor.isValid ()) + { + painter.fillRect (x, y, width, height, color.toQColor()); + } + else + { + const int StippleSize = 4; + + painter.setClipRect (x, y, width, height); + + for (int dy = 0; dy < height; dy += StippleSize) + { + for (int dx = 0; dx < width; dx += StippleSize) + { + const bool parity = ((dy + dx) / StippleSize) % 2; + + kpColor useColor; + if (!parity) { + useColor = color; + } + else { + useColor = stippleColor; + } + + painter.fillRect (x + dx, y + dy, StippleSize, StippleSize, useColor.toQColor()); + } + } + + } +} + +//--------------------------------------------------------------------- + +void kpPixmapFX::drawStippleRect(QImage *image, + int x, int y, int width, int height, + const kpColor &fColor, + const kpColor &fStippleColor) +{ + QPainter painter(image); + + painter.setPen(QPen(fColor.toQColor(), 1, Qt::DashLine)); + painter.setBackground(fStippleColor.toQColor()); + painter.setBackgroundMode(Qt::OpaqueMode); + painter.drawRect(x, y, width - 1, height - 1); +} + +//--------------------------------------------------------------------- diff --git a/pixmapfx/kpPixmapFX_GetSetPixmapParts.cpp b/pixmapfx/kpPixmapFX_GetSetPixmapParts.cpp new file mode 100644 index 0000000..2c28747 --- /dev/null +++ b/pixmapfx/kpPixmapFX_GetSetPixmapParts.cpp @@ -0,0 +1,142 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_PIXMAP_FX 0 + + +#include "kpPixmapFX.h" + + +#include +#include +#include +#include + +#include "kpLogCategories.h" + +#include "imagelib/kpColor.h" + +//--------------------------------------------------------------------- + +// public static +QImage kpPixmapFX::getPixmapAt (const QImage &image, const QRect &rect) +{ + return image.copy(rect); +} + +//--------------------------------------------------------------------- + +// public static +void kpPixmapFX::setPixmapAt(QImage *destPtr, const QRect &destRect, + const QImage &src) +{ +#if DEBUG_KP_PIXMAP_FX && 1 + qCDebug(kpLogPixmapfx) << "kpPixmapFX::setPixmapAt(destPixmap->rect=" + << destPtr->rect () + << ",destRect=" + << destRect + << ",src.rect=" + << src.rect () + << ")"; +#endif + + Q_ASSERT (destPtr); + + // You cannot copy more than what you have. + Q_ASSERT (destRect.width () <= src.width () && + destRect.height () <= src.height ()); + + QPainter painter(destPtr); + // destination shall be source only + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.drawImage(destRect.topLeft(), src, QRect(0, 0, destRect.width(), destRect.height())); +} + +//--------------------------------------------------------------------- + +// public static +void kpPixmapFX::setPixmapAt (QImage *destPtr, const QPoint &destAt, + const QImage &src) +{ + kpPixmapFX::setPixmapAt (destPtr, + QRect (destAt.x (), destAt.y (), + src.width (), src.height ()), + src); +} + +//--------------------------------------------------------------------- + +// public static +void kpPixmapFX::setPixmapAt (QImage *destPtr, int destX, int destY, + const QImage &src) +{ + kpPixmapFX::setPixmapAt (destPtr, QPoint (destX, destY), src); +} + +//--------------------------------------------------------------------- + +// public static +void kpPixmapFX::paintPixmapAt (QImage *destPtr, const QPoint &destAt, + const QImage &src) +{ + // draw image with SourceOver composition mode + QPainter painter(destPtr); + painter.drawImage(destAt, src); +} + +//--------------------------------------------------------------------- + +// public static +void kpPixmapFX::paintPixmapAt (QImage *destPtr, int destX, int destY, + const QImage &src) +{ + kpPixmapFX::paintPixmapAt(destPtr, QPoint (destX, destY), src); +} + +//--------------------------------------------------------------------- + +// public static +kpColor kpPixmapFX::getColorAtPixel (const QImage &img, const QPoint &at) +{ + if (!img.valid (at.x (), at.y ())) { + return kpColor::Invalid; + } + + QRgb rgba = img.pixel(at); + return kpColor (rgba); +} + +//--------------------------------------------------------------------- + +// public static +kpColor kpPixmapFX::getColorAtPixel (const QImage &img, int x, int y) +{ + return kpPixmapFX::getColorAtPixel (img, QPoint (x, y)); +} + +//--------------------------------------------------------------------- diff --git a/pixmapfx/kpPixmapFX_Transforms.cpp b/pixmapfx/kpPixmapFX_Transforms.cpp new file mode 100644 index 0000000..083c66d --- /dev/null +++ b/pixmapfx/kpPixmapFX_Transforms.cpp @@ -0,0 +1,669 @@ + +/* + Copyright (c) 2003-2007 Clarence Dang + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define DEBUG_KP_PIXMAP_FX 0 + + +#include "kpPixmapFX.h" + +#include + +#include +#include +#include +#include + +#include "kpLogCategories.h" + +#include "layers/selections/kpAbstractSelection.h" +#include "imagelib/kpColor.h" +#include "kpDefs.h" + +//--------------------------------------------------------------------- + +// public static +void kpPixmapFX::resize (QImage *destPtr, int w, int h, + const kpColor &backgroundColor) +{ +#if DEBUG_KP_PIXMAP_FX && 1 + qCDebug(kpLogPixmapfx) << "kpPixmapFX::resize()"; +#endif + + if (!destPtr) { + return; + } + + const int oldWidth = destPtr->width (); + const int oldHeight = destPtr->height (); + + if (w == oldWidth && h == oldHeight) { + return; + } + + QImage newImage (w, h, QImage::Format_ARGB32_Premultiplied); + + // Would have new undefined areas? + if (w > oldWidth || h > oldHeight) { + newImage.fill (backgroundColor.toQRgb ()); + } + + // Copy over old pixmap. + QPainter painter(&newImage); + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.drawImage(0, 0, *destPtr); + painter.end(); + + // Replace pixmap with new one. + *destPtr = newImage; +} + +//--------------------------------------------------------------------- + +// public static +QImage kpPixmapFX::resize (const QImage &image, int w, int h, + const kpColor &backgroundColor) +{ + QImage ret = image; + kpPixmapFX::resize (&ret, w, h, backgroundColor); + return ret; +} + +//--------------------------------------------------------------------- + +// public static +void kpPixmapFX::scale (QImage *destPtr, int w, int h, bool pretty) +{ + if (!destPtr) { + return; + } + + *destPtr = kpPixmapFX::scale (*destPtr, w, h, pretty); +} + +//--------------------------------------------------------------------- + +// public static +QImage kpPixmapFX::scale (const QImage &image, int w, int h, bool pretty) +{ +#if DEBUG_KP_PIXMAP_FX && 0 + qCDebug(kpLogPixmapfx) << "kpPixmapFX::scale(oldRect=" << image.rect () + << ",w=" << w + << ",h=" << h + << ",pretty=" << pretty + << ")"; +#endif + + if (w == image.width () && h == image.height ()) { + return image; + } + + return image.scaled(w, h, Qt::IgnoreAspectRatio, + pretty ? Qt::SmoothTransformation : Qt::FastTransformation); +} + +//--------------------------------------------------------------------- + +// public static +const double kpPixmapFX::AngleInDegreesEpsilon = + qRadiansToDegrees (std::tan (1.0 / 10000.0)) + / (2.0/*max error allowed*/ * 2.0/*for good measure*/); + + +static void MatrixDebug (const QString& matrixName, const QTransform &matrix, + int srcPixmapWidth = -1, int srcPixmapHeight = -1) +{ +#if DEBUG_KP_PIXMAP_FX + const int w = srcPixmapWidth, h = srcPixmapHeight; + + qCDebug(kpLogPixmapfx) << matrixName << "=" << matrix; + // Sometimes this precision lets us see unexpected rounding errors. + fprintf (stderr, "m11=%.24f m12=%.24f m21=%.24f m22=%.24f dx=%.24f dy=%.24f\n", + matrix.m11 (), matrix.m12 (), + matrix.m21 (), matrix.m22 (), + matrix.dx (), matrix.dy ()); + if (w > 0 && h > 0) + { + qCDebug(kpLogPixmapfx) << "(0,0) ->" << matrix.map (QPoint (0, 0)); + qCDebug(kpLogPixmapfx) << "(w-1,0) ->" << matrix.map (QPoint (w - 1, 0)); + qCDebug(kpLogPixmapfx) << "(0,h-1) ->" << matrix.map (QPoint (0, h - 1)); + qCDebug(kpLogPixmapfx) << "(w-1,h-1) ->" << matrix.map (QPoint (w - 1, h - 1)); + } + +#else + + Q_UNUSED (matrixName); + Q_UNUSED (matrix); + Q_UNUSED (srcPixmapWidth); + Q_UNUSED (srcPixmapHeight); + +#endif // DEBUG_KP_PIXMAP_FX +} + +//--------------------------------------------------------------------- + +// Theoretically, this should act the same as QPixmap::trueMatrix() but +// it doesn't. As an example, if you rotate tests/transforms.png by 90 +// degrees clockwise, this returns the correct of 26 but +// QPixmap::trueMatrix() returns 27. +// +// You should use the returned matrix to map points accurately (e.g. selection +// borders). For QPainter::drawPixmap()/drawImage() + setWorldMatrix() +// rendering accuracy, pass the returned matrix through QPixmap::trueMatrix() +// and use that. +// +// TODO: If you put the flipMatrix() of tests/transforms.png through this, +// the output is the same as QPixmap::trueMatrix(): is one off +// (dy=27 instead of 26). +// SYNC: I bet this is a Qt4 bug. +static QTransform MatrixWithZeroOrigin (const QTransform &matrix, int width, int height) +{ +#if DEBUG_KP_PIXMAP_FX + qCDebug(kpLogPixmapfx) << "matrixWithZeroOrigin(w=" << width << ",h=" << height << ")"; + qCDebug(kpLogPixmapfx) << "\tmatrix: m11=" << matrix.m11 () + << "m12=" << matrix.m12 () + << "m21=" << matrix.m21 () + << "m22=" << matrix.m22 () + << "dx=" << matrix.dx () + << "dy=" << matrix.dy (); +#endif + + QRect mappedRect = matrix.mapRect (QRect (0, 0, width, height)); +#if DEBUG_KP_PIXMAP_FX + qCDebug(kpLogPixmapfx) << "\tmappedRect=" << mappedRect; +#endif + + QTransform translatedMatrix ( + matrix.m11 (), matrix.m12 (), + matrix.m21 (), matrix.m22 (), + matrix.dx () - mappedRect.left (), matrix.dy () - mappedRect.top ()); + +#if DEBUG_KP_PIXMAP_FX + qCDebug(kpLogPixmapfx) << "\treturning" << translatedMatrix; + qCDebug(kpLogPixmapfx) << "(0,0) ->" << translatedMatrix.map (QPoint (0, 0)); + qCDebug(kpLogPixmapfx) << "(w-1,0) ->" << translatedMatrix.map (QPoint (width - 1, 0)); + qCDebug(kpLogPixmapfx) << "(0,h-1) ->" << translatedMatrix.map (QPoint (0, height - 1)); + qCDebug(kpLogPixmapfx) << "(w-1,h-1) ->" << translatedMatrix.map (QPoint (width - 1, height - 1)); +#endif + + return translatedMatrix; +} + +//--------------------------------------------------------------------- + +static double TrueMatrixEpsilon = 0.000001; + +// An attempt to reverse tiny rounding errors introduced by QPixmap::trueMatrix() +// when skewing tests/transforms.png by 45% horizontally (with TransformPixmap() +// using a QPixmap painter, prior to the 2007-10-09 change -- did not test after +// the change). +// Unfortunately, this does not work enough to stop the rendering errors +// that follow. But it was worth a try and might still help us given the +// sometimes excessive aliasing QPainter::draw{Pixmap,Image}() gives us, when +// QPainter::SmoothPixmapTransform is disabled. +static double TrueMatrixFixInts (double x) +{ + if (std::fabs (x - qRound (x)) < TrueMatrixEpsilon) { + return qRound (x); + } + + return x; +} + +//--------------------------------------------------------------------- + +static QTransform TrueMatrix (const QTransform &matrix, int srcPixmapWidth, int srcPixmapHeight) +{ + ::MatrixDebug (QStringLiteral("TrueMatrix(): org"), matrix); + + const QTransform truMat = QPixmap::trueMatrix (matrix, srcPixmapWidth, srcPixmapHeight); + ::MatrixDebug (QStringLiteral("TrueMatrix(): passed through QPixmap::trueMatrix()"), truMat); + + const QTransform retMat ( + ::TrueMatrixFixInts (truMat.m11 ()), + ::TrueMatrixFixInts (truMat.m12 ()), + ::TrueMatrixFixInts (truMat.m21 ()), + ::TrueMatrixFixInts (truMat.m22 ()), + ::TrueMatrixFixInts (truMat.dx ()), + ::TrueMatrixFixInts (truMat.dy ())); + ::MatrixDebug (QStringLiteral("TrueMatrix(): fixed ints"), retMat); + + return retMat; +} + +//--------------------------------------------------------------------- + +// Like QPixmap::transformed() but fills new areas with +// (unless is invalid) and works around internal QTransform +// floating point -> integer oddities, that would otherwise give fatally +// incorrect results. If you don't believe me on this latter point, compare +// QPixmap::transformed() to us using a flip matrix or a rotate-by-multiple-of-90 +// matrix on tests/transforms.png -- QPixmap::transformed()'s output is 1 +// pixel too high or low depending on whether the matrix is passed through +// QPixmap::trueMatrix(). +// +// Use and to specify the intended output size +// of the pixmap. -1 if don't care. +static QImage TransformPixmap (const QImage &pm, const QTransform &transformMatrix_, + const kpColor &backgroundColor, + int targetWidth, int targetHeight) +{ + QTransform transformMatrix = transformMatrix_; + +#if DEBUG_KP_PIXMAP_FX && 1 + qCDebug(kpLogPixmapfx) << "kppixmapfx.cpp: TransformPixmap(pm.size=" << pm.size () + << ",targetWidth=" << targetWidth + << ",targetHeight=" << targetHeight + << ")"; +#endif + + QRect newRect = transformMatrix.mapRect (pm.rect ()); +#if DEBUG_KP_PIXMAP_FX && 1 + qCDebug(kpLogPixmapfx) << "\tmappedRect=" << newRect; + +#endif + + QTransform scaleMatrix; + if (targetWidth > 0 && targetWidth != newRect.width ()) + { + #if DEBUG_KP_PIXMAP_FX && 1 + qCDebug(kpLogPixmapfx) << "\tadjusting for targetWidth"; + #endif + scaleMatrix.scale (double (targetWidth) / double (newRect.width ()), 1); + } + + if (targetHeight > 0 && targetHeight != newRect.height ()) + { + #if DEBUG_KP_PIXMAP_FX && 1 + qCDebug(kpLogPixmapfx) << "\tadjusting for targetHeight"; + #endif + scaleMatrix.scale (1, double (targetHeight) / double (newRect.height ())); + } + + if (!scaleMatrix.isIdentity ()) + { + #if DEBUG_KP_PIXMAP_FX && 1 + // TODO: What is going on here??? Why isn't matrix * working properly? + QTransform wrongMatrix = transformMatrix * scaleMatrix; + QTransform oldHat = transformMatrix; + + if (targetWidth > 0 && targetWidth != newRect.width ()) { + oldHat.scale (double (targetWidth) / double (newRect.width ()), 1); + } + if (targetHeight > 0 && targetHeight != newRect.height ()) { + oldHat.scale (1, double (targetHeight) / double (newRect.height ())); + } + + QTransform altHat = transformMatrix; + + altHat.scale ((targetWidth > 0 && targetWidth != newRect.width ()) ? double (targetWidth) / double (newRect.width ()) : 1, + (targetHeight > 0 && targetHeight != newRect.height ()) ? double (targetHeight) / double (newRect.height ()) : 1); + QTransform correctMatrix = scaleMatrix * transformMatrix; + + qCDebug(kpLogPixmapfx) << "\tsupposedlyWrongMatrix: m11=" << wrongMatrix.m11 () // <<<---- this is the correct matrix??? + << " m12=" << wrongMatrix.m12 () + << " m21=" << wrongMatrix.m21 () + << " m22=" << wrongMatrix.m22 () + << " dx=" << wrongMatrix.dx () + << " dy=" << wrongMatrix.dy () + << " rect=" << wrongMatrix.mapRect (pm.rect ()) + << "\n" + << "\ti_used_to_use_thisMatrix: m11=" << oldHat.m11 () + << " m12=" << oldHat.m12 () + << " m21=" << oldHat.m21 () + << " m22=" << oldHat.m22 () + << " dx=" << oldHat.dx () + << " dy=" << oldHat.dy () + << " rect=" << oldHat.mapRect (pm.rect ()) + << "\n" + << "\tabove but scaled at the same time: m11=" << altHat.m11 () + << " m12=" << altHat.m12 () + << " m21=" << altHat.m21 () + << " m22=" << altHat.m22 () + << " dx=" << altHat.dx () + << " dy=" << altHat.dy () + << " rect=" << altHat.mapRect (pm.rect ()) + << "\n" + << "\tsupposedlyCorrectMatrix: m11=" << correctMatrix.m11 () + << " m12=" << correctMatrix.m12 () + << " m21=" << correctMatrix.m21 () + << " m22=" << correctMatrix.m22 () + << " dx=" << correctMatrix.dx () + << " dy=" << correctMatrix.dy () + << " rect=" << correctMatrix.mapRect (pm.rect ()); + #endif + + transformMatrix = transformMatrix * scaleMatrix; + + newRect = transformMatrix.mapRect (pm.rect ()); + #if DEBUG_KP_PIXMAP_FX && 1 + qCDebug(kpLogPixmapfx) << "\tnewRect after targetWidth,targetHeight adjust=" << newRect; + #endif + } + + + ::MatrixDebug (QStringLiteral("TransformPixmap(): before trueMatrix"), transformMatrix, + pm.width (), pm.height ()); +#if DEBUG_KP_PIXMAP_FX && 1 + QMatrix oldMatrix = transformMatrix; +#endif + + // Translate the matrix to account for Qt rounding errors, + // so that flipping (if it used this method) and rotating by a multiple + // of 90 degrees actually work as expected (try tests/transforms.png). + // + // SYNC: This was not required with Qt3 so we are actually working + // around a Qt4 bug/feature. + // + // COMPAT: Qt4's rendering with a matrix enabled is low quality anyway + // but does this reduce quality even further? + // + // With or without it, skews by 45 degrees with the QImage + // painter below look bad (with it, you get an extra transparent + // line on the right; without, you get only about 1/4 of a source + // line on the left). In Qt3, with TrueMatrix(), the source + // image is translated 1 pixel off the destination image. + // + // Also, if you skew a rectangular selection, the skewed selection + // border does not line up with the skewed image data. + // TODO: do we need to pass through this new matrix? + transformMatrix = ::TrueMatrix (transformMatrix, + pm.width (), pm.height ()); + +#if DEBUG_KP_PIXMAP_FX && 1 + qCDebug(kpLogPixmapfx) << "trueMatrix changed matrix?" << (oldMatrix == transformMatrix); +#endif + ::MatrixDebug (QStringLiteral("TransformPixmap(): after trueMatrix"), transformMatrix, + pm.width (), pm.height ()); + + + QImage newQImage (targetWidth > 0 ? targetWidth : newRect.width (), + targetHeight > 0 ? targetHeight : newRect.height (), + QImage::Format_ARGB32_Premultiplied); + + if ((targetWidth > 0 && targetWidth != newRect.width ()) || + (targetHeight > 0 && targetHeight != newRect.height ())) + { + #if DEBUG_KP_PIXMAP_FX && 1 + qCDebug(kpLogPixmapfx) << "kppixmapfx.cpp: TransformPixmap(pm.size=" << pm.size () + << ",targetWidth=" << targetWidth + << ",targetHeight=" << targetHeight + << ") newRect=" << newRect + << " (you are a victim of rounding error)"; + #endif + } + + +#if DEBUG_KP_PIXMAP_FX && 0 + qCDebug(kpLogPixmapfx) << "\ttranslate top=" << painter.xForm (QPoint (0, 0)); + qCDebug(kpLogPixmapfx) << "\tmatrix: m11=" << painter.worldMatrix ().m11 () + << " m12=" << painter.worldMatrix ().m12 () + << " m21=" << painter.worldMatrix ().m21 () + << " m22=" << painter.worldMatrix ().m22 () + << " dx=" << painter.worldMatrix ().dx () + << " dy=" << painter.worldMatrix ().dy () + << endl; +#endif + + + // Note: Do _not_ use "p.setRenderHints (QPainter::SmoothPixmapTransform);" + // as the user does not want their image to get blurier every + // time they e.g. rotate it (especially important for multiples + // of 90 degrees but also true for every other angle). Being a + // pixel-based program, we generally like to preserve RGB values + // and avoid unnecessary blurs -- in the worst case, we'd rather + // drop pixels, than blur. + QPainter p (&newQImage); + { + // Make sure transparent pixels are drawn into the destination image. + p.setCompositionMode (QPainter::CompositionMode_Source); + + // Fill the entire new image with the background color. + if (backgroundColor.isValid ()) + { + p.fillRect (newQImage.rect (), backgroundColor.toQColor ()); + } + + p.setWorldTransform (transformMatrix); + p.drawImage (QPoint (0, 0), pm); + } + p.end (); + +#if DEBUG_KP_PIXMAP_FX && 1 + qCDebug(kpLogPixmapfx) << "Done"; +#endif + + return newQImage; +} + +//--------------------------------------------------------------------- + +// public static +QTransform kpPixmapFX::skewMatrix (int width, int height, double hangle, double vangle) +{ + if (std::fabs (hangle - 0) < kpPixmapFX::AngleInDegreesEpsilon && + std::fabs (vangle - 0) < kpPixmapFX::AngleInDegreesEpsilon) + { + return {}; + } + + + /* Diagram for completeness :) + * + * |---------- w ----------| + * (0,0) + * _ _______________________ (w,0) + * | |\~_ va | + * | | \ ~_ | + * | |ha\ ~__ | + * | \ ~__ | dy + * h | \ ~___ | + * | \ ~___ | + * | | \ ~___| (w,w*tan(va)=dy) + * | | \ * \ + * _ |________\________|_____|\ vertical shear factor + * (0,h) dx ^~_ | \ | + * | ~_ \________\________ General Point (x,y) V + * | ~__ \ Skewed Point (x + y*tan(ha),y + x*tan(va)) + * (h*tan(ha)=dx,h) ~__ \ ^ + * ~___ \ | + * ~___ \ horizontal shear factor + * Key: ~___\ + * ha = hangle (w + h*tan(ha)=w+dx,h + w*tan(va)=w+dy) + * va = vangle + * + * Skewing really just twists a rectangle into a parallelogram. + * + */ + + //QTransform matrix (1, tan (KP_DEGREES_TO_RADIANS (vangle)), tan (KP_DEGREES_TO_RADIANS (hangle)), 1, 0, 0); + // I think this is clearer than above :) + QTransform matrix; + matrix.shear (std::tan (qDegreesToRadians (hangle)), + std::tan (qDegreesToRadians (vangle))); + + return ::MatrixWithZeroOrigin (matrix, width, height); +} + +//--------------------------------------------------------------------- + +// public static +QTransform kpPixmapFX::skewMatrix (const QImage &pixmap, double hangle, double vangle) +{ + return kpPixmapFX::skewMatrix (pixmap.width (), pixmap.height (), hangle, vangle); +} + +//--------------------------------------------------------------------- + + +// public static +void kpPixmapFX::skew (QImage *destPtr, double hangle, double vangle, + const kpColor &backgroundColor, + int targetWidth, int targetHeight) +{ + if (!destPtr) { + return; + } + + *destPtr = kpPixmapFX::skew (*destPtr, hangle, vangle, + backgroundColor, + targetWidth, targetHeight); +} + +//--------------------------------------------------------------------- + +// public static +QImage kpPixmapFX::skew (const QImage &pm, double hangle, double vangle, + const kpColor &backgroundColor, + int targetWidth, int targetHeight) +{ +#if DEBUG_KP_PIXMAP_FX + qCDebug(kpLogPixmapfx) << "kpPixmapFX::skew() pm.width=" << pm.width () + << " pm.height=" << pm.height () + << " hangle=" << hangle + << " vangle=" << vangle + << " targetWidth=" << targetWidth + << " targetHeight=" << targetHeight; +#endif + + if (std::fabs (hangle - 0) < kpPixmapFX::AngleInDegreesEpsilon && + std::fabs (vangle - 0) < kpPixmapFX::AngleInDegreesEpsilon && + (targetWidth <= 0 && targetHeight <= 0)/*don't want to scale?*/) + { + return pm; + } + + if (std::fabs (hangle) > 90 - kpPixmapFX::AngleInDegreesEpsilon || + std::fabs (vangle) > 90 - kpPixmapFX::AngleInDegreesEpsilon) + { + qCCritical(kpLogPixmapfx) << "kpPixmapFX::skew() passed hangle and/or vangle out of range (-90 < x < 90)"; + return pm; + } + + + QTransform matrix = skewMatrix (pm, hangle, vangle); + + return ::TransformPixmap (pm, matrix, backgroundColor, targetWidth, targetHeight); +} + +//--------------------------------------------------------------------- + + +// public static +QTransform kpPixmapFX::rotateMatrix (int width, int height, double angle) +{ + if (std::fabs (angle - 0) < kpPixmapFX::AngleInDegreesEpsilon) + { + return {}; + } + + QTransform matrix; + matrix.translate (width / 2, height / 2); + matrix.rotate (angle); + + return ::MatrixWithZeroOrigin (matrix, width, height); +} + +//--------------------------------------------------------------------- + +// public static +QTransform kpPixmapFX::rotateMatrix (const QImage &pixmap, double angle) +{ + return kpPixmapFX::rotateMatrix (pixmap.width (), pixmap.height (), angle); +} + +//--------------------------------------------------------------------- + + +// public static +bool kpPixmapFX::isLosslessRotation (double angle) +{ + const double angleIn = angle; + + // Reflect angle into positive if negative + if (angle < 0) { + angle = -angle; + } + + // Remove multiples of 90 to make sure 0 <= angle <= 90 + angle -= (static_cast (angle)) / 90 * 90; + + // "Impossible" situation? + if (angle < 0 || angle > 90) + { + qCCritical(kpLogPixmapfx) << "kpPixmapFX::isLosslessRotation(" << angleIn + << ") result=" << angle; + return false; // better safe than sorry + } + + const bool ret = (angle < kpPixmapFX::AngleInDegreesEpsilon || + 90 - angle < kpPixmapFX::AngleInDegreesEpsilon); +#if DEBUG_KP_PIXMAP_FX + qCDebug(kpLogPixmapfx) << "kpPixmapFX::isLosslessRotation(" << angleIn << ")" + << " residual angle=" << angle + << " returning " << ret; +#endif + return ret; +} + +//--------------------------------------------------------------------- + + +// public static +void kpPixmapFX::rotate (QImage *destPtr, double angle, + const kpColor &backgroundColor, + int targetWidth, int targetHeight) +{ + if (!destPtr) { + return; + } + + *destPtr = kpPixmapFX::rotate (*destPtr, angle, + backgroundColor, + targetWidth, targetHeight); +} + +//--------------------------------------------------------------------- + +// public static +QImage kpPixmapFX::rotate (const QImage &pm, double angle, + const kpColor &backgroundColor, + int targetWidth, int targetHeight) +{ + if (std::fabs (angle - 0) < kpPixmapFX::AngleInDegreesEpsilon && + (targetWidth <= 0 && targetHeight <= 0)/*don't want to scale?*/) + { + return pm; + } + + + QTransform matrix = rotateMatrix (pm, angle); + + return ::TransformPixmap (pm, matrix, backgroundColor, targetWidth, targetHeight); +} + +//--------------------------------------------------------------------- diff --git a/po/ar/kolourpaint.po b/po/ar/kolourpaint.po new file mode 100644 index 0000000..1899787 --- /dev/null +++ b/po/ar/kolourpaint.po @@ -0,0 +1,2968 @@ +# translation of kolourpaint.po to Arabic +# translation of kolourpaint4.po to +# translation of kolourpaint.po to +# محمد سعد Mohamed SAAD , 2006. +# محمد مجدى Mohamed Magdy , 2007. +# Youssef Chahibi , 2007. +# zayed , 2008, 2021, 2022. +msgid "" +msgstr "" +"Project-Id-Version: kolourpaint\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2022-07-08 00:44+0000\n" +"PO-Revision-Date: 2022-01-04 17:29+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" +"X-Generator: Lokalize 21.07.70\n" +"X-Poedit-SourceCharset: utf-8\n" + +#, kde-format +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Mohamed SAAD محمد سعد,زايد السعيدي" + +#, kde-format +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "metehyi@free.fr,zayed.alsaidi@gmail.com" + +#: commands/imagelib/effects/kpEffectBalanceCommand.cpp:40 +#: dialogs/imagelib/effects/kpEffectsDialog.cpp:112 +#, kde-format +msgid "Balance" +msgstr "التوازن" + +#: commands/imagelib/effects/kpEffectBlurSharpenCommand.cpp:52 +#, kde-format +msgid "Soften" +msgstr "ليّن" + +#: commands/imagelib/effects/kpEffectBlurSharpenCommand.cpp:53 +#, kde-format +msgid "Sharpen" +msgstr "اشحذ" + +#: commands/imagelib/effects/kpEffectClearCommand.cpp:58 +#, kde-format +msgid "Clear" +msgstr "امسح" + +#: commands/imagelib/effects/kpEffectClearCommand.cpp:60 +#: commands/imagelib/effects/kpEffectCommandBase.cpp:66 +#: commands/imagelib/transforms/kpTransformFlipCommand.cpp:83 +#: commands/imagelib/transforms/kpTransformRotateCommand.cpp:74 +#: commands/imagelib/transforms/kpTransformSkewCommand.cpp:77 +#, kde-format +msgid "Selection: %1" +msgstr "المحدد: %1" + +#: commands/imagelib/effects/kpEffectEmbossCommand.cpp:42 +#: dialogs/imagelib/effects/kpEffectsDialog.cpp:113 +#, kde-format +msgid "Emboss" +msgstr "أبرز" + +#: commands/imagelib/effects/kpEffectFlattenCommand.cpp:43 +#: dialogs/imagelib/effects/kpEffectsDialog.cpp:114 +#, kde-format +msgid "Flatten" +msgstr "سطّح" + +#: commands/imagelib/effects/kpEffectGrayscaleCommand.cpp:41 +#, kde-format +msgid "Reduce to Grayscale" +msgstr "اختزل إلى تدرج الرمادي" + +#: commands/imagelib/effects/kpEffectHSVCommand.cpp:37 +#: dialogs/imagelib/effects/kpEffectsDialog.cpp:116 +#, kde-format +msgid "Hue, Saturation, Value" +msgstr "التدرج ، الإشباع ، القيمة" + +#: commands/imagelib/effects/kpEffectInvertCommand.cpp:41 +#, kde-format +msgid "Invert Colors" +msgstr "اعكس الألوان" + +#: commands/imagelib/effects/kpEffectInvertCommand.cpp:41 +#: dialogs/imagelib/effects/kpEffectsDialog.cpp:117 +#, kde-format +msgid "Invert" +msgstr "اعكس" + +#: commands/imagelib/effects/kpEffectReduceColorsCommand.cpp:54 +#, kde-format +msgid "Reduce to Monochrome (Dithered)" +msgstr "اختزل إلى أ&حادي اللون (منتشر)" + +#: commands/imagelib/effects/kpEffectReduceColorsCommand.cpp:56 +#, kde-format +msgid "Reduce to Monochrome" +msgstr "اختزل إلى أحادي اللون" + +#: commands/imagelib/effects/kpEffectReduceColorsCommand.cpp:60 +#, kde-format +msgid "Reduce to 256 Color (Dithered)" +msgstr "اختزل إلى 256 لونا (منتشر)" + +#: commands/imagelib/effects/kpEffectReduceColorsCommand.cpp:62 +#, kde-format +msgid "Reduce to 256 Color" +msgstr "اختزل إلى 256 لونا" + +#: commands/imagelib/effects/kpEffectToneEnhanceCommand.cpp:41 +#: dialogs/imagelib/effects/kpEffectsDialog.cpp:115 +#, kde-format +msgid "Histogram Equalizer" +msgstr "المدرج الإحصائي المعادل" + +#: commands/imagelib/transforms/kpTransformFlipCommand.cpp:66 +#, kde-format +msgid "Flip" +msgstr "اقلب" + +#: commands/imagelib/transforms/kpTransformFlipCommand.cpp:69 +#, kde-format +msgid "Flip horizontally and vertically" +msgstr "اقلب أفقياً أو عمودياً" + +#: commands/imagelib/transforms/kpTransformFlipCommand.cpp:71 +#, kde-format +msgid "Flip horizontally" +msgstr "اقلب أفقياً" + +#: commands/imagelib/transforms/kpTransformFlipCommand.cpp:73 +#, kde-format +msgid "Flip vertically" +msgstr "اقلب عمودياً" + +#: commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp:101 +#: commands/tools/selection/kpToolSelectionResizeScaleCommand.cpp:54 +#, kde-format +msgid "Text: Resize Box" +msgstr "النص: صندوق تغيير الحجم" + +#: commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp:107 +#, kde-format +msgid "Selection: Scale" +msgstr "التحديد: القياس" + +#: commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp:111 +#: commands/tools/selection/kpToolSelectionResizeScaleCommand.cpp:55 +#, kde-format +msgid "Selection: Smooth Scale" +msgstr "المحدد: القياس السلس" + +#: commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp:120 +#, kde-format +msgid "Resize" +msgstr "غيّر الحجم" + +#: commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp:122 +#, kde-format +msgid "Scale" +msgstr "غيّر القياس" + +#: commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp:124 +#, kde-format +msgid "Smooth Scale" +msgstr "غيّر القياس بنعومة" + +#: commands/imagelib/transforms/kpTransformRotateCommand.cpp:72 +#, kde-format +msgid "Rotate" +msgstr "أدر" + +#: commands/imagelib/transforms/kpTransformSkewCommand.cpp:75 +#, kde-format +msgid "Skew" +msgstr "انحراف" + +#: commands/kpCommandHistoryBase.cpp:437 +#, kde-format +msgid "&Undo: %1" +msgstr "&تراجع: %1" + +#: commands/kpCommandHistoryBase.cpp:437 +#, kde-format +msgid "&Undo" +msgstr "&تراجع" + +#: commands/kpCommandHistoryBase.cpp:445 +#, kde-format +msgid "&Redo: %1" +msgstr "&كرّر: %1" + +#: commands/kpCommandHistoryBase.cpp:445 +#, kde-format +msgid "&Redo" +msgstr "&كرّر" + +#: commands/kpCommandHistoryBase.cpp:454 +#, kde-format +msgid "Undo: %1" +msgstr "تراجع: %1" + +#: commands/kpCommandHistoryBase.cpp:454 commands/kpCommandHistoryBase.cpp:635 +#, kde-format +msgid "Undo" +msgstr "تراجع" + +#: commands/kpCommandHistoryBase.cpp:462 +#, kde-format +msgid "Redo: %1" +msgstr "كرّر: %1" + +#: commands/kpCommandHistoryBase.cpp:462 commands/kpCommandHistoryBase.cpp:656 +#, kde-format +msgid "Redo" +msgstr "كرّر" + +#: commands/kpCommandHistoryBase.cpp:597 +#, kde-format +msgid "%1: %2" +msgstr "%1: %2" + +#: commands/kpCommandHistoryBase.cpp:608 +#, kde-format +msgid "%1 more item" +msgid_plural "%1 more items" +msgstr[0] "لا عنصرا إضافيا" +msgstr[1] "عنصر إضافي واحد" +msgstr[2] "عنصران إضافيان" +msgstr[3] "%1 عناصر إضافية" +msgstr[4] "%1 عنصرا إضافيا" +msgstr[5] "%1 عنصر إضافي" + +#: commands/tools/kpToolColorPickerCommand.cpp:59 +#: tools/kpToolColorPicker.cpp:45 +#, kde-format +msgid "Color Picker" +msgstr "منتقي اللون" + +#: commands/tools/kpToolFloodFillCommand.cpp:77 tools/kpToolFloodFill.cpp:55 +#, kde-format +msgid "Flood Fill" +msgstr "ملء بالسوائل" + +#: dialogs/imagelib/effects/kpEffectsDialog.cpp:89 +#, kde-format +msgctxt "@title:window" +msgid "More Image Effects (Selection)" +msgstr "المزيد من التأثيرات الصورة (المحدد)" + +#: dialogs/imagelib/effects/kpEffectsDialog.cpp:92 +#, kde-format +msgctxt "@title:window" +msgid "More Image Effects" +msgstr "المزيد من التأثيرات الصورة" + +#: dialogs/imagelib/effects/kpEffectsDialog.cpp:106 +#, kde-format +msgid "&Effect:" +msgstr "التأ&ثير:" + +#: dialogs/imagelib/effects/kpEffectsDialog.cpp:118 +#, kde-format +msgid "Reduce Colors" +msgstr "انقص الألوان" + +#: dialogs/imagelib/effects/kpEffectsDialog.cpp:119 +#, kde-format +msgid "Soften & Sharpen" +msgstr "ليّن و اشحذ" + +#: dialogs/imagelib/kpDocumentMetaInfoDialog.cpp:121 +#, kde-format +msgctxt "@title:window" +msgid "Document Properties" +msgstr "خصائص المستند" + +#: dialogs/imagelib/kpDocumentMetaInfoDialog.cpp:141 +#, kde-format +msgid "Dots &Per Inch (DPI)" +msgstr "نقاط لكلّ &بوصة" + +#: dialogs/imagelib/kpDocumentMetaInfoDialog.cpp:148 +#: dialogs/imagelib/kpDocumentMetaInfoDialog.cpp:159 +#, kde-format +msgid "Unspecified" +msgstr "غير محدد" + +#: dialogs/imagelib/kpDocumentMetaInfoDialog.cpp:151 +#, kde-format +msgctxt "Horizontal DPI 'x' Vertical DPI" +msgid " x " +msgstr " × " + +#: dialogs/imagelib/kpDocumentMetaInfoDialog.cpp:164 +#: dialogs/imagelib/kpDocumentMetaInfoDialog.cpp:215 +#, kde-format +msgid "Horizontal:" +msgstr "أفقي:" + +#: dialogs/imagelib/kpDocumentMetaInfoDialog.cpp:167 +#: dialogs/imagelib/kpDocumentMetaInfoDialog.cpp:217 +#, kde-format +msgid "Vertical:" +msgstr "عمودي:" + +#: dialogs/imagelib/kpDocumentMetaInfoDialog.cpp:175 +#, kde-format +msgid "" +"

    Dots Per Inch (DPI) specifies the number of pixels of the " +"image that should be printed inside one inch (2.54cm).

    The higher the " +"image's DPI, the smaller the printed image. Note that your printer is " +"unlikely to produce high quality prints if you increase this to more than " +"300 or 600 DPI, depending on the printer.

    If you would like to print " +"the image so that it is the same size as it is displayed on the screen, set " +"the image's DPI values to be the same as the screen's.

    If either DPI " +"value is Unspecified, the image will also be printed to be the same " +"size as on the screen.

    Not all image formats support DPI values. If " +"the format you save in does not support them, they will not be saved.

    " +msgstr "" +"

    نقطة لكل بوصة (DPI) تحدد عدد البكسلات الصورة التي يجب أن تطبع " +"بداخل بوصة واحدة (2.54سم).

    كلما زاد النقاط كلما صغرت الصورة المطبوعة. " +"لا حظ أن طابعتك في الأغلب لن تنتج صورة عالية الجودة إذا زاد عدد النقاط عن " +"300 أو 600 (يعتمد على الطابعة).

    إذا رغبت في طباعة الصورة كما تظهر في " +"الشاشة ، ضع قيمة النقاط في كل بوصة تساوي ما هو معروض في الشاشة.

    إذا " +"كانت قيمة نقاط لكل بوصة غر محددة فإن الصورة ستطبع كحجمها في الشاشة.

    ليس كل أنساق الصور تدعم قيم النقاط لكل بوصة. إذا كان الذي تحفظ فيه لا " +"يدعم هه الميزة ، فإن هذه القيمة لن تحفظ.

    " + +#: dialogs/imagelib/kpDocumentMetaInfoDialog.cpp:205 +#, kde-format +msgid "O&ffset" +msgstr "الإ&زاحة" + +#: dialogs/imagelib/kpDocumentMetaInfoDialog.cpp:225 +#, kde-format +msgid "" +"

    The Offset is the relative position where this image should be " +"placed, compared to other images.

    Not all image formats support the " +"Offset feature. If the format you save in does not support it, the " +"values specified here will not be saved.

    " +msgstr "" +"

    الازاجةهي موضع نسبي يمثل أين يجب وضع هذه الصورة بالمقارنة مع " +"الصورة الأخرى.

    ليس كل أنساق الصور تدعم ميزةالإزاحة. إذا كان " +"التنسيق لا تحفظ به لا يدعمها ، فإن القيمة المخصصة هنا لن تحفظ.

    " + +#: dialogs/imagelib/kpDocumentMetaInfoDialog.cpp:241 +#, kde-format +msgid "&Text Fields" +msgstr "&حقول نصية" + +#: dialogs/imagelib/kpDocumentMetaInfoDialog.cpp:253 +#, kde-format +msgid "&Add Row" +msgstr "أ&ضف صفا" + +#: dialogs/imagelib/kpDocumentMetaInfoDialog.cpp:258 +#, kde-format +msgid "&Delete Row" +msgstr "ا&حذف الصف" + +#: dialogs/imagelib/kpDocumentMetaInfoDialog.cpp:263 +#: widgets/imagelib/effects/kpEffectBalanceWidget.cpp:69 +#, kde-format +msgid "&Reset" +msgstr "استر&جع" + +#: dialogs/imagelib/kpDocumentMetaInfoDialog.cpp:282 +#, kde-format +msgid "" +"

    Text Fields provide extra information about the image. This is " +"probably a comment area that you can freely write any text in.

    However, this is format-specific so the fields could theoretically be " +"computer-interpreted data - that you should not modify - but this is " +"unlikely.

    Not all image formats support Text Fields. If the " +"format you save in does not support them, they will not be saved.

    " +msgstr "" +"

    حقول النص تعطي معلومات إضافية عن الصورة قد تكون منطقة تعليقات " +"حيث يمكنك كتابة أي شيء بحرية.

    على كل حال هذه شيء خاص بالتنسيق ، لذا " +"فقد يمكن تفسير المعلومات الموجودة فيها عن طريق الحاسوب فلذا لا ينبغي " +"تعديلها ، لكن هذا نادرا.

    ليس كل أنساق الصور تدعم حقول النص. " +"إذا لم يكن تحفظ به يدعم هذه الميزة فإنها لن تحفظ.

    " + +#: dialogs/imagelib/kpDocumentMetaInfoDialog.cpp:372 +#, kde-format +msgid "Key" +msgstr "المفتاح" + +#: dialogs/imagelib/kpDocumentMetaInfoDialog.cpp:372 +#, kde-format +msgid "Value" +msgstr "القيمة" + +#: dialogs/imagelib/kpDocumentMetaInfoDialog.cpp:466 +#, kde-format +msgid "The text value \"%1\" on line %2 requires a key." +msgstr "قيمة النص \"%1\" في السطر %2 تحتاج إلى مفتاح." + +#: dialogs/imagelib/kpDocumentMetaInfoDialog.cpp:493 +#, kde-format +msgid "" +"All text keys must be unique. The text key \"%1\" on lines %2 and %3 are " +"identical." +msgstr "" +"يجب أن تكون كل المفاتيح النصية مميزة. المفتاح النصي \"%1\" في السطرين %2 و " +"%3 متطابقان." + +#: dialogs/imagelib/kpDocumentMetaInfoDialog.cpp:746 +#, kde-format +msgctxt "@title:window" +msgid "Invalid Text Fields" +msgstr "حقول نصية غير صالحة" + +#: dialogs/imagelib/transforms/kpTransformPreviewDialog.cpp:139 +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:283 +#, kde-format +msgid "Dimensions" +msgstr "الأبعاد" + +#: dialogs/imagelib/transforms/kpTransformPreviewDialog.cpp:141 +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:290 +#, kde-format +msgid "Original:" +msgstr "الأصلية:" + +#: dialogs/imagelib/transforms/kpTransformPreviewDialog.cpp:145 +#: dialogs/imagelib/transforms/kpTransformPreviewDialog.cpp:253 +#: mainWindow/kpMainWindow_StatusBar.cpp:92 +#: mainWindow/kpMainWindow_StatusBar.cpp:236 +#, kde-format +msgid "%1 x %2" +msgstr "%1 × %2" + +#: dialogs/imagelib/transforms/kpTransformPreviewDialog.cpp:172 +#: dialogs/kpColorSimilarityDialog.cpp:59 +#, kde-format +msgid "Preview" +msgstr "معاينة" + +#: dialogs/imagelib/transforms/kpTransformPreviewDialog.cpp:179 +#: dialogs/kpColorSimilarityDialog.cpp:64 +#, kde-format +msgid "&Update" +msgstr "&حدّث" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:92 +#, kde-format +msgctxt "@title:window" +msgid "Resize / Scale" +msgstr "تغيير الحجم/ القياس" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:161 +#, kde-format +msgid "Ac&t on:" +msgstr "ط&بّق على:" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:167 +#, kde-format +msgid "Entire Image" +msgstr "كامل الصورة" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:170 +#: layers/selections/image/kpAbstractImageSelection.cpp:204 +#, kde-format +msgid "Selection" +msgstr "التحديد" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:173 +#, kde-format +msgid "Text Box" +msgstr "صندوق النص" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:222 +#, kde-format +msgid "Operation" +msgstr "العملية" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:224 +#, kde-format +msgid "" +"
    • Resize: The size of the picture will be increased by " +"creating new areas to the right and/or bottom (filled in with the background " +"color) or decreased by cutting it at the right and/or bottom.
    • Scale: The picture will be expanded by duplicating pixels or " +"squashed by dropping pixels.
    • Smooth Scale: This is the same " +"as Scale except that it blends neighboring pixels to produce a " +"smoother looking picture.
    " +msgstr "" +"
    • تحجيم: سيزيد حجم الصورة بإضافة مناطقإلى اليمين أو/و " +"الأسفل (ستملأ بلون الخلفية)أو بإنقاص مناطق بقطعها من اليمين أو/و الأسفل.
    • تغيير القياس:ستمدد الصورة بتكرار البكسلات أو ستقلص بحذف " +"البكسلات.
    • تغير القياس بنعومة: هذا نفس تغيير القياس " +"باستثناء أنه يخلط البكسلات المجاورة لينتج صورة بمظهر أكثر نعومة.
    " + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:245 +#, kde-format +msgid "&Resize" +msgstr "غيّر ال&حجم" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:250 +#, kde-format +msgid "&Scale" +msgstr "غيّر ال&قياس" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:255 +#, kde-format +msgid "S&mooth Scale" +msgstr "غيّر القياس بس&لاسة" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:285 +#, kde-format +msgid "Width:" +msgstr "العرض:" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:287 +#, kde-format +msgid "Height:" +msgstr "الطول:" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:294 +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:302 +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:314 +#, kde-format +msgid "x" +msgstr "×" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:299 +#, kde-format +msgid "&New:" +msgstr "&جديد:" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:306 +#, kde-format +msgid "&Percent:" +msgstr "النسبة الم&ئوية:" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:312 +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:321 +#: dialogs/kpColorSimilarityDialog.cpp:82 +#, kde-format +msgid "%" +msgstr "%" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:323 +#, kde-format +msgid "Keep &aspect ratio" +msgstr "أبقِ النس&بة المظهرية" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:726 +#, kde-format +msgid "" +"

    Resizing the text box to %1x%2 may take a substantial amount of " +"memory. This can reduce system responsiveness and cause other application " +"resource problems.

    Are you sure you want to resize the text box?

    " +msgstr "" +"

    تحجيم مربع النص إلى %1×%2 قد يستهلك كمية كبيرة من الذاكرة.وهذا قد " +"يسبب في إبطاء استجابة النظام و بعض مشاكل الموارد للتطبيقات الأخرى.

    هل " +"أنت متأكد أنك تريد تحجيم مربع النص؟

    " + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:734 +#, kde-format +msgctxt "@title:window" +msgid "Resize Text Box?" +msgstr "هل أغيّر حجم صندوق النص؟" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:735 +#, kde-format +msgid "R&esize Text Box" +msgstr "غيّر &حجم صندوق النص" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:740 +#: mainWindow/kpMainWindow_Tools.cpp:654 +#, kde-format +msgid "" +"

    Resizing the image to %1x%2 may take a substantial amount of memory. " +"This can reduce system responsiveness and cause other application resource " +"problems.

    Are you sure you want to resize the image?

    " +msgstr "" +"

    تحجيم الصورة إلى %1×%2 قد يستهلك كمية كبيرة من الذاكرة.وهذا قد يسبب " +"في إبطاء استجابة النظام و بعض مشاكل الموارد للتطبيقات الأخرى.

    هل أنت " +"متأكد أنك تريد تحجيم الصورة؟

    " + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:748 +#: mainWindow/kpMainWindow_Tools.cpp:664 +#, kde-format +msgctxt "@title:window" +msgid "Resize Image?" +msgstr "هل أغيّر حجم الصورة؟" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:749 +#: mainWindow/kpMainWindow_Tools.cpp:665 +#, kde-format +msgid "R&esize Image" +msgstr "&غيّر حجم الصورة" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:758 +#, kde-format +msgid "" +"

    Scaling the image to %1x%2 may take a substantial amount of memory. " +"This can reduce system responsiveness and cause other application resource " +"problems.

    Are you sure you want to scale the image?

    " +msgstr "" +"

    تغير قياس الصورة إلى %1×%2 قد يستهلك كمية كبيرة من الذاكرة.وهذا قد " +"يسبب في إبطاء استجابة النظام و بعض مشاكل الموارد للتطبيقات الأخرى.

    هل " +"أنت متأكد أنك تريد تغير قياس الصورة؟

    " + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:766 +#, kde-format +msgctxt "@title:window" +msgid "Scale Image?" +msgstr "هل أغيّر قياس الصورة؟" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:767 +#, kde-format +msgid "Scal&e Image" +msgstr "غيّر &قياس الصورة" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:772 +#, kde-format +msgid "" +"

    Scaling the selection to %1x%2 may take a substantial amount of " +"memory. This can reduce system responsiveness and cause other application " +"resource problems.

    Are you sure you want to scale the selection?

    " +msgstr "" +"

    تغير قياس التحديد إلى %1×%2 قد يستهلك كمية كبيرة من الذاكرة.وهذا قد " +"يسبب في إبطاء استجابة النظام و بعض مشاكل الموارد للتطبيقات الأخرى.

    هل " +"أنت متأكد أنك تريد تغير قياس المحدد؟

    " + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:780 +#, kde-format +msgctxt "@title:window" +msgid "Scale Selection?" +msgstr "هل أغيّر قياس التحديد؟" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:781 +#, kde-format +msgid "Scal&e Selection" +msgstr "غيّر &قياس التحديد" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:790 +#, kde-format +msgid "" +"

    Smooth Scaling the image to %1x%2 may take a substantial amount of " +"memory. This can reduce system responsiveness and cause other application " +"resource problems.

    Are you sure you want to smooth scale the image?" +msgstr "" +"

    تغير قياس الصورة بنعومة إلى %1×%2 قد يستهلك كمية كبيرة من الذاكرة." +"وهذا قد يسبب في إبطاء استجابة النظام و بعض مشاكل الموارد للتطبيقات الأخرى.

    هل أنت متأكد أنك تريد تغيير قياس الصورة بنعومة؟

    " + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:798 +#, kde-format +msgctxt "@title:window" +msgid "Smooth Scale Image?" +msgstr "هل أغير قياس الصورة بنعومة؟" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:799 +#, kde-format +msgid "Smooth Scal&e Image" +msgstr "غيّر قياس الصورة بنعو&مة" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:804 +#, kde-format +msgid "" +"

    Smooth Scaling the selection to %1x%2 may take a substantial amount " +"of memory. This can reduce system responsiveness and cause other application " +"resource problems.

    Are you sure you want to smooth scale the selection?" +"

    " +msgstr "" +"

    تغير قياس المحدد بنعومة إلى %1×%2 قد يستهلك كمية كبيرة من الذاكرة." +"وهذا قد يسبب في إبطاء استجابة النظام و بعض مشاكل الموارد للتطبيقات الأخرى.

    هل أنت متأكد أنك تريد تغيير قياس المحدد بنعومة؟

    " + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:812 +#, kde-format +msgctxt "@title:window" +msgid "Smooth Scale Selection?" +msgstr "أغير مقاس المحدد بنعومة ؟" + +#: dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp:813 +#, kde-format +msgid "Smooth Scal&e Selection" +msgstr "تغيير مقاس المحدد &بنعومة" + +#: dialogs/imagelib/transforms/kpTransformRotateDialog.cpp:67 +#, kde-format +msgctxt "@title:window" +msgid "Rotate Selection" +msgstr "أدر المحدد" + +#: dialogs/imagelib/transforms/kpTransformRotateDialog.cpp:67 +#, kde-format +msgctxt "@title:window" +msgid "Rotate Image" +msgstr "أدر الصورة" + +#: dialogs/imagelib/transforms/kpTransformRotateDialog.cpp:68 +#, kde-format +msgid "After rotate:" +msgstr "بعد الدوران:" + +#: dialogs/imagelib/transforms/kpTransformRotateDialog.cpp:98 +#, kde-format +msgid "Direction" +msgstr "الاتجاه" + +#: dialogs/imagelib/transforms/kpTransformRotateDialog.cpp:124 +#, kde-format +msgid "Cou&nterclockwise" +msgstr "عكسَ عقارب ال&ساعة" + +#: dialogs/imagelib/transforms/kpTransformRotateDialog.cpp:125 +#, kde-format +msgid "C&lockwise" +msgstr "مثلَ عقارب الس&اعة" + +#: dialogs/imagelib/transforms/kpTransformRotateDialog.cpp:149 +#: dialogs/imagelib/transforms/kpTransformSkewDialog.cpp:100 +#, kde-format +msgid "Angle" +msgstr "الزاوية" + +#: dialogs/imagelib/transforms/kpTransformRotateDialog.cpp:153 +#, kde-format +msgid "90 °rees" +msgstr "90 &درجة" + +#: dialogs/imagelib/transforms/kpTransformRotateDialog.cpp:154 +#, kde-format +msgid "180 d&egrees" +msgstr "180 د&رجة" + +#: dialogs/imagelib/transforms/kpTransformRotateDialog.cpp:155 +#, kde-format +msgid "270 de&grees" +msgstr "270 در&جة" + +#: dialogs/imagelib/transforms/kpTransformRotateDialog.cpp:157 +#, kde-format +msgid "C&ustom:" +msgstr "م&خصّص:" + +#: dialogs/imagelib/transforms/kpTransformRotateDialog.cpp:162 +#: dialogs/imagelib/transforms/kpTransformSkewDialog.cpp:113 +#: dialogs/imagelib/transforms/kpTransformSkewDialog.cpp:125 +#, kde-format +msgid "degrees" +msgstr "درجات" + +#: dialogs/imagelib/transforms/kpTransformRotateDialog.cpp:293 +#, kde-format +msgid "" +"

    Rotating the selection to %1x%2 may take a substantial amount of " +"memory. This can reduce system responsiveness and cause other application " +"resource problems.

    Are you sure you want to rotate the selection?

    " +msgstr "" +"

    تدوير المحدد إلى %1×%2 قد يستهلك كمية كبيرة من الذاكرة.وهذا قد يسبب " +"في إبطاء استجابة النظام و بعض مشاكل الموارد للتطبيقات الأخرى.

    هل أنت " +"متأكد أنك تريد تدوير المحدد؟

    " + +#: dialogs/imagelib/transforms/kpTransformRotateDialog.cpp:301 +#, kde-format +msgctxt "@title:window" +msgid "Rotate Selection?" +msgstr "هل أدير المحدد؟" + +#: dialogs/imagelib/transforms/kpTransformRotateDialog.cpp:302 +#, kde-format +msgid "Rotat&e Selection" +msgstr "أد&ر المحدد" + +#: dialogs/imagelib/transforms/kpTransformRotateDialog.cpp:308 +#, kde-format +msgid "" +"

    Rotating the image to %1x%2 may take a substantial amount of memory. " +"This can reduce system responsiveness and cause other application resource " +"problems.

    Are you sure you want to rotate the image?

    " +msgstr "" +"

    تدوير الصورة إلى %1×%2 قد يستهلك كمية كبيرة من الذاكرة.وهذا قد يسبب " +"في إبطاء استجابة النظام و بعض مشاكل الموارد للتطبيقات الأخرى.

    هل أنت " +"متأكد أنك تريد تدوير الصورة؟

    " + +#: dialogs/imagelib/transforms/kpTransformRotateDialog.cpp:316 +#, kde-format +msgctxt "@title:window" +msgid "Rotate Image?" +msgstr "هل أدير الصورة؟" + +#: dialogs/imagelib/transforms/kpTransformRotateDialog.cpp:317 +#, kde-format +msgid "Rotat&e Image" +msgstr "أ&در الصورة" + +#: dialogs/imagelib/transforms/kpTransformSkewDialog.cpp:67 +#, kde-format +msgctxt "@title:window" +msgid "Skew Selection" +msgstr "اجعل المحدد منحرفا" + +#: dialogs/imagelib/transforms/kpTransformSkewDialog.cpp:67 +#, kde-format +msgctxt "@title:window" +msgid "Skew Image" +msgstr "اجعل الصورة منحرفة" + +#: dialogs/imagelib/transforms/kpTransformSkewDialog.cpp:68 +#, kde-format +msgid "After skew:" +msgstr "بعد الانحراف:" + +#: dialogs/imagelib/transforms/kpTransformSkewDialog.cpp:107 +#, kde-format +msgid "&Horizontal:" +msgstr "أفقيا:" + +#: dialogs/imagelib/transforms/kpTransformSkewDialog.cpp:119 +#, kde-format +msgid "&Vertical:" +msgstr "&عموديا:" + +#: dialogs/imagelib/transforms/kpTransformSkewDialog.cpp:254 +#, kde-format +msgid "" +"

    Skewing the selection to %1x%2 may take a substantial amount of " +"memory. This can reduce system responsiveness and cause other application " +"resource problems.

    Are you sure you want to skew the selection?

    " +msgstr "" +"

    حرف المحدد إلى %1×%2 قد يأخذ كمية كمية من الذاكرة .وهذا قد يؤدي إلى " +"خفض مدى استجابة النظام و يسبب مشاكل لبعض التطبيقات .

    هل أنت متأكد أنك " +"تريد حرف المحدد؟

    " + +#: dialogs/imagelib/transforms/kpTransformSkewDialog.cpp:262 +#, kde-format +msgctxt "@title:window" +msgid "Skew Selection?" +msgstr "أتجعل المحدد منحرفا؟" + +#: dialogs/imagelib/transforms/kpTransformSkewDialog.cpp:263 +#, kde-format +msgid "Sk&ew Selection" +msgstr "اجعل الصورة منحرفة" + +#: dialogs/imagelib/transforms/kpTransformSkewDialog.cpp:269 +#, kde-format +msgid "" +"

    Skewing the image to %1x%2 may take a substantial amount of memory. " +"This can reduce system responsiveness and cause other application resource " +"problems.

    Are you sure you want to skew the image?

    " +msgstr "" +"

    حرف الصورة إلى %1×%2 قد يأخذ كمية كمية من الذاكرة .وهذا قد يؤدي إلى " +"خفض مدى استجابة النظام و يسبب مشاكل لبعض التطبيقات .

    هل أنت متأكد أنك " +"تريد حرف المحدد؟

    " + +#: dialogs/imagelib/transforms/kpTransformSkewDialog.cpp:277 +#, kde-format +msgctxt "@title:window" +msgid "Skew Image?" +msgstr "أتجعل الصورة منحرفة؟" + +#: dialogs/imagelib/transforms/kpTransformSkewDialog.cpp:278 +#, kde-format +msgid "Sk&ew Image" +msgstr "اجعل الصورة من&حرفة؟" + +#: dialogs/kpColorSimilarityDialog.cpp:47 +#, kde-format +msgctxt "@title:window" +msgid "Color Similarity" +msgstr "تشابه اللون" + +#: dialogs/kpColorSimilarityDialog.cpp:76 +#, kde-format +msgid "&RGB Color Cube Distance" +msgstr "مسافة مكعب الألوان &آر‌جي‌بي" + +#: dialogs/kpColorSimilarityDialog.cpp:83 +#, kde-format +msgid "Exact Match" +msgstr "المطابقة التامة" + +#: dialogs/kpColorSimilarityDialog.cpp:88 +#, kde-format +msgid "
    What is Color Similarity?" +msgstr "ما هو تشابه الألوان؟" + +#: dialogs/kpDocumentSaveOptionsPreviewDialog.cpp:61 +#, kde-format +msgctxt "@title:window" +msgid "Save Preview" +msgstr "احفظ المعاينة" + +#: dialogs/kpDocumentSaveOptionsPreviewDialog.cpp:129 +#, kde-format +msgid "1 byte (approx. %2%)" +msgid_plural "%1 bytes (approx. %2%)" +msgstr[0] "لا بايتا (تقريباً %2%)" +msgstr[1] "بايت واحد (تقريباً %2%)" +msgstr[2] "بايتان (تقريباً %2%)" +msgstr[3] "%1 بايتات (تقريباً %2%)" +msgstr[4] "%1 بايتا (تقريباً %2%)" +msgstr[5] "%1 بايت (تقريباً %2%)" + +#: document/kpDocument_Open.cpp:114 +#, kde-format +msgid "Could not open \"%1\"." +msgstr "لم أتمكن من فتح \"%1\"." + +#: document/kpDocument_Open.cpp:150 +#, kde-format +msgid "" +"Could not open \"%1\" - unsupported image format.\n" +"The file may be corrupt." +msgstr "" +"لم أتمكن من فتح \"%1\" - هيئة الصورة غير مدعمة.\n" +"أو أنّ الملف معطوب." + +#: document/kpDocument_Save.cpp:82 +#, kde-format +msgid "Could not save image - insufficient information." +msgstr "لم أتمكن من حفظ الصورة - المعلومات غير كافية." + +#: document/kpDocument_Save.cpp:83 +#, kde-format +msgid "" +"URL: %1\n" +"Mimetype: %2" +msgstr "" +"العنوان: %1\n" +"نوع التنسيق: %2" + +#: document/kpDocument_Save.cpp:87 +#, kde-format +msgid "" +msgstr "<فارغ>" + +#: document/kpDocument_Save.cpp:89 +#, kde-format +msgctxt "@title:window" +msgid "Internal Error" +msgstr "خطأ داخلي" + +#: document/kpDocument_Save.cpp:124 +#, kde-format +msgid "" +"

    The %1 format may not be able to preserve all of the image's " +"color information.

    Are you sure you want to save in this format?

    " +msgstr "" +"

    يمكن للهيئة %1 أن لا تحافظ على كل معلومات الألوان للصورة.

    هل أنت متأكد أنك تريد الحفظ بهذه الهيئة ؟

    " + +#: document/kpDocument_Save.cpp:131 +#, kde-format +msgctxt "@title:window" +msgid "Lossy File Format" +msgstr "تنسيق ملف مضيع" + +#: document/kpDocument_Save.cpp:140 +#, kde-format +msgid "" +"

    Saving the image at the low color depth of %1-bit may result in the " +"loss of color information. Any transparency might also be removed.

    Are " +"you sure you want to save at this color depth?

    " +msgstr "" +"

    إنّ حفظ الصورة في عمق اللون %1-بت قد يتسبب في نقص في معلومات اللون. " +"سيتم أيضاً حذف الشفافية.

    هل أنت متأكد من أنك تريد الحفظ بهذا العمق " +"اللوني؟

    " + +#: document/kpDocument_Save.cpp:150 +#, kde-format +msgctxt "@title:window" +msgid "Low Color Depth" +msgstr "عمق اللون منخفض" + +#: document/kpDocument_Save.cpp:275 +#, kde-format +msgid "Could not save image - unable to create temporary file." +msgstr "لم أتمكن من حفظ الصورة - غير قادر على إنشاء الملف المؤقت." + +#: document/kpDocument_Save.cpp:283 +#, kde-format +msgid "Could not save as \"%1\": %2" +msgstr "لم أتمكن من الحفظ كــ \"%1\": %2" + +#: document/kpDocument_Save.cpp:355 document/kpDocument_Save.cpp:398 +#, kde-format +msgid "Error saving image" +msgstr "خطأ حفظ الصورة" + +#: document/kpDocument_Save.cpp:433 +#, kde-format +msgid "Could not save image - failed to upload." +msgstr "لم أتمكن من حفظ الصورة - فشلت في التحميل." + +#: imagelib/transforms/kpTransformAutoCrop.cpp:384 +#, kde-format +msgid "Remove Internal B&order" +msgstr "أزل ال&حافة الداخلية" + +#: imagelib/transforms/kpTransformAutoCrop.cpp:387 +#, kde-format +msgid "Remove Internal Border" +msgstr "أزل الحافة الداخلية" + +#: imagelib/transforms/kpTransformAutoCrop.cpp:391 +#, kde-format +msgid "Autocr&op" +msgstr "قص &تلقائي" + +#: imagelib/transforms/kpTransformAutoCrop.cpp:393 +#, kde-format +msgid "Autocrop" +msgstr "قص تلقائي" + +#: imagelib/transforms/kpTransformAutoCrop.cpp:623 +#, kde-format +msgid "" +"KolourPaint cannot remove the selection's internal border as it could not be " +"located." +msgstr "الرسام لا يمكنه إزالة الحافات الداخلية للاختيار ولا تحديد موقعه." + +#: imagelib/transforms/kpTransformAutoCrop.cpp:625 +#, kde-format +msgctxt "@title:window" +msgid "Cannot Remove Internal Border" +msgstr "غير قادر على إزالة الحافة الداخلية" + +#: imagelib/transforms/kpTransformAutoCrop.cpp:631 +#, kde-format +msgid "" +"KolourPaint cannot automatically crop the image as its border could not be " +"located." +msgstr "" +"الرسام لا يمكنه الحصول تلقائياً على الصورة وكذلك الحدود لا يمكن تحديدها." + +#: imagelib/transforms/kpTransformAutoCrop.cpp:633 +#, kde-format +msgctxt "@title:window" +msgid "Cannot Autocrop" +msgstr "لا يمكن القص التلقائي" + +#: imagelib/transforms/kpTransformCrop.cpp:69 +#: imagelib/transforms/kpTransformCrop.cpp:72 +#, kde-format +msgid "Set as Image" +msgstr "عيّن كصورة" + +#: imagelib/transforms/kpTransformCrop_ImageSelection.cpp:251 +#: mainWindow/kpMainWindow_Edit.cpp:365 mainWindow/kpMainWindow_Image.cpp:320 +#: tools/selection/image/kpAbstractImageSelectionTool.cpp:74 +#, kde-format +msgid "Selection: Create" +msgstr "التحديد: أنشئ" + +#: kolourpaint.cpp:51 +#, kde-format +msgid "KolourPaint" +msgstr "الرسام" + +#: kolourpaint.cpp:53 +#, kde-format +msgid "Paint Program by KDE" +msgstr "برنامج الرسم بواسطة كدي" + +#: kolourpaint.cpp:67 +#, kde-format +msgid "Clarence Dang" +msgstr "كلارانس دانج (Clarence Dang)" + +#: kolourpaint.cpp:67 +#, kde-format +msgid "Project Founder" +msgstr "مؤسس المشروع" + +#: kolourpaint.cpp:69 +#, kde-format +msgid "Thurston Dang" +msgstr "ثارستون دانج (Thurston Dang)" + +#: kolourpaint.cpp:69 +#, kde-format +msgid "Chief Investigator" +msgstr "الباحث الرئيسي" + +#: kolourpaint.cpp:72 +#, kde-format +msgid "Martin Koller" +msgstr "مارتين كوللر (Martin Koller)" + +#: kolourpaint.cpp:72 +#, kde-format +msgid "Scanning Support, Alpha Support, Current Maintainer" +msgstr "دعم المسح ، دعم ألفا ، المشرف الحالي" + +#: kolourpaint.cpp:75 +#, kde-format +msgid "Kristof Borrey" +msgstr "كريستوف بوري (Kristof Borrey)" + +#: kolourpaint.cpp:75 kolourpaint.cpp:78 kolourpaint.cpp:79 +#, kde-format +msgid "Icons" +msgstr "الأيقونات" + +#: kolourpaint.cpp:76 +#, kde-format +msgid "Tasuku Suzuki" +msgstr "Tasuku Suzuki" + +#: kolourpaint.cpp:76 kolourpaint.cpp:77 +#, kde-format +msgid "InputMethod Support" +msgstr "دعم منهج الإدخال" + +#: kolourpaint.cpp:77 +#, kde-format +msgid "Kazuki Ohta" +msgstr "كازوكي أوهتا (Kazuki Ohta)" + +#: kolourpaint.cpp:78 +#, kde-format +msgid "Nuno Pinheiro" +msgstr "نونو بينييرو (Nuno Pinheiro)" + +#: kolourpaint.cpp:79 +#, kde-format +msgid "Danny Allen" +msgstr "داني آلن (Danny Allen)" + +#: kolourpaint.cpp:80 +#, kde-format +msgid "Mike Gashler" +msgstr "مايك جاشلر (Mike Gashler)" + +#: kolourpaint.cpp:80 +#, kde-format +msgid "Image Effects" +msgstr "تأثيرات الصورة" + +#: kolourpaint.cpp:82 +#, kde-format +msgid "Laurent Montel" +msgstr "لوران مونتال (Laurent Montel)" + +#: kolourpaint.cpp:82 +#, kde-format +msgid "KDE 4 Porting" +msgstr "النقل إلى كدي4" + +#: kolourpaint.cpp:83 +#, kde-format +msgid "Christoph Feck" +msgstr "Christoph Feck" + +#: kolourpaint.cpp:83 +#, kde-format +msgid "KF 5 Porting" +msgstr "النقل إلىKF 5" + +#: kolourpaint.cpp:85 +#, kde-format +msgid "" +"Thanks to the many others who have helped to make this program possible." +msgstr "الشّكر إلى آخرين كثيرين الذين ساعدوا لجعل هذا البرنامج ممكنا." + +#: kolourpaint.cpp:90 +#, kde-format +msgid "Image files to open, optionally" +msgstr "ملفات الصورة لفتحه ، اختياري" + +#: kolourpaint.cpp:93 +#, kde-format +msgid "List all readable image MIME types" +msgstr "تسرد جميع أنواع MIME للصور القابلة للقراءة" + +#. i18n: ectx: Menu (view) +#: kolourpaintui.rc:36 +#, kde-format +msgid "&View" +msgstr "&انظر" + +#. i18n: ectx: Menu (image) +#: kolourpaintui.rc:71 kolourpaintui.rc:207 +#, kde-format +msgid "&Image" +msgstr "&صورة" + +#. i18n: ectx: Menu (colors) +#: kolourpaintui.rc:99 +#, kde-format +msgid "&Colors" +msgstr "الأل&وان" + +#. i18n: ectx: ToolBar (mainToolBar) +#: kolourpaintui.rc:147 +#, kde-format +msgid "Main Toolbar" +msgstr "شريط الأدوات الرئيسي" + +#. i18n: ectx: ToolBar (textToolBar) +#: kolourpaintui.rc:162 +#, kde-format +msgid "Text Toolbar" +msgstr "شريط أدوات النص" + +#. i18n: ectx: Menu (selectionToolRMBMenu) +#: kolourpaintui.rc:172 +#, kde-format +msgid "Selection Tool RMB Menu" +msgstr "أداة اختيار قائمة RMB" + +#. i18n: ectx: Menu (edit) +#: kolourpaintui.rc:174 +#, kde-format +msgid "&Edit" +msgstr "&حرر" + +#: kpThumbnail.cpp:129 +#, kde-format +msgctxt "@title:window" +msgid "Thumbnail" +msgstr "المصغرة" + +#: kpViewScrollableContainer.cpp:157 kpViewScrollableContainer.cpp:869 +#: kpViewScrollableContainer.cpp:873 kpViewScrollableContainer.cpp:877 +#, kde-format +msgid "Left drag the handle to resize the image." +msgstr "اسحب المقبض إلى اليسار لتغيير حجم الصورة." + +#: kpViewScrollableContainer.cpp:196 +#, kde-format +msgid "Resize Image: Let go of all the mouse buttons." +msgstr "تحجيم صورة: دع كل أزرار الفأرة" + +#: kpViewScrollableContainer.cpp:228 +#, kde-format +msgid "Resize Image: Right click to cancel." +msgstr "تحجيم الصورة: انقر باليمين للإلغاء." + +#: layers/selections/text/kpTextSelection.cpp:136 +#: tools/selection/text/kpToolText.cpp:55 +#, kde-format +msgid "Text" +msgstr "النص" + +#: lgpl/generic/kpColorCollection.cpp:121 +#, kde-format +msgid "Could not open color palette \"%1\"." +msgstr "لا يمكن فتح لوحة الألوان \"%1\"." + +#: lgpl/generic/kpColorCollection.cpp:154 +#, kde-format +msgid "" +"Could not open color palette \"%1\" - unsupported format.\n" +"The file may be corrupt." +msgstr "" +"لا يمكن فتح لوحة الألوان \"%1\" - التنسيق غير مدعوم. قد يكون الملف تالف." + +#: lgpl/generic/kpColorCollection.cpp:214 +#, kde-format +msgid "Could not open KDE color palette \"%1\"." +msgstr "لا يمكن فتح لوحة الألوان كدي \"%1\"." + +#: lgpl/generic/kpColorCollection.cpp:264 +#, kde-format +msgid "Could not save color palette as \"%1\"." +msgstr "لا يمكن حفظ لوحة الألوان \"%1\"." + +#: lgpl/generic/kpUrlFormatter.cpp:41 lgpl/generic/kpUrlFormatter.cpp:54 +#: widgets/toolbars/kpColorToolBar.cpp:283 +#, kde-format +msgid "Untitled" +msgstr "بدون ‌عنوان" + +#: mainWindow/kpMainWindow_Colors.cpp:61 +#, kde-format +msgid "Use KolourPaint Defaults" +msgstr "استعمل افتراضات الرسام" + +#: mainWindow/kpMainWindow_Colors.cpp:66 +#, kde-format +msgctxt "@item:inmenu colors" +msgid "Use KDE's" +msgstr "استخدم الخاص بكدي" + +#: mainWindow/kpMainWindow_Colors.cpp:78 +#, kde-format +msgctxt "@item:inmenu colors" +msgid "&Open..." +msgstr "ا&فتح ..." + +#: mainWindow/kpMainWindow_Colors.cpp:82 +#, kde-format +msgctxt "@item:inmenu colors" +msgid "Reloa&d" +msgstr "أعد الت&حميل" + +#: mainWindow/kpMainWindow_Colors.cpp:87 +#, kde-format +msgctxt "@item:inmenu colors" +msgid "&Save" +msgstr "ا&حفظ" + +#: mainWindow/kpMainWindow_Colors.cpp:92 +#, kde-format +msgctxt "@item:inmenu colors" +msgid "Save &As..." +msgstr "احفظ &كـ..." + +#: mainWindow/kpMainWindow_Colors.cpp:97 +#, kde-format +msgctxt "@item:inmenu colors" +msgid "Add Row" +msgstr "أضف صفا" + +#: mainWindow/kpMainWindow_Colors.cpp:102 +#, kde-format +msgctxt "@item:inmenu colors" +msgid "Delete Last Row" +msgstr "احذف أخر صف" + +#: mainWindow/kpMainWindow_Colors.cpp:115 +#, kde-format +msgid "Color Box" +msgstr "صندوق الألوان" + +#: mainWindow/kpMainWindow_Colors.cpp:200 +#, kde-format +msgid "" +"The color palette \"%1\" has been modified.\n" +"Do you want to save it?" +msgstr "" +"تم تعديل لوحة الألوان \"%1\" .\n" +"هل تريد حفظها؟" + +#: mainWindow/kpMainWindow_Colors.cpp:212 +#, kde-format +msgid "" +"The KDE color palette \"%1\" has been modified.\n" +"Do you want to save it to a file?" +msgstr "" +"تم تعديل لوحة ألوان كدي \"%1\". \n" +"هل تريد حفظها في ملف؟" + +#: mainWindow/kpMainWindow_Colors.cpp:221 +#, kde-format +msgid "" +"The default color palette has been modified.\n" +"Do you want to save it to a file?" +msgstr "" +"لوحة الألوان الافتراضية عدلت.\n" +"هل تريد حفظها في ملف؟" + +#: mainWindow/kpMainWindow_Colors.cpp:340 +#, kde-format +msgctxt "@title:window" +msgid "Open Color Palette" +msgstr "افتح لوحة الألوان" + +#: mainWindow/kpMainWindow_Colors.cpp:369 +#, kde-format +msgid "" +"The color palette \"%1\" has been modified.\n" +"Reloading will lose all changes since you last saved it.\n" +"Are you sure?" +msgstr "" +"لوحة الألوان \"%1\" عدلت.\n" +"إعادة تحميلها ستضيع كل التعديلات التي لم تحفظ.\n" +"هل أنت متأكد؟" + +#: mainWindow/kpMainWindow_Colors.cpp:374 +#: mainWindow/kpMainWindow_Colors.cpp:387 +#: mainWindow/kpMainWindow_Colors.cpp:396 mainWindow/kpMainWindow_File.cpp:1077 +#: mainWindow/kpMainWindow_File.cpp:1087 +#, kde-format +msgid "&Reload" +msgstr "أعد ال&تحميل" + +#: mainWindow/kpMainWindow_Colors.cpp:382 +#, kde-format +msgid "" +"The KDE color palette \"%1\" has been modified.\n" +"Reloading will lose all changes.\n" +"Are you sure?" +msgstr "" +"لوحة ألوان كدي \"%1\" عدلت.\n" +"إعادة تحميلها ستضيع كل التعديلات.\n" +"هل أنت متأكد؟" + +#: mainWindow/kpMainWindow_Colors.cpp:392 +#, kde-format +msgid "" +"The default color palette has been modified.\n" +"Reloading will lose all changes.\n" +"Are you sure?" +msgstr "" +"لوحة الألوان الافتراضية عدلت.\n" +"إعادة تحميلها ستضيع كل التعديلات.\n" +"هل أنت متأكد؟" + +#: mainWindow/kpMainWindow_Colors.cpp:453 +#, kde-format +msgid "Save Color Palette As" +msgstr "احفظ لوحة الألوان كـ" + +#: mainWindow/kpMainWindow_Edit.cpp:96 +#, kde-format +msgid "Paste in &New Window" +msgstr "ألصق في النافذة ال&جديدة" + +#: mainWindow/kpMainWindow_Edit.cpp:103 +#, kde-format +msgid "&Delete Selection" +msgstr "ا&مح المحدد" + +#: mainWindow/kpMainWindow_Edit.cpp:111 +#, kde-format +msgid "C&opy to File..." +msgstr "ا&نسخ إلى الملف..." + +#: mainWindow/kpMainWindow_Edit.cpp:115 +#, kde-format +msgid "Paste &From File..." +msgstr "ألصق &من الملف..." + +#: mainWindow/kpMainWindow_Edit.cpp:364 mainWindow/kpMainWindow_Edit.cpp:438 +#: mainWindow/kpMainWindow_Image.cpp:323 tools/selection/text/kpToolText.cpp:89 +#, kde-format +msgid "Text: Create Box" +msgstr "النص: أنشئ صندوقا" + +#: mainWindow/kpMainWindow_Edit.cpp:444 +#, kde-format +msgid "Text: Paste" +msgstr "النص: ألصق" + +#: mainWindow/kpMainWindow_Edit.cpp:610 +#, kde-format +msgid "" +"KolourPaint cannot paste the contents of the clipboard as it has an " +"unknown format." +msgstr "" +" لا يمكن لـ كولار بنت لصق محتويات الحافظة لأنها ذات تنسيق غير معروف. " + +#: mainWindow/kpMainWindow_Edit.cpp:612 +#, kde-format +msgid "Cannot Paste" +msgstr "لا أستطيع اللصق" + +#: mainWindow/kpMainWindow_Edit.cpp:691 +#, kde-format +msgid "Text: Delete Box" +msgstr "النص: امح الصندوق" + +#. i18n ("Text: Delete") +#: mainWindow/kpMainWindow_Edit.cpp:692 +#, kde-format +msgid "Selection: Delete" +msgstr "المحدد: امح" + +#: mainWindow/kpMainWindow_Edit.cpp:766 +#, kde-format +msgid "Text: Finish" +msgstr "النص: أنه" + +#: mainWindow/kpMainWindow_Edit.cpp:767 +#, kde-format +msgid "Selection: Deselect" +msgstr "المحدد: أزل التحديد" + +#: mainWindow/kpMainWindow_Edit.cpp:851 +#, kde-format +msgctxt "@title:window" +msgid "Copy to File" +msgstr "انسخ إلى الملف" + +#: mainWindow/kpMainWindow_Edit.cpp:898 +#, kde-format +msgctxt "@title:window" +msgid "Paste From File" +msgstr "ألصق من ملف" + +#: mainWindow/kpMainWindow_File.cpp:105 +#, kde-format +msgid "E&xport..." +msgstr "ص&در..." + +#: mainWindow/kpMainWindow_File.cpp:110 +#, kde-format +msgid "Scan..." +msgstr "امسح ضوئيا..." + +#: mainWindow/kpMainWindow_File.cpp:119 +#, kde-format +msgid "Acquire Screenshot" +msgstr "خذ لقطات الشاشة" + +#: mainWindow/kpMainWindow_File.cpp:123 +#, kde-format +msgid "Properties" +msgstr "الخصائص" + +#: mainWindow/kpMainWindow_File.cpp:129 +#, kde-format +msgid "Reloa&d" +msgstr "أعد الت&حميل" + +#: mainWindow/kpMainWindow_File.cpp:459 +#, kde-format +msgid "All Supported Files (%1)" +msgstr "كلّ الملفّات المدعومة (%1)" + +#: mainWindow/kpMainWindow_File.cpp:484 +#, kde-format +msgctxt "@title:window" +msgid "Open Image" +msgstr "افتح الصورة" + +#: mainWindow/kpMainWindow_File.cpp:547 +#, kde-format +msgid "Failed to open scanning dialog." +msgstr "فشل في فتح حواري المسح الضوئي." + +#: mainWindow/kpMainWindow_File.cpp:548 +#, kde-format +msgctxt "@title:window" +msgid "Scanning Failed" +msgstr "فشل المسح الضوئي" + +#: mainWindow/kpMainWindow_File.cpp:665 +#, kde-format +msgid "Snapshot Delay" +msgstr "تأخير اللقطة:" + +#: mainWindow/kpMainWindow_File.cpp:668 +#, kde-format +msgid " second" +msgid_plural " seconds" +msgstr[0] " ثانية" +msgstr[1] " ثانية واحدة" +msgstr[2] " ثانيتين" +msgstr[3] " ثواني" +msgstr[4] "ثانية" +msgstr[5] " ثانية" + +#: mainWindow/kpMainWindow_File.cpp:669 +#, kde-format +msgid "No delay" +msgstr "بدون تأخير" + +#: mainWindow/kpMainWindow_File.cpp:671 +#, kde-format +msgid "Hide Main Window" +msgstr "أخفِ النافذة الرئيسية" + +#: mainWindow/kpMainWindow_File.cpp:725 +#, kde-format +msgid "Document Properties" +msgstr "خصائص الوثيقة" + +#: mainWindow/kpMainWindow_File.cpp:960 +#, kde-format +msgctxt "@title:window" +msgid "Save Image As" +msgstr "احفظ الصورة كــ" + +#: mainWindow/kpMainWindow_File.cpp:1008 +#, kde-format +msgctxt "@title:window" +msgid "Export" +msgstr "صدّر" + +#: mainWindow/kpMainWindow_File.cpp:1072 +#, kde-format +msgid "" +"The document \"%1\" has been modified.\n" +"Reloading will lose all changes since you last saved it.\n" +"Are you sure?" +msgstr "" +"لقد تم تغيير المستند \"%1\".\n" +"إن إعادة التحميل ستسبب خسارة كل التغييرات التي حصلت منذ آخر عملية للحفظ.\n" +"هل أنت متأكد ؟" + +#: mainWindow/kpMainWindow_File.cpp:1082 +#, kde-format +msgid "" +"The document \"%1\" has been modified.\n" +"Reloading will lose all changes.\n" +"Are you sure?" +msgstr "" +"لقد تمّ تغيير المستند \"%1\".\n" +"إعادة التحميل ستلغي كلّ التغييرات.\n" +"هل أنت متأكد ؟" + +#: mainWindow/kpMainWindow_File.cpp:1330 +#, kde-format +msgctxt "@title:window" +msgid "Print Image" +msgstr "اطبع الصورة" + +#: mainWindow/kpMainWindow_File.cpp:1410 +#, kde-format +msgid "" +"You must save this image before sending it.\n" +"Do you want to save it?" +msgstr "" +"يجب عليك حفظ هذه الصورة قبل إرسالها.\n" +"هل تريد حفظها ؟" + +#: mainWindow/kpMainWindow_File.cpp:1448 +#, kde-format +msgid "" +"The document \"%1\" has been modified.\n" +"Do you want to save it?" +msgstr "" +"لقد تم تغيير المستند \"%1\" .\n" +"هل تريد حفظه ؟" + +#: mainWindow/kpMainWindow_Image.cpp:117 +#, kde-format +msgid "R&esize / Scale..." +msgstr "تغيير ال&حجم/ القياس..." + +#: mainWindow/kpMainWindow_Image.cpp:123 +#, kde-format +msgid "Se&t as Image (Crop)" +msgstr "تعيي&ن كصورة (قصّ)" + +#: mainWindow/kpMainWindow_Image.cpp:133 +#, kde-format +msgid "&Flip (upside down)" +msgstr "اقلب (رأساً على عقب)" + +#: mainWindow/kpMainWindow_Image.cpp:138 +#, kde-format +msgid "Mirror (horizontally)" +msgstr "اعكس (أفقيًّا)" + +#: mainWindow/kpMainWindow_Image.cpp:143 +#, kde-format +msgid "&Rotate..." +msgstr "&دور..." + +#: mainWindow/kpMainWindow_Image.cpp:149 +#, kde-format +msgid "Rotate &Left" +msgstr "د&ور لليسار" + +#: mainWindow/kpMainWindow_Image.cpp:155 +#, kde-format +msgid "Rotate Righ&t" +msgstr "دور لل&يمين" + +#: mainWindow/kpMainWindow_Image.cpp:161 +#, kde-format +msgid "S&kew..." +msgstr "ا&حرف..." + +#: mainWindow/kpMainWindow_Image.cpp:166 +#, kde-format +msgid "Reduce to Mo&nochrome (Dithered)" +msgstr "انقص إلى أ&حادي اللون (منتشر)" + +#: mainWindow/kpMainWindow_Image.cpp:171 +#, kde-format +msgid "Reduce to &Grayscale" +msgstr "انقص إلى تدرج ال&رمادي" + +#: mainWindow/kpMainWindow_Image.cpp:175 +#, kde-format +msgid "&Invert Colors" +msgstr "أع&كس الألوان" + +#: mainWindow/kpMainWindow_Image.cpp:180 +#, kde-format +msgid "C&lear" +msgstr "امس&ح" + +#: mainWindow/kpMainWindow_Image.cpp:185 +#, kde-format +msgid "Make Confidential" +msgstr "اجعله سري" + +#: mainWindow/kpMainWindow_Image.cpp:189 +#, kde-format +msgid "&More Effects..." +msgstr "ال&مزيد من التأثيرات..." + +#: mainWindow/kpMainWindow_Image.cpp:230 +#, kde-format +msgctxt "" +"Image/Selection Menu caption - make sure the translation has the same accel " +"as the Select&ion translation" +msgid "&Image" +msgstr "ال&صورة" + +#: mainWindow/kpMainWindow_Image.cpp:234 +#, kde-format +msgctxt "" +"Image/Selection Menu caption - make sure that translation has the same accel " +"as the &Image translation" +msgid "Select&ion" +msgstr "ال&تحديد" + +#: mainWindow/kpMainWindow_Settings.cpp:67 +#, kde-format +msgid "Show &Path" +msgstr "أظهر ال&مسار" + +#: mainWindow/kpMainWindow_Settings.cpp:72 +#, kde-format +msgid "Draw Anti-Aliased" +msgstr "الرسم الناعم" + +#: mainWindow/kpMainWindow_StatusBar.cpp:156 +#, kde-format +msgid "%1,%2" +msgstr "%1 ، %2" + +#: mainWindow/kpMainWindow_StatusBar.cpp:162 +#, kde-format +msgid "%1,%2 - %3,%4" +msgstr "%1 ، %2 - %3 ، %4" + +#: mainWindow/kpMainWindow_StatusBar.cpp:205 +#: widgets/toolbars/options/kpToolWidgetEraserSize.cpp:112 +#: widgets/toolbars/options/kpToolWidgetSpraycanSize.cpp:92 +#, kde-format +msgid "%1x%2" +msgstr "%1×%2" + +#: mainWindow/kpMainWindow_StatusBar.cpp:264 +#, kde-format +msgid "%1bpp" +msgstr "%1 بت في بكسل" + +#: mainWindow/kpMainWindow_StatusBar.cpp:290 +#: mainWindow/kpMainWindow_View_Zoom.cpp:88 +#, kde-format +msgid "%1%" +msgstr "%1%" + +#: mainWindow/kpMainWindow_Text.cpp:56 +#, kde-format +msgid "Font Family" +msgstr "عائلة الخط" + +#: mainWindow/kpMainWindow_Text.cpp:62 +#, kde-format +msgid "Font Size" +msgstr "حجم الخط" + +#: mainWindow/kpMainWindow_Text.cpp:69 +#, kde-format +msgid "Bold" +msgstr "عريض" + +#: mainWindow/kpMainWindow_Text.cpp:75 +#, kde-format +msgid "Italic" +msgstr "مائل" + +#: mainWindow/kpMainWindow_Text.cpp:81 +#, kde-format +msgid "Underline" +msgstr "تسطير" + +#: mainWindow/kpMainWindow_Text.cpp:87 +#, kde-format +msgid "Strike Through" +msgstr "مسطر عليه" + +#: mainWindow/kpMainWindow_Tools.cpp:135 +#, kde-format +msgid "Previous Tool Option (Group #1)" +msgstr "خيار الأداة السابق (المجموعة رقم 1)" + +#: mainWindow/kpMainWindow_Tools.cpp:141 +#, kde-format +msgid "Next Tool Option (Group #1)" +msgstr "خيار الأداة التالي (المجموعة رقم 1)" + +#: mainWindow/kpMainWindow_Tools.cpp:147 +#, kde-format +msgid "Previous Tool Option (Group #2)" +msgstr "خيار الأداة السابق (المجموعة رقم 2)" + +#: mainWindow/kpMainWindow_Tools.cpp:153 +#, kde-format +msgid "Next Tool Option (Group #2)" +msgstr "خيار الأداة التالي (المجموعة رقم 2)" + +#: mainWindow/kpMainWindow_Tools.cpp:165 +#, kde-format +msgid "&Draw Opaque" +msgstr "ا&رسم معتم" + +#: mainWindow/kpMainWindow_Tools.cpp:170 +#, kde-format +msgid "Draw With Color Similarity..." +msgstr "ارسم بالتشابه اللوني..." + +#: mainWindow/kpMainWindow_Tools.cpp:181 +#, kde-format +msgid "Tool Box" +msgstr "صندوق الأدوات" + +#: mainWindow/kpMainWindow_View.cpp:65 +#, kde-format +msgid "Show &Grid" +msgstr "أظهر ال&شبكة" + +#: mainWindow/kpMainWindow_View_Thumbnail.cpp:63 +#, kde-format +msgid "Show T&humbnail" +msgstr "اعرض الأ&ظافر" + +#: mainWindow/kpMainWindow_View_Thumbnail.cpp:74 +#, kde-format +msgid "Zoo&med Thumbnail Mode" +msgstr "نمط المصغرات الم&كبرة" + +#: mainWindow/kpMainWindow_View_Thumbnail.cpp:84 +#, kde-format +msgid "Enable Thumbnail &Rectangle" +msgstr "تمكين مست&طيل المصغرات" + +#: mainWindow/kpMainWindow_View_Zoom.cpp:110 +#, kde-format +msgid "&Zoom" +msgstr "&كبر" + +#: scan/sanedialog.cpp:41 +#, kde-format +msgctxt "@title:window" +msgid "Acquire Image" +msgstr "اكتسب الصورة" + +#: scan/sanedialog.cpp:73 +#, kde-format +msgid "Opening the selected scanner failed." +msgstr "فشل فتح الماسح الضّوئيّ المحدّد." + +#: tools/flow/kpToolBrush.cpp:35 +#, kde-format +msgid "Brush" +msgstr "الفرشاة" + +#: tools/flow/kpToolBrush.cpp:36 +#, kde-format +msgid "Draw using brushes of different shapes and sizes" +msgstr "ارسم باستخدام فرش مختلفة الأشكال والأحجام" + +#: tools/flow/kpToolBrush.cpp:47 tools/flow/kpToolPen.cpp:57 +#, kde-format +msgid "Click to draw dots or drag to draw strokes." +msgstr "انقر كي ترسم نقاطا أو اسحب كي ترسم شخطات." + +#: tools/flow/kpToolColorEraser.cpp:49 tools/flow/kpToolColorEraser.cpp:78 +#, kde-format +msgid "Color Eraser" +msgstr "ماحي اللون" + +#: tools/flow/kpToolColorEraser.cpp:50 +#, kde-format +msgid "Replaces pixels of the foreground color with the background color" +msgstr "استبدل بكسلات لون الأمامية بلون الخلفية" + +#: tools/flow/kpToolColorEraser.cpp:115 +#, kde-format +msgid "Click or drag to erase pixels of the foreground color." +msgstr "انقر أو اسحب كي تمحي بكسلات لون الأمامية." + +#: tools/flow/kpToolEraser.cpp:42 +#, kde-format +msgid "Eraser" +msgstr "الماحية" + +#: tools/flow/kpToolEraser.cpp:43 +#, kde-format +msgid "Lets you rub out mistakes" +msgstr "يجعلك تمحي الأخطاء" + +#: tools/flow/kpToolEraser.cpp:70 +#, kde-format +msgid "Click or drag to erase." +msgstr "انقر أو اسحب لكي تمحي." + +#: tools/flow/kpToolFlowBase.cpp:304 tools/kpToolColorPicker.cpp:108 +#: tools/kpToolFloodFill.cpp:144 tools/kpToolZoom.cpp:207 +#: tools/polygonal/kpToolPolygonalBase.cpp:435 +#: tools/rectangular/kpToolRectangularBase.cpp:351 +#: tools/selection/kpAbstractSelectionTool.cpp:261 +#: tools/selection/kpAbstractSelectionTool.cpp:528 +#, kde-format +msgid "Let go of all the mouse buttons." +msgstr "اترك أزرار الفأرة." + +#: tools/flow/kpToolPen.cpp:46 +#, kde-format +msgid "Pen" +msgstr "القلم" + +#: tools/flow/kpToolPen.cpp:46 +#, kde-format +msgid "Draws dots and freehand strokes" +msgstr "يرسم نقاطا و شخطات" + +#: tools/flow/kpToolSpraycan.cpp:56 +#, kde-format +msgid "Spraycan" +msgstr "علبة البخاخ" + +#: tools/flow/kpToolSpraycan.cpp:56 +#, kde-format +msgid "Sprays graffiti" +msgstr "يبخّ رسما جداريا" + +#: tools/flow/kpToolSpraycan.cpp:71 +#, kde-format +msgid "Click or drag to spray graffiti." +msgstr "انقر أو اسحب كي ترشّ رسما جداريا" + +#: tools/kpTool.cpp:140 +#, kde-format +msgctxt " ()" +msgid "%1 (%2)" +msgstr "%1 (%2)" + +#: tools/kpTool_UserNotifications.cpp:42 +#, kde-format +msgid "Right click to cancel." +msgstr "انقر باليمين للإلغاء." + +#: tools/kpTool_UserNotifications.cpp:45 +#, kde-format +msgid "Left click to cancel." +msgstr "انقر باليسار للإلغاء." + +#: tools/kpTool_UserNotifications.cpp:75 +#, kde-format +msgid "%1: " +msgstr "%1: " + +#: tools/kpToolColorPicker.cpp:45 +#, kde-format +msgid "Lets you select a color from the image" +msgstr "يجعلك تختار لونا من الصورة" + +#: tools/kpToolColorPicker.cpp:68 +#, kde-format +msgid "Click to select a color." +msgstr "انقر لتختار لونا." + +#: tools/kpToolFloodFill.cpp:55 +#, kde-format +msgid "Fills regions in the image" +msgstr "يملئ مناطق من الصورة" + +#: tools/kpToolFloodFill.cpp:75 +#, kde-format +msgid "Click to fill a region." +msgstr "انقر لتملئ منطقة ما." + +#: tools/kpToolZoom.cpp:74 +#, kde-format +msgid "Zoom" +msgstr "كبّر" + +#: tools/kpToolZoom.cpp:74 +#, kde-format +msgid "Zooms in and out of the image" +msgstr "يكبر ويصغر الصورة" + +#: tools/kpToolZoom.cpp:118 +#, kde-format +msgid "Click to zoom in/out or left drag to zoom into a specific area." +msgstr "انقر كبر/صغر أو اسحب يسارا لتكبر في المنطقة المحددة." + +#: tools/polygonal/kpToolCurve.cpp:103 +#, kde-format +msgid "Curve" +msgstr "المنحنى" + +#: tools/polygonal/kpToolCurve.cpp:104 +#, kde-format +msgid "Draws curves" +msgstr "يرسم منحنيات" + +#: tools/polygonal/kpToolCurve.cpp:118 +#, kde-format +msgid "Drag out the start and end points." +msgstr "اسحب نقاط البداية والنهاية." + +#: tools/polygonal/kpToolCurve.cpp:157 +#, kde-format +msgid "Left drag to set the first control point or right click to finish." +msgstr "اسحب بالزر الأيسر لوضع نقطة التحكم الأولى أو انقر باليمين للانتهاء." + +#: tools/polygonal/kpToolCurve.cpp:162 +#, kde-format +msgid "Right drag to set the first control point or left click to finish." +msgstr "اسحب بالزر الأيمن لوضع نقطة التحكم الأولى أو انقر باليسار للانتهاء." + +#: tools/polygonal/kpToolCurve.cpp:172 +#, kde-format +msgid "Left drag to set the last control point or right click to finish." +msgstr "اسحب بالزر الأيسر لوضع نقطة التحكم الأخيرة أو انقر باليمين للانتهاء." + +#: tools/polygonal/kpToolCurve.cpp:177 +#, kde-format +msgid "Right drag to set the last control point or left click to finish." +msgstr "اسحب بالزر الأيمن لوضع نقطة التحكم الأخيرة أو انقر باليسار للانتهاء." + +#: tools/polygonal/kpToolLine.cpp:41 +#, kde-format +msgid "Line" +msgstr "السطر" + +#: tools/polygonal/kpToolLine.cpp:42 +#, kde-format +msgid "Draws lines" +msgstr "يرسم أسطرا" + +#: tools/polygonal/kpToolLine.cpp:55 +#: tools/rectangular/kpToolRectangularBase.cpp:119 +#, kde-format +msgid "Drag to draw." +msgstr "اسحب كي ترسم." + +#: tools/polygonal/kpToolPolygon.cpp:93 +#, kde-format +msgid "Polygon" +msgstr "المضلّع" + +#: tools/polygonal/kpToolPolygon.cpp:94 +#, kde-format +msgid "Draws polygons" +msgstr "يرسم مضلّعات" + +#: tools/polygonal/kpToolPolygon.cpp:112 tools/polygonal/kpToolPolyline.cpp:60 +#, kde-format +msgid "Drag to draw the first line." +msgstr "اسحب كي ترسم الخط الأول." + +#: tools/polygonal/kpToolPolygon.cpp:182 tools/polygonal/kpToolPolyline.cpp:117 +#, kde-format +msgid "Left drag another line or right click to finish." +msgstr "اسحب بالزرّ الأيسر خطا آخر أو انقر باليمين للانتهاء." + +#: tools/polygonal/kpToolPolygon.cpp:186 tools/polygonal/kpToolPolyline.cpp:121 +#, kde-format +msgid "Right drag another line or left click to finish." +msgstr "اسحب بالزرّ الأيمن خط آخر أو انقر باليسار للانتهاء." + +#: tools/polygonal/kpToolPolyline.cpp:46 +#, kde-format +msgid "Connected Lines" +msgstr "الخطوط المتصلة" + +#: tools/polygonal/kpToolPolyline.cpp:47 +#, kde-format +msgid "Draws connected lines" +msgstr "يرسم خطوطا متصلة" + +#: tools/rectangular/kpToolEllipse.cpp:42 +#, kde-format +msgid "Ellipse" +msgstr "إهليلج" + +#: tools/rectangular/kpToolEllipse.cpp:43 +#, kde-format +msgid "Draws ellipses and circles" +msgstr "يرسم إهليلجا ودائرات" + +#: tools/rectangular/kpToolRectangle.cpp:43 +#, kde-format +msgid "Rectangle" +msgstr "المستطيل" + +#: tools/rectangular/kpToolRectangle.cpp:44 +#, kde-format +msgid "Draws rectangles and squares" +msgstr "يرسم مستطيلات ومربعات" + +#: tools/rectangular/kpToolRoundedRectangle.cpp:42 +#, kde-format +msgid "Rounded Rectangle" +msgstr "مستطيل مدوّر" + +#: tools/rectangular/kpToolRoundedRectangle.cpp:43 +#, kde-format +msgid "Draws rectangles and squares with rounded corners" +msgstr "يرسم مستطيلات ومربعات بزوايا مدوّرة" + +#: tools/selection/image/kpAbstractImageSelectionTool.cpp:83 +#, kde-format +msgid "Left drag to create selection." +msgstr "اسحب بالزر الأيسر لإنشاء الاختيار." + +#: tools/selection/image/kpAbstractImageSelectionTool.cpp:91 +#, kde-format +msgid "Left drag to move selection." +msgstr "اسحب بالزر الأيسر لنقل الاختيار." + +#: tools/selection/image/kpAbstractImageSelectionTool.cpp:99 +#, kde-format +msgid "Left drag to scale selection." +msgstr "اسحب بالزر الأيسر لتغيير قياس المحدد" + +#: tools/selection/image/kpAbstractImageSelectionTool_Transparency.cpp:162 +#, kde-format +msgid "Selection: Opaque" +msgstr "التحديد: معتم" + +#: tools/selection/image/kpAbstractImageSelectionTool_Transparency.cpp:163 +#, kde-format +msgid "Selection: Transparent" +msgstr "التحديد: شفاف" + +#: tools/selection/image/kpAbstractImageSelectionTool_Transparency.cpp:184 +#, kde-format +msgid "Selection: Transparency Color" +msgstr "التحديد: لون الشفافية" + +#: tools/selection/image/kpAbstractImageSelectionTool_Transparency.cpp:205 +#, kde-format +msgid "Selection: Transparency Color Similarity" +msgstr "التحديد: تشابه لون الشفافية" + +#: tools/selection/image/kpToolEllipticalSelection.cpp:42 +#, kde-format +msgid "Selection (Elliptical)" +msgstr "التحديد (إهليلجي)" + +#: tools/selection/image/kpToolEllipticalSelection.cpp:43 +#, kde-format +msgid "Makes an elliptical or circular selection" +msgstr "يختار منطقة عل شكل إهليلج أو دائرة" + +#: tools/selection/image/kpToolFreeFormSelection.cpp:45 +#, kde-format +msgid "Selection (Free-Form)" +msgstr "المحدد (شكل حرّ)" + +#: tools/selection/image/kpToolFreeFormSelection.cpp:46 +#, kde-format +msgid "Makes a free-form selection" +msgstr "يجعلك تختار شكلا حرّا" + +#: tools/selection/image/kpToolRectSelection.cpp:40 +#, kde-format +msgid "Selection (Rectangular)" +msgstr "التحديد (المستطيل)" + +#: tools/selection/image/kpToolRectSelection.cpp:41 +#, kde-format +msgid "Makes a rectangular selection" +msgstr "يصنع تحديد مستطيل" + +#: tools/selection/kpAbstractSelectionTool_Move.cpp:324 +#, kde-format +msgid "Selection: Move" +msgstr "المحدد: حرّك" + +#: tools/selection/kpAbstractSelectionTool_Move.cpp:347 +#, kde-format +msgid "%1: Smear" +msgstr "%1: المسحة" + +#: tools/selection/text/kpToolText.cpp:55 +#, kde-format +msgid "Writes text" +msgstr "يكتب النص" + +#: tools/selection/text/kpToolText_Commands.cpp:69 +#, kde-format +msgid "Text: Backspace" +msgstr "النص: حرف فارغ إلى الوراء" + +#: tools/selection/text/kpToolText_Commands.cpp:86 +#, kde-format +msgid "Text: Delete" +msgstr "النص: اُمح" + +#: tools/selection/text/kpToolText_Commands.cpp:103 +#, kde-format +msgid "Text: New Line" +msgstr "النص: سطر جديد" + +#: tools/selection/text/kpToolText_Commands.cpp:120 +#, kde-format +msgid "Text: Write" +msgstr "النص: اُكتب" + +#: tools/selection/text/kpToolText_Create.cpp:48 +#, kde-format +msgid "Left drag to create text box." +msgstr "اسحب بالزر الأيسر لإنشاء صندوق نص." + +#: tools/selection/text/kpToolText_Move.cpp:42 +#, kde-format +msgid "Left drag to move text box." +msgstr "اسحب بالزر الأيسر لنقل صندوق النص." + +#: tools/selection/text/kpToolText_Move.cpp:61 +#, kde-format +msgid "Text: Move Box" +msgstr "النص: حرك الصندوق" + +#: tools/selection/text/kpToolText_ResizeScale.cpp:42 +#, kde-format +msgid "Left drag to resize text box." +msgstr "اسحب بالزر الأيسر لتغيير حجم صندوق النص." + +#: tools/selection/text/kpToolText_SelectText.cpp:57 +#, kde-format +msgid "Left click to change cursor position." +msgstr "اسحب بالزر الأيسر لتغيير موقع المؤشر." + +#: tools/selection/text/kpToolText_TextStyle.cpp:113 +#, kde-format +msgid "Text: Opaque Background" +msgstr "النص: خلفية معتمة" + +#: tools/selection/text/kpToolText_TextStyle.cpp:114 +#, kde-format +msgid "Text: Transparent Background" +msgstr "النص: خلفية شفافة" + +#: tools/selection/text/kpToolText_TextStyle.cpp:137 +#, kde-format +msgid "Text: Swap Colors" +msgstr "النص: بدّل الألوان" + +#: tools/selection/text/kpToolText_TextStyle.cpp:158 +#, kde-format +msgid "Text: Foreground Color" +msgstr "النص: لون الأمامية" + +#: tools/selection/text/kpToolText_TextStyle.cpp:179 +#, kde-format +msgid "Text: Background Color" +msgstr "النص: لون الخلفية" + +#: tools/selection/text/kpToolText_TextStyle.cpp:215 +#, kde-format +msgid "Text: Font" +msgstr "النص: الخط" + +#: tools/selection/text/kpToolText_TextStyle.cpp:242 +#, kde-format +msgid "Text: Font Size" +msgstr "النص: حجم الخط" + +#: tools/selection/text/kpToolText_TextStyle.cpp:265 +#, kde-format +msgid "Text: Bold" +msgstr "نص: عريض" + +#: tools/selection/text/kpToolText_TextStyle.cpp:287 +#, kde-format +msgid "Text: Italic" +msgstr "النص: مائل" + +#: tools/selection/text/kpToolText_TextStyle.cpp:309 +#, kde-format +msgid "Text: Underline" +msgstr "النص: مسطر تحته" + +#: tools/selection/text/kpToolText_TextStyle.cpp:331 +#, kde-format +msgid "Text: Strike Through" +msgstr "النص: مسطر عليه" + +#: views/kpUnzoomedThumbnailView.cpp:86 +#, kde-format +msgid "Unzoomed Mode - Thumbnail" +msgstr "وضعية غير مكبرة - صورة مصغرة" + +#: views/kpZoomedThumbnailView.cpp:65 +#, kde-format +msgid "%1% - Thumbnail" +msgstr "%1% - مصغرة" + +#: widgets/colorSimilarity/kpColorSimilarityHolder.cpp:70 +#, kde-format +msgid "" +"

    Color Similarity is how similar the colors of different " +"pixels must be, for operations to consider them to be the same.

    If you " +"set it to something other than Exact Match, you can work more " +"effectively with dithered images and photos, in a comparable manner to the " +"\"Magic Wand\" feature of other paint programs.

    This feature applies " +"to:

    • Selections: In Transparent mode, any color in " +"the selection that is similar to the background color will be made " +"transparent.
    • Flood Fill: For regions with similar - " +"but not identical - colored pixels, a higher setting is likely to fill more " +"pixels.
    • Color Eraser: Any pixel whose color is similar " +"to the foreground color will be replaced with the background color.
    • Autocrop and Remove Internal Border: For borders with " +"similar - but not identical - colored pixels, a higher setting is " +"more likely to crop the whole border.

    Higher settings mean that " +"operations consider an increased range of colors to be sufficiently " +"similar so as to be the same. Therefore, you should increase the " +"setting if the above operations are not affecting pixels whose colors you " +"consider to be similar enough.

    However, if they are having too much of " +"an effect and are changing pixels whose colors you do not consider to be " +"similar (e.g. if Flood Fill is changing too many pixels), you should " +"decrease this setting.

    To configure it, click on the cube.

    " +msgstr "" +"

    التشابه اللونيهو كيفيةتشابه ألوان البكسلات المختلفة حتى " +"تعتبرها العمليات أنها متطابقة.

    إذا وضعتها لشيء غير التطابق التام فيمكنك العمل بكفاءة أكثر مع الصور أو الرسومات منعمة الحواف بصورة يمكن " +"مقارنتها مع ميزة\"العصا السحرية\" في تطبيقات الرسم الأخرى.

    هذه الميزة " +"يمكن تطبيقها على: